log.go 24 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138
  1. package model
  2. import (
  3. "errors"
  4. "fmt"
  5. "strings"
  6. "time"
  7. "github.com/bytedance/sonic"
  8. "github.com/labring/aiproxy/core/common"
  9. "github.com/labring/aiproxy/core/common/config"
  10. "golang.org/x/sync/errgroup"
  11. "gorm.io/gorm"
  12. )
  13. type RequestDetail struct {
  14. CreatedAt time.Time `gorm:"autoCreateTime;index" json:"-"`
  15. RequestBody string `gorm:"type:text" json:"request_body,omitempty"`
  16. ResponseBody string `gorm:"type:text" json:"response_body,omitempty"`
  17. RequestBodyTruncated bool ` json:"request_body_truncated,omitempty"`
  18. ResponseBodyTruncated bool ` json:"response_body_truncated,omitempty"`
  19. ID int `gorm:"primaryKey" json:"id"`
  20. LogID int `gorm:"index" json:"log_id"`
  21. }
  22. func (d *RequestDetail) BeforeSave(_ *gorm.DB) (err error) {
  23. if reqMax := config.GetLogDetailRequestBodyMaxSize(); reqMax > 0 &&
  24. int64(len(d.RequestBody)) > reqMax {
  25. d.RequestBody = common.TruncateByRune(d.RequestBody, int(reqMax)) + "..."
  26. d.RequestBodyTruncated = true
  27. }
  28. if respMax := config.GetLogDetailResponseBodyMaxSize(); respMax > 0 &&
  29. int64(len(d.ResponseBody)) > respMax {
  30. d.ResponseBody = common.TruncateByRune(d.ResponseBody, int(respMax)) + "..."
  31. d.ResponseBodyTruncated = true
  32. }
  33. return
  34. }
  35. type Log struct {
  36. RequestDetail *RequestDetail `gorm:"foreignKey:LogID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"request_detail,omitempty"`
  37. RequestAt time.Time ` json:"request_at"`
  38. RetryAt time.Time ` json:"retry_at,omitempty"`
  39. TTFBMilliseconds ZeroNullInt64 ` json:"ttfb_milliseconds,omitempty"`
  40. CreatedAt time.Time `gorm:"autoCreateTime;index" json:"created_at"`
  41. TokenName string `gorm:"size:32" json:"token_name,omitempty"`
  42. Endpoint EmptyNullString `gorm:"size:64" json:"endpoint,omitempty"`
  43. Content EmptyNullString `gorm:"type:text" json:"content,omitempty"`
  44. GroupID string `gorm:"size:64" json:"group,omitempty"`
  45. Model string `gorm:"size:64" json:"model"`
  46. RequestID EmptyNullString `gorm:"type:char(16);index:,where:request_id is not null" json:"request_id"`
  47. ID int `gorm:"primaryKey" json:"id"`
  48. TokenID int `gorm:"index" json:"token_id,omitempty"`
  49. ChannelID int ` json:"channel,omitempty"`
  50. Code int `gorm:"index" json:"code,omitempty"`
  51. Mode int ` json:"mode,omitempty"`
  52. IP EmptyNullString `gorm:"size:45;index:,where:ip is not null" json:"ip,omitempty"`
  53. RetryTimes ZeroNullInt64 ` json:"retry_times,omitempty"`
  54. Price Price `gorm:"embedded" json:"price,omitempty"`
  55. Usage Usage `gorm:"embedded" json:"usage,omitempty"`
  56. UsedAmount float64 ` json:"used_amount,omitempty"`
  57. // https://platform.openai.com/docs/guides/safety-best-practices#end-user-ids
  58. User EmptyNullString `gorm:"type:text" json:"user,omitempty"`
  59. Metadata map[string]string `gorm:"serializer:fastjson;type:text" json:"metadata,omitempty"`
  60. }
  61. func CreateLogIndexes(db *gorm.DB) error {
  62. var indexes []string
  63. if common.UsingSQLite {
  64. // not support INCLUDE
  65. indexes = []string{
  66. // used by global search logs
  67. "CREATE INDEX IF NOT EXISTS idx_model_creat ON logs (model, created_at DESC)",
  68. // used by global search logs
  69. "CREATE INDEX IF NOT EXISTS idx_channel_creat ON logs (channel_id, created_at DESC)",
  70. // used by global search logs
  71. "CREATE INDEX IF NOT EXISTS idx_channel_model_creat ON logs (channel_id, model, created_at DESC)",
  72. // used by search group logs
  73. "CREATE INDEX IF NOT EXISTS idx_group_creat ON logs (group_id, created_at DESC)",
  74. // used by search group logs
  75. "CREATE INDEX IF NOT EXISTS idx_group_token_creat ON logs (group_id, token_name, created_at DESC)",
  76. // used by search group logs
  77. "CREATE INDEX IF NOT EXISTS idx_group_model_creat ON logs (group_id, model, created_at DESC)",
  78. // used by search group logs
  79. "CREATE INDEX IF NOT EXISTS idx_group_token_model_creat ON logs (group_id, token_name, model, created_at DESC)",
  80. }
  81. } else {
  82. indexes = []string{
  83. // used by global search logs
  84. "CREATE INDEX IF NOT EXISTS idx_model_creat ON logs (model, created_at DESC) INCLUDE (code)",
  85. // used by global search logs
  86. "CREATE INDEX IF NOT EXISTS idx_channel_creat ON logs (channel_id, created_at DESC) INCLUDE (code)",
  87. // used by global search logs
  88. "CREATE INDEX IF NOT EXISTS idx_channel_model_creat ON logs (channel_id, model, created_at DESC) INCLUDE (code)",
  89. // used by search group logs
  90. "CREATE INDEX IF NOT EXISTS idx_group_creat ON logs (group_id, created_at DESC) INCLUDE (code)",
  91. // used by search group logs
  92. "CREATE INDEX IF NOT EXISTS idx_group_token_creat ON logs (group_id, token_name, created_at DESC) INCLUDE (code)",
  93. // used by search group logs
  94. "CREATE INDEX IF NOT EXISTS idx_group_model_creat ON logs (group_id, model, created_at DESC) INCLUDE (code)",
  95. // used by search group logs
  96. "CREATE INDEX IF NOT EXISTS idx_group_token_model_creat ON logs (group_id, token_name, model, created_at DESC) INCLUDE (code)",
  97. }
  98. }
  99. for _, index := range indexes {
  100. if err := db.Exec(index).Error; err != nil {
  101. return err
  102. }
  103. }
  104. return nil
  105. }
  106. const (
  107. contentMaxSize = 2 * 1024 // 2KB
  108. )
  109. func (l *Log) BeforeCreate(_ *gorm.DB) (err error) {
  110. if len(l.Content) > contentMaxSize {
  111. l.Content = common.TruncateByRune(l.Content, contentMaxSize) + "..."
  112. }
  113. if l.CreatedAt.IsZero() {
  114. l.CreatedAt = time.Now()
  115. }
  116. if l.RequestAt.IsZero() {
  117. l.RequestAt = l.CreatedAt
  118. }
  119. return
  120. }
  121. func (l *Log) MarshalJSON() ([]byte, error) {
  122. type Alias Log
  123. a := &struct {
  124. *Alias
  125. CreatedAt int64 `json:"created_at"`
  126. RequestAt int64 `json:"request_at"`
  127. RetryAt int64 `json:"retry_at,omitempty"`
  128. }{
  129. Alias: (*Alias)(l),
  130. CreatedAt: l.CreatedAt.UnixMilli(),
  131. RequestAt: l.RequestAt.UnixMilli(),
  132. }
  133. if !l.RetryAt.IsZero() {
  134. a.RetryAt = l.RetryAt.UnixMilli()
  135. }
  136. return sonic.Marshal(a)
  137. }
  138. func GetLogDetail(logID int) (*RequestDetail, error) {
  139. var detail RequestDetail
  140. err := LogDB.
  141. Model(&RequestDetail{}).
  142. Where("log_id = ?", logID).
  143. First(&detail).Error
  144. if err != nil {
  145. return nil, err
  146. }
  147. return &detail, nil
  148. }
  149. func GetGroupLogDetail(logID int, group string) (*RequestDetail, error) {
  150. if group == "" {
  151. return nil, errors.New("invalid group parameter")
  152. }
  153. var detail RequestDetail
  154. err := LogDB.
  155. Model(&RequestDetail{}).
  156. Joins("JOIN logs ON logs.id = request_details.log_id").
  157. Where("logs.group_id = ?", group).
  158. Where("log_id = ?", logID).
  159. First(&detail).Error
  160. if err != nil {
  161. return nil, err
  162. }
  163. return &detail, nil
  164. }
  165. const defaultCleanLogBatchSize = 5000
  166. func CleanLog(batchSize int, optimize bool) (err error) {
  167. err = cleanLog(batchSize)
  168. if err != nil {
  169. return err
  170. }
  171. err = cleanLogDetail(batchSize)
  172. if err != nil {
  173. return err
  174. }
  175. if optimize {
  176. return optimizeLog()
  177. }
  178. return nil
  179. }
  180. func cleanLog(batchSize int) error {
  181. if batchSize <= 0 {
  182. batchSize = defaultCleanLogBatchSize
  183. }
  184. logStorageHours := config.GetLogStorageHours()
  185. if logStorageHours != 0 {
  186. subQuery := LogDB.
  187. Model(&Log{}).
  188. Where(
  189. "created_at < ?",
  190. time.Now().Add(-time.Duration(logStorageHours)*time.Hour),
  191. ).
  192. Limit(batchSize).
  193. Select("id")
  194. err := LogDB.
  195. Session(&gorm.Session{SkipDefaultTransaction: true}).
  196. Where("id IN (?)", subQuery).
  197. Delete(&Log{}).Error
  198. if err != nil {
  199. return err
  200. }
  201. }
  202. retryLogStorageHours := config.GetRetryLogStorageHours()
  203. if retryLogStorageHours != 0 {
  204. subQuery := LogDB.
  205. Model(&RetryLog{}).
  206. Where(
  207. "created_at < ?",
  208. time.Now().Add(-time.Duration(retryLogStorageHours)*time.Hour),
  209. ).
  210. Limit(batchSize).
  211. Select("id")
  212. err := LogDB.
  213. Session(&gorm.Session{SkipDefaultTransaction: true}).
  214. Where("id IN (?)", subQuery).
  215. Delete(&RetryLog{}).Error
  216. if err != nil {
  217. return err
  218. }
  219. }
  220. return LogDB.
  221. Model(&StoreV2{}).
  222. Where("expires_at < ?", time.Now()).
  223. Delete(&StoreV2{}).
  224. Error
  225. }
  226. func optimizeLog() error {
  227. switch {
  228. case common.UsingSQLite:
  229. return LogDB.Exec("VACUUM").Error
  230. default:
  231. return LogDB.Exec("VACUUM ANALYZE logs").Error
  232. }
  233. }
  234. func cleanLogDetail(batchSize int) error {
  235. detailStorageHours := config.GetLogDetailStorageHours()
  236. if detailStorageHours <= 0 {
  237. return nil
  238. }
  239. if batchSize <= 0 {
  240. batchSize = defaultCleanLogBatchSize
  241. }
  242. subQuery := LogDB.
  243. Model(&RequestDetail{}).
  244. Where(
  245. "created_at < ?",
  246. time.Now().Add(-time.Duration(detailStorageHours)*time.Hour),
  247. ).
  248. Limit(batchSize).
  249. Select("id")
  250. err := LogDB.
  251. Session(&gorm.Session{SkipDefaultTransaction: true}).
  252. Where("id IN (?)", subQuery).
  253. Delete(&RequestDetail{}).Error
  254. if err != nil {
  255. return err
  256. }
  257. return nil
  258. }
  259. func RecordConsumeLog(
  260. requestID string,
  261. createAt time.Time,
  262. requestAt time.Time,
  263. retryAt time.Time,
  264. firstByteAt time.Time,
  265. group string,
  266. code int,
  267. channelID int,
  268. modelName string,
  269. tokenID int,
  270. tokenName string,
  271. endpoint string,
  272. content string,
  273. mode int,
  274. ip string,
  275. retryTimes int,
  276. requestDetail *RequestDetail,
  277. usage Usage,
  278. modelPrice Price,
  279. amount float64,
  280. user string,
  281. metadata map[string]string,
  282. ) error {
  283. if createAt.IsZero() {
  284. createAt = time.Now()
  285. }
  286. if requestAt.IsZero() {
  287. requestAt = createAt
  288. }
  289. if firstByteAt.IsZero() || firstByteAt.Before(requestAt) {
  290. firstByteAt = requestAt
  291. }
  292. log := &Log{
  293. RequestID: EmptyNullString(requestID),
  294. RequestAt: requestAt,
  295. CreatedAt: createAt,
  296. RetryAt: retryAt,
  297. TTFBMilliseconds: ZeroNullInt64(firstByteAt.Sub(requestAt).Milliseconds()),
  298. GroupID: group,
  299. Code: code,
  300. TokenID: tokenID,
  301. TokenName: tokenName,
  302. Model: modelName,
  303. Mode: mode,
  304. IP: EmptyNullString(ip),
  305. ChannelID: channelID,
  306. Endpoint: EmptyNullString(endpoint),
  307. Content: EmptyNullString(content),
  308. RetryTimes: ZeroNullInt64(retryTimes),
  309. RequestDetail: requestDetail,
  310. Price: modelPrice,
  311. Usage: usage,
  312. UsedAmount: amount,
  313. User: EmptyNullString(user),
  314. Metadata: metadata,
  315. }
  316. return LogDB.Create(log).Error
  317. }
  318. func getLogOrder(order string) string {
  319. prefix, suffix, _ := strings.Cut(order, "-")
  320. switch prefix {
  321. case "request_at", "id":
  322. switch suffix {
  323. case "asc":
  324. return prefix + " asc"
  325. default:
  326. return prefix + " desc"
  327. }
  328. default:
  329. return "created_at desc"
  330. }
  331. }
  332. type CodeType string
  333. const (
  334. CodeTypeAll CodeType = "all"
  335. CodeTypeSuccess CodeType = "success"
  336. CodeTypeError CodeType = "error"
  337. )
  338. type GetLogsResult struct {
  339. Logs []*Log `json:"logs"`
  340. Total int64 `json:"total"`
  341. Channels []int `json:"channels,omitempty"`
  342. }
  343. type GetGroupLogsResult struct {
  344. GetLogsResult
  345. Models []string `json:"models"`
  346. TokenNames []string `json:"token_names"`
  347. }
  348. func buildGetLogsQuery(
  349. group string,
  350. startTimestamp time.Time,
  351. endTimestamp time.Time,
  352. modelName string,
  353. requestID string,
  354. tokenID int,
  355. tokenName string,
  356. channelID int,
  357. codeType CodeType,
  358. code int,
  359. ip string,
  360. user string,
  361. ) *gorm.DB {
  362. tx := LogDB.Model(&Log{})
  363. if requestID != "" {
  364. tx = tx.Where("request_id = ?", requestID)
  365. }
  366. if ip != "" {
  367. tx = tx.Where("ip = ?", ip)
  368. }
  369. if group != "" {
  370. tx = tx.Where("group_id = ?", group)
  371. }
  372. if modelName != "" {
  373. tx = tx.Where("model = ?", modelName)
  374. }
  375. if tokenName != "" {
  376. tx = tx.Where("token_name = ?", tokenName)
  377. }
  378. if channelID != 0 {
  379. tx = tx.Where("channel_id = ?", channelID)
  380. }
  381. switch {
  382. case !startTimestamp.IsZero() && !endTimestamp.IsZero():
  383. tx = tx.Where("created_at BETWEEN ? AND ?", startTimestamp, endTimestamp)
  384. case !startTimestamp.IsZero():
  385. tx = tx.Where("created_at >= ?", startTimestamp)
  386. case !endTimestamp.IsZero():
  387. tx = tx.Where("created_at <= ?", endTimestamp)
  388. }
  389. switch codeType {
  390. case CodeTypeSuccess:
  391. tx = tx.Where("code = 200")
  392. case CodeTypeError:
  393. tx = tx.Where("code != 200")
  394. default:
  395. if code != 0 {
  396. tx = tx.Where("code = ?", code)
  397. }
  398. }
  399. if tokenID != 0 {
  400. tx = tx.Where("token_id = ?", tokenID)
  401. }
  402. if user != "" {
  403. tx = tx.Where("user = ?", user)
  404. }
  405. return tx
  406. }
  407. func getLogs(
  408. group string,
  409. startTimestamp time.Time,
  410. endTimestamp time.Time,
  411. modelName string,
  412. requestID string,
  413. tokenID int,
  414. tokenName string,
  415. channelID int,
  416. order string,
  417. codeType CodeType,
  418. code int,
  419. withBody bool,
  420. ip string,
  421. user string,
  422. page int,
  423. perPage int,
  424. ) (int64, []*Log, error) {
  425. var (
  426. total int64
  427. logs []*Log
  428. )
  429. g := new(errgroup.Group)
  430. g.Go(func() error {
  431. return buildGetLogsQuery(
  432. group,
  433. startTimestamp,
  434. endTimestamp,
  435. modelName,
  436. requestID,
  437. tokenID,
  438. tokenName,
  439. channelID,
  440. codeType,
  441. code,
  442. ip,
  443. user,
  444. ).Count(&total).Error
  445. })
  446. g.Go(func() error {
  447. query := buildGetLogsQuery(
  448. group,
  449. startTimestamp,
  450. endTimestamp,
  451. modelName,
  452. requestID,
  453. tokenID,
  454. tokenName,
  455. channelID,
  456. codeType,
  457. code,
  458. ip,
  459. user,
  460. )
  461. if withBody {
  462. query = query.Preload("RequestDetail")
  463. } else {
  464. query = query.Preload("RequestDetail", func(db *gorm.DB) *gorm.DB {
  465. return db.Select("id", "log_id")
  466. })
  467. }
  468. limit, offset := toLimitOffset(page, perPage)
  469. return query.
  470. Order(getLogOrder(order)).
  471. Limit(limit).
  472. Offset(offset).
  473. Find(&logs).Error
  474. })
  475. if err := g.Wait(); err != nil {
  476. return 0, nil, err
  477. }
  478. return total, logs, nil
  479. }
  480. func GetLogs(
  481. startTimestamp time.Time,
  482. endTimestamp time.Time,
  483. modelName string,
  484. requestID string,
  485. channelID int,
  486. order string,
  487. codeType CodeType,
  488. code int,
  489. withBody bool,
  490. ip string,
  491. user string,
  492. page int,
  493. perPage int,
  494. ) (*GetLogsResult, error) {
  495. var (
  496. total int64
  497. logs []*Log
  498. channels []int
  499. )
  500. g := new(errgroup.Group)
  501. g.Go(func() error {
  502. var err error
  503. channels, err = GetUsedChannels(startTimestamp, endTimestamp)
  504. return err
  505. })
  506. g.Go(func() error {
  507. var err error
  508. total, logs, err = getLogs(
  509. "",
  510. startTimestamp,
  511. endTimestamp,
  512. modelName,
  513. requestID,
  514. 0,
  515. "",
  516. channelID,
  517. order,
  518. codeType,
  519. code,
  520. withBody,
  521. ip,
  522. user,
  523. page,
  524. perPage,
  525. )
  526. return err
  527. })
  528. if err := g.Wait(); err != nil {
  529. return nil, err
  530. }
  531. result := &GetLogsResult{
  532. Logs: logs,
  533. Total: total,
  534. Channels: channels,
  535. }
  536. return result, nil
  537. }
  538. func GetGroupLogs(
  539. group string,
  540. startTimestamp time.Time,
  541. endTimestamp time.Time,
  542. modelName string,
  543. requestID string,
  544. tokenID int,
  545. tokenName string,
  546. order string,
  547. codeType CodeType,
  548. code int,
  549. withBody bool,
  550. ip string,
  551. user string,
  552. page int,
  553. perPage int,
  554. ) (*GetGroupLogsResult, error) {
  555. if group == "" {
  556. return nil, errors.New("group is required")
  557. }
  558. var (
  559. total int64
  560. logs []*Log
  561. tokenNames []string
  562. models []string
  563. )
  564. g := new(errgroup.Group)
  565. g.Go(func() error {
  566. var err error
  567. total, logs, err = getLogs(
  568. group,
  569. startTimestamp,
  570. endTimestamp,
  571. modelName,
  572. requestID,
  573. tokenID,
  574. tokenName,
  575. 0,
  576. order,
  577. codeType,
  578. code,
  579. withBody,
  580. ip,
  581. user,
  582. page,
  583. perPage,
  584. )
  585. return err
  586. })
  587. g.Go(func() error {
  588. var err error
  589. tokenNames, err = GetGroupUsedTokenNames(group, startTimestamp, endTimestamp)
  590. return err
  591. })
  592. g.Go(func() error {
  593. var err error
  594. models, err = GetGroupUsedModels(group, tokenName, startTimestamp, endTimestamp)
  595. return err
  596. })
  597. if err := g.Wait(); err != nil {
  598. return nil, err
  599. }
  600. return &GetGroupLogsResult{
  601. GetLogsResult: GetLogsResult{
  602. Logs: logs,
  603. Total: total,
  604. },
  605. Models: models,
  606. TokenNames: tokenNames,
  607. }, nil
  608. }
  609. func buildSearchLogsQuery(
  610. group string,
  611. keyword string,
  612. requestID string,
  613. tokenID int,
  614. tokenName string,
  615. modelName string,
  616. startTimestamp time.Time,
  617. endTimestamp time.Time,
  618. channelID int,
  619. codeType CodeType,
  620. code int,
  621. ip string,
  622. user string,
  623. ) *gorm.DB {
  624. tx := LogDB.Model(&Log{})
  625. if requestID != "" {
  626. tx = tx.Where("request_id = ?", requestID)
  627. }
  628. if ip != "" {
  629. tx = tx.Where("ip = ?", ip)
  630. }
  631. if group != "" {
  632. tx = tx.Where("group_id = ?", group)
  633. }
  634. if modelName != "" {
  635. tx = tx.Where("model = ?", modelName)
  636. }
  637. if tokenName != "" {
  638. tx = tx.Where("token_name = ?", tokenName)
  639. }
  640. if channelID != 0 {
  641. tx = tx.Where("channel_id = ?", channelID)
  642. }
  643. switch {
  644. case !startTimestamp.IsZero() && !endTimestamp.IsZero():
  645. tx = tx.Where("created_at BETWEEN ? AND ?", startTimestamp, endTimestamp)
  646. case !startTimestamp.IsZero():
  647. tx = tx.Where("created_at >= ?", startTimestamp)
  648. case !endTimestamp.IsZero():
  649. tx = tx.Where("created_at <= ?", endTimestamp)
  650. }
  651. switch codeType {
  652. case CodeTypeSuccess:
  653. tx = tx.Where("code = 200")
  654. case CodeTypeError:
  655. tx = tx.Where("code != 200")
  656. default:
  657. if code != 0 {
  658. tx = tx.Where("code = ?", code)
  659. }
  660. }
  661. if tokenID != 0 {
  662. tx = tx.Where("token_id = ?", tokenID)
  663. }
  664. if user != "" {
  665. tx = tx.Where("user = ?", user)
  666. }
  667. // Handle keyword search for zero value fields
  668. if keyword != "" {
  669. var (
  670. conditions []string
  671. values []any
  672. )
  673. if requestID == "" {
  674. conditions = append(conditions, "request_id = ?")
  675. values = append(values, keyword)
  676. }
  677. if group == "" {
  678. conditions = append(conditions, "group_id = ?")
  679. values = append(values, keyword)
  680. }
  681. if modelName == "" {
  682. conditions = append(conditions, "model = ?")
  683. values = append(values, keyword)
  684. }
  685. if tokenName == "" {
  686. conditions = append(conditions, "token_name = ?")
  687. values = append(values, keyword)
  688. }
  689. // if num := String2Int(keyword); num != 0 {
  690. // if channelID == 0 {
  691. // conditions = append(conditions, "channel_id = ?")
  692. // values = append(values, num)
  693. // }
  694. // }
  695. // if ip != "" {
  696. // conditions = append(conditions, "ip = ?")
  697. // values = append(values, ip)
  698. // }
  699. // slow query
  700. // if common.UsingPostgreSQL {
  701. // conditions = append(conditions, "content ILIKE ?")
  702. // } else {
  703. // conditions = append(conditions, "content LIKE ?")
  704. // }
  705. // values = append(values, "%"+keyword+"%")
  706. if len(conditions) > 0 {
  707. tx = tx.Where(fmt.Sprintf("(%s)", strings.Join(conditions, " OR ")), values...)
  708. }
  709. }
  710. return tx
  711. }
  712. func searchLogs(
  713. group string,
  714. keyword string,
  715. requestID string,
  716. tokenID int,
  717. tokenName string,
  718. modelName string,
  719. startTimestamp time.Time,
  720. endTimestamp time.Time,
  721. channelID int,
  722. order string,
  723. codeType CodeType,
  724. code int,
  725. withBody bool,
  726. ip string,
  727. user string,
  728. page int,
  729. perPage int,
  730. ) (int64, []*Log, error) {
  731. var (
  732. total int64
  733. logs []*Log
  734. )
  735. g := new(errgroup.Group)
  736. g.Go(func() error {
  737. return buildSearchLogsQuery(
  738. group,
  739. keyword,
  740. requestID,
  741. tokenID,
  742. tokenName,
  743. modelName,
  744. startTimestamp,
  745. endTimestamp,
  746. channelID,
  747. codeType,
  748. code,
  749. ip,
  750. user,
  751. ).Count(&total).Error
  752. })
  753. g.Go(func() error {
  754. query := buildSearchLogsQuery(
  755. group,
  756. keyword,
  757. requestID,
  758. tokenID,
  759. tokenName,
  760. modelName,
  761. startTimestamp,
  762. endTimestamp,
  763. channelID,
  764. codeType,
  765. code,
  766. ip,
  767. user,
  768. )
  769. if withBody {
  770. query = query.Preload("RequestDetail")
  771. } else {
  772. query = query.Preload("RequestDetail", func(db *gorm.DB) *gorm.DB {
  773. return db.Select("id", "log_id")
  774. })
  775. }
  776. limit, offset := toLimitOffset(page, perPage)
  777. return query.
  778. Order(getLogOrder(order)).
  779. Limit(limit).
  780. Offset(offset).
  781. Find(&logs).Error
  782. })
  783. if err := g.Wait(); err != nil {
  784. return 0, nil, err
  785. }
  786. return total, logs, nil
  787. }
  788. func SearchLogs(
  789. keyword string,
  790. requestID string,
  791. tokenID int,
  792. modelName string,
  793. startTimestamp time.Time,
  794. endTimestamp time.Time,
  795. channelID int,
  796. order string,
  797. codeType CodeType,
  798. code int,
  799. withBody bool,
  800. ip string,
  801. user string,
  802. page int,
  803. perPage int,
  804. ) (*GetLogsResult, error) {
  805. var (
  806. total int64
  807. logs []*Log
  808. channels []int
  809. )
  810. g := new(errgroup.Group)
  811. g.Go(func() error {
  812. var err error
  813. total, logs, err = searchLogs(
  814. "",
  815. keyword,
  816. requestID,
  817. tokenID,
  818. "",
  819. modelName,
  820. startTimestamp,
  821. endTimestamp,
  822. channelID,
  823. order,
  824. codeType,
  825. code,
  826. withBody,
  827. ip,
  828. user,
  829. page,
  830. perPage,
  831. )
  832. return err
  833. })
  834. g.Go(func() error {
  835. var err error
  836. channels, err = GetUsedChannels(startTimestamp, endTimestamp)
  837. return err
  838. })
  839. if err := g.Wait(); err != nil {
  840. return nil, err
  841. }
  842. result := &GetLogsResult{
  843. Logs: logs,
  844. Total: total,
  845. Channels: channels,
  846. }
  847. return result, nil
  848. }
  849. func SearchGroupLogs(
  850. group string,
  851. keyword string,
  852. requestID string,
  853. tokenID int,
  854. tokenName string,
  855. modelName string,
  856. startTimestamp time.Time,
  857. endTimestamp time.Time,
  858. order string,
  859. codeType CodeType,
  860. code int,
  861. withBody bool,
  862. ip string,
  863. user string,
  864. page int,
  865. perPage int,
  866. ) (*GetGroupLogsResult, error) {
  867. if group == "" {
  868. return nil, errors.New("group is required")
  869. }
  870. var (
  871. total int64
  872. logs []*Log
  873. tokenNames []string
  874. models []string
  875. )
  876. g := new(errgroup.Group)
  877. g.Go(func() error {
  878. var err error
  879. total, logs, err = searchLogs(group,
  880. keyword,
  881. requestID,
  882. tokenID,
  883. tokenName,
  884. modelName,
  885. startTimestamp,
  886. endTimestamp,
  887. 0,
  888. order,
  889. codeType,
  890. code,
  891. withBody,
  892. ip,
  893. user,
  894. page,
  895. perPage,
  896. )
  897. return err
  898. })
  899. g.Go(func() error {
  900. var err error
  901. tokenNames, err = GetGroupUsedTokenNames(group, startTimestamp, endTimestamp)
  902. return err
  903. })
  904. g.Go(func() error {
  905. var err error
  906. models, err = GetGroupUsedModels(group, tokenName, startTimestamp, endTimestamp)
  907. return err
  908. })
  909. if err := g.Wait(); err != nil {
  910. return nil, err
  911. }
  912. result := &GetGroupLogsResult{
  913. GetLogsResult: GetLogsResult{
  914. Logs: logs,
  915. Total: total,
  916. },
  917. Models: models,
  918. TokenNames: tokenNames,
  919. }
  920. return result, nil
  921. }
  922. func DeleteOldLog(timestamp time.Time) (int64, error) {
  923. result := LogDB.Where("created_at < ?", timestamp).Delete(&Log{})
  924. return result.RowsAffected, result.Error
  925. }
  926. func DeleteGroupLogs(groupID string) (int64, error) {
  927. if groupID == "" {
  928. return 0, errors.New("group is required")
  929. }
  930. result := LogDB.Where("group_id = ?", groupID).Delete(&Log{})
  931. return result.RowsAffected, result.Error
  932. }
  933. func GetIPGroups(threshold int, start, end time.Time) (map[string][]string, error) {
  934. if threshold < 1 {
  935. threshold = 1
  936. }
  937. var selectClause string
  938. if common.UsingSQLite {
  939. selectClause = "ip, GROUP_CONCAT(DISTINCT group_id) as groups"
  940. } else {
  941. selectClause = "ip, STRING_AGG(DISTINCT group_id, ',') as groups"
  942. }
  943. db := LogDB.Model(&Log{}).
  944. Select(selectClause).
  945. Group("ip").
  946. Having("COUNT(DISTINCT group_id) >= ?", threshold)
  947. switch {
  948. case !start.IsZero() && !end.IsZero():
  949. db = db.Where("created_at BETWEEN ? AND ?", start, end)
  950. case !start.IsZero():
  951. db = db.Where("created_at >= ?", start)
  952. case !end.IsZero():
  953. db = db.Where("created_at <= ?", end)
  954. }
  955. db.Where("ip IS NOT NULL AND ip != '' AND group_id != ''")
  956. result := make(map[string][]string)
  957. rows, err := db.Rows()
  958. if err != nil {
  959. return nil, err
  960. }
  961. defer rows.Close()
  962. for rows.Next() {
  963. var (
  964. ip string
  965. groups string
  966. )
  967. err = rows.Scan(&ip, &groups)
  968. if err != nil {
  969. return nil, err
  970. }
  971. result[ip] = strings.Split(groups, ",")
  972. }
  973. return result, nil
  974. }