log.go 24 KB

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