log.go 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140
  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 err
  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 err
  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. group string,
  792. tokenID int,
  793. tokenName string,
  794. modelName string,
  795. startTimestamp time.Time,
  796. endTimestamp time.Time,
  797. channelID int,
  798. order string,
  799. codeType CodeType,
  800. code int,
  801. withBody bool,
  802. ip string,
  803. user string,
  804. page int,
  805. perPage int,
  806. ) (*GetLogsResult, error) {
  807. var (
  808. total int64
  809. logs []*Log
  810. channels []int
  811. )
  812. g := new(errgroup.Group)
  813. g.Go(func() error {
  814. var err error
  815. total, logs, err = searchLogs(
  816. group,
  817. keyword,
  818. requestID,
  819. tokenID,
  820. tokenName,
  821. modelName,
  822. startTimestamp,
  823. endTimestamp,
  824. channelID,
  825. order,
  826. codeType,
  827. code,
  828. withBody,
  829. ip,
  830. user,
  831. page,
  832. perPage,
  833. )
  834. return err
  835. })
  836. g.Go(func() error {
  837. var err error
  838. channels, err = GetUsedChannels(startTimestamp, endTimestamp)
  839. return err
  840. })
  841. if err := g.Wait(); err != nil {
  842. return nil, err
  843. }
  844. result := &GetLogsResult{
  845. Logs: logs,
  846. Total: total,
  847. Channels: channels,
  848. }
  849. return result, nil
  850. }
  851. func SearchGroupLogs(
  852. group string,
  853. keyword string,
  854. requestID string,
  855. tokenID int,
  856. tokenName string,
  857. modelName string,
  858. startTimestamp time.Time,
  859. endTimestamp time.Time,
  860. order string,
  861. codeType CodeType,
  862. code int,
  863. withBody bool,
  864. ip string,
  865. user string,
  866. page int,
  867. perPage int,
  868. ) (*GetGroupLogsResult, error) {
  869. if group == "" {
  870. return nil, errors.New("group is required")
  871. }
  872. var (
  873. total int64
  874. logs []*Log
  875. tokenNames []string
  876. models []string
  877. )
  878. g := new(errgroup.Group)
  879. g.Go(func() error {
  880. var err error
  881. total, logs, err = searchLogs(group,
  882. keyword,
  883. requestID,
  884. tokenID,
  885. tokenName,
  886. modelName,
  887. startTimestamp,
  888. endTimestamp,
  889. 0,
  890. order,
  891. codeType,
  892. code,
  893. withBody,
  894. ip,
  895. user,
  896. page,
  897. perPage,
  898. )
  899. return err
  900. })
  901. g.Go(func() error {
  902. var err error
  903. tokenNames, err = GetGroupUsedTokenNames(group, startTimestamp, endTimestamp)
  904. return err
  905. })
  906. g.Go(func() error {
  907. var err error
  908. models, err = GetGroupUsedModels(group, tokenName, startTimestamp, endTimestamp)
  909. return err
  910. })
  911. if err := g.Wait(); err != nil {
  912. return nil, err
  913. }
  914. result := &GetGroupLogsResult{
  915. GetLogsResult: GetLogsResult{
  916. Logs: logs,
  917. Total: total,
  918. },
  919. Models: models,
  920. TokenNames: tokenNames,
  921. }
  922. return result, nil
  923. }
  924. func DeleteOldLog(timestamp time.Time) (int64, error) {
  925. result := LogDB.Where("created_at < ?", timestamp).Delete(&Log{})
  926. return result.RowsAffected, result.Error
  927. }
  928. func DeleteGroupLogs(groupID string) (int64, error) {
  929. if groupID == "" {
  930. return 0, errors.New("group is required")
  931. }
  932. result := LogDB.Where("group_id = ?", groupID).Delete(&Log{})
  933. return result.RowsAffected, result.Error
  934. }
  935. func GetIPGroups(threshold int, start, end time.Time) (map[string][]string, error) {
  936. if threshold < 1 {
  937. threshold = 1
  938. }
  939. var selectClause string
  940. if common.UsingSQLite {
  941. selectClause = "ip, GROUP_CONCAT(DISTINCT group_id) as groups"
  942. } else {
  943. selectClause = "ip, STRING_AGG(DISTINCT group_id, ',') as groups"
  944. }
  945. db := LogDB.Model(&Log{}).
  946. Select(selectClause).
  947. Group("ip").
  948. Having("COUNT(DISTINCT group_id) >= ?", threshold)
  949. switch {
  950. case !start.IsZero() && !end.IsZero():
  951. db = db.Where("created_at BETWEEN ? AND ?", start, end)
  952. case !start.IsZero():
  953. db = db.Where("created_at >= ?", start)
  954. case !end.IsZero():
  955. db = db.Where("created_at <= ?", end)
  956. }
  957. db.Where("ip IS NOT NULL AND ip != '' AND group_id != ''")
  958. result := make(map[string][]string)
  959. rows, err := db.Rows()
  960. if err != nil {
  961. return nil, err
  962. }
  963. defer rows.Close()
  964. for rows.Next() {
  965. var (
  966. ip string
  967. groups string
  968. )
  969. err = rows.Scan(&ip, &groups)
  970. if err != nil {
  971. return nil, err
  972. }
  973. result[ip] = strings.Split(groups, ",")
  974. }
  975. return result, nil
  976. }