log.go 24 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075
  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 ` json:"token_name,omitempty"`
  42. Endpoint EmptyNullString ` json:"endpoint,omitempty"`
  43. Content EmptyNullString `gorm:"type:text" json:"content,omitempty"`
  44. GroupID string ` json:"group,omitempty"`
  45. Model string ` json:"model"`
  46. RequestID EmptyNullString `gorm:"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:"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 ` 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(&Store{}).
  222. Where("expires_at < ?", time.Now()).
  223. Delete(&Store{}).
  224. Error
  225. }
  226. func optimizeLog() error {
  227. switch {
  228. case common.UsingPostgreSQL:
  229. return LogDB.Exec("VACUUM ANALYZE logs").Error
  230. case common.UsingMySQL:
  231. return LogDB.Exec("OPTIMIZE TABLE logs").Error
  232. case common.UsingSQLite:
  233. return LogDB.Exec("VACUUM").Error
  234. }
  235. return nil
  236. }
  237. func cleanLogDetail(batchSize int) error {
  238. detailStorageHours := config.GetLogDetailStorageHours()
  239. if detailStorageHours <= 0 {
  240. return nil
  241. }
  242. if batchSize <= 0 {
  243. batchSize = defaultCleanLogBatchSize
  244. }
  245. subQuery := LogDB.
  246. Model(&RequestDetail{}).
  247. Where(
  248. "created_at < ?",
  249. time.Now().Add(-time.Duration(detailStorageHours)*time.Hour),
  250. ).
  251. Limit(batchSize).
  252. Select("id")
  253. err := LogDB.
  254. Session(&gorm.Session{SkipDefaultTransaction: true}).
  255. Where("id IN (?)", subQuery).
  256. Delete(&RequestDetail{}).Error
  257. if err != nil {
  258. return err
  259. }
  260. return nil
  261. }
  262. func RecordConsumeLog(
  263. requestID string,
  264. createAt time.Time,
  265. requestAt time.Time,
  266. retryAt time.Time,
  267. firstByteAt time.Time,
  268. group string,
  269. code int,
  270. channelID int,
  271. modelName string,
  272. tokenID int,
  273. tokenName string,
  274. endpoint string,
  275. content string,
  276. mode int,
  277. ip string,
  278. retryTimes int,
  279. requestDetail *RequestDetail,
  280. usage Usage,
  281. modelPrice Price,
  282. amount float64,
  283. user string,
  284. metadata map[string]string,
  285. ) error {
  286. if createAt.IsZero() {
  287. createAt = time.Now()
  288. }
  289. if requestAt.IsZero() {
  290. requestAt = createAt
  291. }
  292. if firstByteAt.IsZero() || firstByteAt.Before(requestAt) {
  293. firstByteAt = requestAt
  294. }
  295. log := &Log{
  296. RequestID: EmptyNullString(requestID),
  297. RequestAt: requestAt,
  298. CreatedAt: createAt,
  299. RetryAt: retryAt,
  300. TTFBMilliseconds: ZeroNullInt64(firstByteAt.Sub(requestAt).Milliseconds()),
  301. GroupID: group,
  302. Code: code,
  303. TokenID: tokenID,
  304. TokenName: tokenName,
  305. Model: modelName,
  306. Mode: mode,
  307. IP: EmptyNullString(ip),
  308. ChannelID: channelID,
  309. Endpoint: EmptyNullString(endpoint),
  310. Content: EmptyNullString(content),
  311. RetryTimes: ZeroNullInt64(retryTimes),
  312. RequestDetail: requestDetail,
  313. Price: modelPrice,
  314. Usage: usage,
  315. UsedAmount: amount,
  316. User: EmptyNullString(user),
  317. Metadata: metadata,
  318. }
  319. return LogDB.Create(log).Error
  320. }
  321. func getLogOrder(order string) string {
  322. prefix, suffix, _ := strings.Cut(order, "-")
  323. switch prefix {
  324. case "request_at", "id":
  325. switch suffix {
  326. case "asc":
  327. return prefix + " asc"
  328. default:
  329. return prefix + " desc"
  330. }
  331. default:
  332. return "created_at desc"
  333. }
  334. }
  335. type CodeType string
  336. const (
  337. CodeTypeAll CodeType = "all"
  338. CodeTypeSuccess CodeType = "success"
  339. CodeTypeError CodeType = "error"
  340. )
  341. type GetLogsResult struct {
  342. Logs []*Log `json:"logs"`
  343. Total int64 `json:"total"`
  344. Channels []int `json:"channels,omitempty"`
  345. }
  346. type GetGroupLogsResult struct {
  347. GetLogsResult
  348. Models []string `json:"models"`
  349. TokenNames []string `json:"token_names"`
  350. }
  351. func buildGetLogsQuery(
  352. group string,
  353. startTimestamp time.Time,
  354. endTimestamp time.Time,
  355. modelName string,
  356. requestID string,
  357. tokenID int,
  358. tokenName string,
  359. channelID int,
  360. codeType CodeType,
  361. code int,
  362. ip string,
  363. user string,
  364. ) *gorm.DB {
  365. tx := LogDB.Model(&Log{})
  366. if requestID != "" {
  367. tx = tx.Where("request_id = ?", requestID)
  368. }
  369. if ip != "" {
  370. tx = tx.Where("ip = ?", ip)
  371. }
  372. if group != "" {
  373. tx = tx.Where("group_id = ?", group)
  374. }
  375. if modelName != "" {
  376. tx = tx.Where("model = ?", modelName)
  377. }
  378. if tokenName != "" {
  379. tx = tx.Where("token_name = ?", tokenName)
  380. }
  381. if channelID != 0 {
  382. tx = tx.Where("channel_id = ?", channelID)
  383. }
  384. switch {
  385. case !startTimestamp.IsZero() && !endTimestamp.IsZero():
  386. tx = tx.Where("created_at BETWEEN ? AND ?", startTimestamp, endTimestamp)
  387. case !startTimestamp.IsZero():
  388. tx = tx.Where("created_at >= ?", startTimestamp)
  389. case !endTimestamp.IsZero():
  390. tx = tx.Where("created_at <= ?", endTimestamp)
  391. }
  392. switch codeType {
  393. case CodeTypeSuccess:
  394. tx = tx.Where("code = 200")
  395. case CodeTypeError:
  396. tx = tx.Where("code != 200")
  397. default:
  398. if code != 0 {
  399. tx = tx.Where("code = ?", code)
  400. }
  401. }
  402. if tokenID != 0 {
  403. tx = tx.Where("token_id = ?", tokenID)
  404. }
  405. if user != "" {
  406. tx = tx.Where("user = ?", user)
  407. }
  408. return tx
  409. }
  410. func getLogs(
  411. group string,
  412. startTimestamp time.Time,
  413. endTimestamp time.Time,
  414. modelName string,
  415. requestID string,
  416. tokenID int,
  417. tokenName string,
  418. channelID int,
  419. order string,
  420. codeType CodeType,
  421. code int,
  422. withBody bool,
  423. ip string,
  424. user string,
  425. page int,
  426. perPage int,
  427. ) (int64, []*Log, error) {
  428. var total int64
  429. var logs []*Log
  430. g := new(errgroup.Group)
  431. g.Go(func() error {
  432. return buildGetLogsQuery(
  433. group,
  434. startTimestamp,
  435. endTimestamp,
  436. modelName,
  437. requestID,
  438. tokenID,
  439. tokenName,
  440. channelID,
  441. codeType,
  442. code,
  443. ip,
  444. user,
  445. ).Count(&total).Error
  446. })
  447. g.Go(func() error {
  448. query := buildGetLogsQuery(
  449. group,
  450. startTimestamp,
  451. endTimestamp,
  452. modelName,
  453. requestID,
  454. tokenID,
  455. tokenName,
  456. channelID,
  457. codeType,
  458. code,
  459. ip,
  460. user,
  461. )
  462. if withBody {
  463. query = query.Preload("RequestDetail")
  464. } else {
  465. query = query.Preload("RequestDetail", func(db *gorm.DB) *gorm.DB {
  466. return db.Select("id", "log_id")
  467. })
  468. }
  469. limit, offset := toLimitOffset(page, perPage)
  470. return query.
  471. Order(getLogOrder(order)).
  472. Limit(limit).
  473. Offset(offset).
  474. Find(&logs).Error
  475. })
  476. if err := g.Wait(); err != nil {
  477. return 0, nil, err
  478. }
  479. return total, logs, nil
  480. }
  481. func GetLogs(
  482. startTimestamp time.Time,
  483. endTimestamp time.Time,
  484. modelName string,
  485. requestID string,
  486. channelID int,
  487. order string,
  488. codeType CodeType,
  489. code int,
  490. withBody bool,
  491. ip string,
  492. user string,
  493. page int,
  494. perPage int,
  495. ) (*GetLogsResult, error) {
  496. var total int64
  497. var logs []*Log
  498. var channels []int
  499. g := new(errgroup.Group)
  500. g.Go(func() error {
  501. var err error
  502. channels, err = GetUsedChannels(startTimestamp, endTimestamp)
  503. return err
  504. })
  505. g.Go(func() error {
  506. var err error
  507. total, logs, err = getLogs(
  508. "",
  509. startTimestamp,
  510. endTimestamp,
  511. modelName,
  512. requestID,
  513. 0,
  514. "",
  515. channelID,
  516. order,
  517. codeType,
  518. code,
  519. withBody,
  520. ip,
  521. user,
  522. page,
  523. perPage,
  524. )
  525. return err
  526. })
  527. if err := g.Wait(); err != nil {
  528. return nil, err
  529. }
  530. result := &GetLogsResult{
  531. Logs: logs,
  532. Total: total,
  533. Channels: channels,
  534. }
  535. return result, nil
  536. }
  537. func GetGroupLogs(
  538. group string,
  539. startTimestamp time.Time,
  540. endTimestamp time.Time,
  541. modelName string,
  542. requestID string,
  543. tokenID int,
  544. tokenName string,
  545. order string,
  546. codeType CodeType,
  547. code int,
  548. withBody bool,
  549. ip string,
  550. user string,
  551. page int,
  552. perPage int,
  553. ) (*GetGroupLogsResult, error) {
  554. if group == "" {
  555. return nil, errors.New("group is required")
  556. }
  557. var (
  558. total int64
  559. logs []*Log
  560. tokenNames []string
  561. models []string
  562. )
  563. g := new(errgroup.Group)
  564. g.Go(func() error {
  565. var err error
  566. total, logs, err = getLogs(
  567. group,
  568. startTimestamp,
  569. endTimestamp,
  570. modelName,
  571. requestID,
  572. tokenID,
  573. tokenName,
  574. 0,
  575. order,
  576. codeType,
  577. code,
  578. withBody,
  579. ip,
  580. user,
  581. page,
  582. perPage,
  583. )
  584. return err
  585. })
  586. g.Go(func() error {
  587. var err error
  588. tokenNames, err = GetGroupUsedTokenNames(group, startTimestamp, endTimestamp)
  589. return err
  590. })
  591. g.Go(func() error {
  592. var err error
  593. models, err = GetGroupUsedModels(group, tokenName, startTimestamp, endTimestamp)
  594. return err
  595. })
  596. if err := g.Wait(); err != nil {
  597. return nil, err
  598. }
  599. return &GetGroupLogsResult{
  600. GetLogsResult: GetLogsResult{
  601. Logs: logs,
  602. Total: total,
  603. },
  604. Models: models,
  605. TokenNames: tokenNames,
  606. }, nil
  607. }
  608. func buildSearchLogsQuery(
  609. group string,
  610. keyword string,
  611. requestID string,
  612. tokenID int,
  613. tokenName string,
  614. modelName string,
  615. startTimestamp time.Time,
  616. endTimestamp time.Time,
  617. channelID int,
  618. codeType CodeType,
  619. code int,
  620. ip string,
  621. user string,
  622. ) *gorm.DB {
  623. tx := LogDB.Model(&Log{})
  624. if requestID != "" {
  625. tx = tx.Where("request_id = ?", requestID)
  626. }
  627. if ip != "" {
  628. tx = tx.Where("ip = ?", ip)
  629. }
  630. if group != "" {
  631. tx = tx.Where("group_id = ?", group)
  632. }
  633. if modelName != "" {
  634. tx = tx.Where("model = ?", modelName)
  635. }
  636. if tokenName != "" {
  637. tx = tx.Where("token_name = ?", tokenName)
  638. }
  639. if channelID != 0 {
  640. tx = tx.Where("channel_id = ?", channelID)
  641. }
  642. switch {
  643. case !startTimestamp.IsZero() && !endTimestamp.IsZero():
  644. tx = tx.Where("created_at BETWEEN ? AND ?", startTimestamp, endTimestamp)
  645. case !startTimestamp.IsZero():
  646. tx = tx.Where("created_at >= ?", startTimestamp)
  647. case !endTimestamp.IsZero():
  648. tx = tx.Where("created_at <= ?", endTimestamp)
  649. }
  650. switch codeType {
  651. case CodeTypeSuccess:
  652. tx = tx.Where("code = 200")
  653. case CodeTypeError:
  654. tx = tx.Where("code != 200")
  655. default:
  656. if code != 0 {
  657. tx = tx.Where("code = ?", code)
  658. }
  659. }
  660. if tokenID != 0 {
  661. tx = tx.Where("token_id = ?", tokenID)
  662. }
  663. if user != "" {
  664. tx = tx.Where("user = ?", user)
  665. }
  666. // Handle keyword search for zero value fields
  667. if keyword != "" {
  668. var conditions []string
  669. var values []any
  670. if requestID == "" {
  671. conditions = append(conditions, "request_id = ?")
  672. values = append(values, keyword)
  673. }
  674. if group == "" {
  675. conditions = append(conditions, "group_id = ?")
  676. values = append(values, keyword)
  677. }
  678. if modelName == "" {
  679. conditions = append(conditions, "model = ?")
  680. values = append(values, keyword)
  681. }
  682. if tokenName == "" {
  683. conditions = append(conditions, "token_name = ?")
  684. values = append(values, keyword)
  685. }
  686. // if num := String2Int(keyword); num != 0 {
  687. // if channelID == 0 {
  688. // conditions = append(conditions, "channel_id = ?")
  689. // values = append(values, num)
  690. // }
  691. // }
  692. // if ip != "" {
  693. // conditions = append(conditions, "ip = ?")
  694. // values = append(values, ip)
  695. // }
  696. // slow query
  697. // if common.UsingPostgreSQL {
  698. // conditions = append(conditions, "content ILIKE ?")
  699. // } else {
  700. // conditions = append(conditions, "content LIKE ?")
  701. // }
  702. // values = append(values, "%"+keyword+"%")
  703. if len(conditions) > 0 {
  704. tx = tx.Where(fmt.Sprintf("(%s)", strings.Join(conditions, " OR ")), values...)
  705. }
  706. }
  707. return tx
  708. }
  709. func searchLogs(
  710. group string,
  711. keyword string,
  712. requestID string,
  713. tokenID int,
  714. tokenName string,
  715. modelName string,
  716. startTimestamp time.Time,
  717. endTimestamp time.Time,
  718. channelID int,
  719. order string,
  720. codeType CodeType,
  721. code int,
  722. withBody bool,
  723. ip string,
  724. user string,
  725. page int,
  726. perPage int,
  727. ) (int64, []*Log, error) {
  728. var total int64
  729. var logs []*Log
  730. g := new(errgroup.Group)
  731. g.Go(func() error {
  732. return buildSearchLogsQuery(
  733. group,
  734. keyword,
  735. requestID,
  736. tokenID,
  737. tokenName,
  738. modelName,
  739. startTimestamp,
  740. endTimestamp,
  741. channelID,
  742. codeType,
  743. code,
  744. ip,
  745. user,
  746. ).Count(&total).Error
  747. })
  748. g.Go(func() error {
  749. query := buildSearchLogsQuery(
  750. group,
  751. keyword,
  752. requestID,
  753. tokenID,
  754. tokenName,
  755. modelName,
  756. startTimestamp,
  757. endTimestamp,
  758. channelID,
  759. codeType,
  760. code,
  761. ip,
  762. user,
  763. )
  764. if withBody {
  765. query = query.Preload("RequestDetail")
  766. } else {
  767. query = query.Preload("RequestDetail", func(db *gorm.DB) *gorm.DB {
  768. return db.Select("id", "log_id")
  769. })
  770. }
  771. limit, offset := toLimitOffset(page, perPage)
  772. return query.
  773. Order(getLogOrder(order)).
  774. Limit(limit).
  775. Offset(offset).
  776. Find(&logs).Error
  777. })
  778. if err := g.Wait(); err != nil {
  779. return 0, nil, err
  780. }
  781. return total, logs, nil
  782. }
  783. func SearchLogs(
  784. keyword string,
  785. requestID string,
  786. tokenID int,
  787. modelName string,
  788. startTimestamp time.Time,
  789. endTimestamp time.Time,
  790. channelID int,
  791. order string,
  792. codeType CodeType,
  793. code int,
  794. withBody bool,
  795. ip string,
  796. user string,
  797. page int,
  798. perPage int,
  799. ) (*GetLogsResult, error) {
  800. var total int64
  801. var logs []*Log
  802. var channels []int
  803. g := new(errgroup.Group)
  804. g.Go(func() error {
  805. var err error
  806. total, logs, err = searchLogs(
  807. "",
  808. keyword,
  809. requestID,
  810. tokenID,
  811. "",
  812. modelName,
  813. startTimestamp,
  814. endTimestamp,
  815. channelID,
  816. order,
  817. codeType,
  818. code,
  819. withBody,
  820. ip,
  821. user,
  822. page,
  823. perPage,
  824. )
  825. return err
  826. })
  827. g.Go(func() error {
  828. var err error
  829. channels, err = GetUsedChannels(startTimestamp, endTimestamp)
  830. return err
  831. })
  832. if err := g.Wait(); err != nil {
  833. return nil, err
  834. }
  835. result := &GetLogsResult{
  836. Logs: logs,
  837. Total: total,
  838. Channels: channels,
  839. }
  840. return result, nil
  841. }
  842. func SearchGroupLogs(
  843. group string,
  844. keyword string,
  845. requestID string,
  846. tokenID int,
  847. tokenName string,
  848. modelName string,
  849. startTimestamp time.Time,
  850. endTimestamp time.Time,
  851. order string,
  852. codeType CodeType,
  853. code int,
  854. withBody bool,
  855. ip string,
  856. user string,
  857. page int,
  858. perPage int,
  859. ) (*GetGroupLogsResult, error) {
  860. if group == "" {
  861. return nil, errors.New("group is required")
  862. }
  863. var (
  864. total int64
  865. logs []*Log
  866. tokenNames []string
  867. models []string
  868. )
  869. g := new(errgroup.Group)
  870. g.Go(func() error {
  871. var err error
  872. total, logs, err = searchLogs(group,
  873. keyword,
  874. requestID,
  875. tokenID,
  876. tokenName,
  877. modelName,
  878. startTimestamp,
  879. endTimestamp,
  880. 0,
  881. order,
  882. codeType,
  883. code,
  884. withBody,
  885. ip,
  886. user,
  887. page,
  888. perPage,
  889. )
  890. return err
  891. })
  892. g.Go(func() error {
  893. var err error
  894. tokenNames, err = GetGroupUsedTokenNames(group, startTimestamp, endTimestamp)
  895. return err
  896. })
  897. g.Go(func() error {
  898. var err error
  899. models, err = GetGroupUsedModels(group, tokenName, startTimestamp, endTimestamp)
  900. return err
  901. })
  902. if err := g.Wait(); err != nil {
  903. return nil, err
  904. }
  905. result := &GetGroupLogsResult{
  906. GetLogsResult: GetLogsResult{
  907. Logs: logs,
  908. Total: total,
  909. },
  910. Models: models,
  911. TokenNames: tokenNames,
  912. }
  913. return result, nil
  914. }
  915. func DeleteOldLog(timestamp time.Time) (int64, error) {
  916. result := LogDB.Where("created_at < ?", timestamp).Delete(&Log{})
  917. return result.RowsAffected, result.Error
  918. }
  919. func DeleteGroupLogs(groupID string) (int64, error) {
  920. if groupID == "" {
  921. return 0, errors.New("group is required")
  922. }
  923. result := LogDB.Where("group_id = ?", groupID).Delete(&Log{})
  924. return result.RowsAffected, result.Error
  925. }
  926. func GetIPGroups(threshold int, start, end time.Time) (map[string][]string, error) {
  927. if threshold < 1 {
  928. threshold = 1
  929. }
  930. var selectClause string
  931. if common.UsingSQLite {
  932. selectClause = "ip, GROUP_CONCAT(DISTINCT group_id) as groups"
  933. } else {
  934. selectClause = "ip, STRING_AGG(DISTINCT group_id, ',') as groups"
  935. }
  936. db := LogDB.Model(&Log{}).
  937. Select(selectClause).
  938. Group("ip").
  939. Having("COUNT(DISTINCT group_id) >= ?", threshold)
  940. switch {
  941. case !start.IsZero() && !end.IsZero():
  942. db = db.Where("created_at BETWEEN ? AND ?", start, end)
  943. case !start.IsZero():
  944. db = db.Where("created_at >= ?", start)
  945. case !end.IsZero():
  946. db = db.Where("created_at <= ?", end)
  947. }
  948. db.Where("ip IS NOT NULL AND ip != '' AND group_id != ''")
  949. result := make(map[string][]string)
  950. rows, err := db.Rows()
  951. if err != nil {
  952. return nil, err
  953. }
  954. defer rows.Close()
  955. for rows.Next() {
  956. var ip string
  957. var groups string
  958. err = rows.Scan(&ip, &groups)
  959. if err != nil {
  960. return nil, err
  961. }
  962. result[ip] = strings.Split(groups, ",")
  963. }
  964. return result, nil
  965. }