log.go 24 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142
  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 (
  429. total int64
  430. logs []*Log
  431. )
  432. g := new(errgroup.Group)
  433. g.Go(func() error {
  434. return buildGetLogsQuery(
  435. group,
  436. startTimestamp,
  437. endTimestamp,
  438. modelName,
  439. requestID,
  440. tokenID,
  441. tokenName,
  442. channelID,
  443. codeType,
  444. code,
  445. ip,
  446. user,
  447. ).Count(&total).Error
  448. })
  449. g.Go(func() error {
  450. query := buildGetLogsQuery(
  451. group,
  452. startTimestamp,
  453. endTimestamp,
  454. modelName,
  455. requestID,
  456. tokenID,
  457. tokenName,
  458. channelID,
  459. codeType,
  460. code,
  461. ip,
  462. user,
  463. )
  464. if withBody {
  465. query = query.Preload("RequestDetail")
  466. } else {
  467. query = query.Preload("RequestDetail", func(db *gorm.DB) *gorm.DB {
  468. return db.Select("id", "log_id")
  469. })
  470. }
  471. limit, offset := toLimitOffset(page, perPage)
  472. return query.
  473. Order(getLogOrder(order)).
  474. Limit(limit).
  475. Offset(offset).
  476. Find(&logs).Error
  477. })
  478. if err := g.Wait(); err != nil {
  479. return 0, nil, err
  480. }
  481. return total, logs, nil
  482. }
  483. func GetLogs(
  484. startTimestamp time.Time,
  485. endTimestamp time.Time,
  486. modelName string,
  487. requestID string,
  488. channelID int,
  489. order string,
  490. codeType CodeType,
  491. code int,
  492. withBody bool,
  493. ip string,
  494. user string,
  495. page int,
  496. perPage int,
  497. ) (*GetLogsResult, error) {
  498. var (
  499. total int64
  500. logs []*Log
  501. channels []int
  502. )
  503. g := new(errgroup.Group)
  504. g.Go(func() error {
  505. var err error
  506. channels, err = GetUsedChannels(startTimestamp, endTimestamp)
  507. return err
  508. })
  509. g.Go(func() error {
  510. var err error
  511. total, logs, err = getLogs(
  512. "",
  513. startTimestamp,
  514. endTimestamp,
  515. modelName,
  516. requestID,
  517. 0,
  518. "",
  519. channelID,
  520. order,
  521. codeType,
  522. code,
  523. withBody,
  524. ip,
  525. user,
  526. page,
  527. perPage,
  528. )
  529. return err
  530. })
  531. if err := g.Wait(); err != nil {
  532. return nil, err
  533. }
  534. result := &GetLogsResult{
  535. Logs: logs,
  536. Total: total,
  537. Channels: channels,
  538. }
  539. return result, nil
  540. }
  541. func GetGroupLogs(
  542. group string,
  543. startTimestamp time.Time,
  544. endTimestamp time.Time,
  545. modelName string,
  546. requestID string,
  547. tokenID int,
  548. tokenName string,
  549. order string,
  550. codeType CodeType,
  551. code int,
  552. withBody bool,
  553. ip string,
  554. user string,
  555. page int,
  556. perPage int,
  557. ) (*GetGroupLogsResult, error) {
  558. if group == "" {
  559. return nil, errors.New("group is required")
  560. }
  561. var (
  562. total int64
  563. logs []*Log
  564. tokenNames []string
  565. models []string
  566. )
  567. g := new(errgroup.Group)
  568. g.Go(func() error {
  569. var err error
  570. total, logs, err = getLogs(
  571. group,
  572. startTimestamp,
  573. endTimestamp,
  574. modelName,
  575. requestID,
  576. tokenID,
  577. tokenName,
  578. 0,
  579. order,
  580. codeType,
  581. code,
  582. withBody,
  583. ip,
  584. user,
  585. page,
  586. perPage,
  587. )
  588. return err
  589. })
  590. g.Go(func() error {
  591. var err error
  592. tokenNames, err = GetGroupUsedTokenNames(group, startTimestamp, endTimestamp)
  593. return err
  594. })
  595. g.Go(func() error {
  596. var err error
  597. models, err = GetGroupUsedModels(group, tokenName, startTimestamp, endTimestamp)
  598. return err
  599. })
  600. if err := g.Wait(); err != nil {
  601. return nil, err
  602. }
  603. return &GetGroupLogsResult{
  604. GetLogsResult: GetLogsResult{
  605. Logs: logs,
  606. Total: total,
  607. },
  608. Models: models,
  609. TokenNames: tokenNames,
  610. }, nil
  611. }
  612. func buildSearchLogsQuery(
  613. group string,
  614. keyword string,
  615. requestID string,
  616. tokenID int,
  617. tokenName string,
  618. modelName string,
  619. startTimestamp time.Time,
  620. endTimestamp time.Time,
  621. channelID int,
  622. codeType CodeType,
  623. code int,
  624. ip string,
  625. user string,
  626. ) *gorm.DB {
  627. tx := LogDB.Model(&Log{})
  628. if requestID != "" {
  629. tx = tx.Where("request_id = ?", requestID)
  630. }
  631. if ip != "" {
  632. tx = tx.Where("ip = ?", ip)
  633. }
  634. if group != "" {
  635. tx = tx.Where("group_id = ?", group)
  636. }
  637. if modelName != "" {
  638. tx = tx.Where("model = ?", modelName)
  639. }
  640. if tokenName != "" {
  641. tx = tx.Where("token_name = ?", tokenName)
  642. }
  643. if channelID != 0 {
  644. tx = tx.Where("channel_id = ?", channelID)
  645. }
  646. switch {
  647. case !startTimestamp.IsZero() && !endTimestamp.IsZero():
  648. tx = tx.Where("created_at BETWEEN ? AND ?", startTimestamp, endTimestamp)
  649. case !startTimestamp.IsZero():
  650. tx = tx.Where("created_at >= ?", startTimestamp)
  651. case !endTimestamp.IsZero():
  652. tx = tx.Where("created_at <= ?", endTimestamp)
  653. }
  654. switch codeType {
  655. case CodeTypeSuccess:
  656. tx = tx.Where("code = 200")
  657. case CodeTypeError:
  658. tx = tx.Where("code != 200")
  659. default:
  660. if code != 0 {
  661. tx = tx.Where("code = ?", code)
  662. }
  663. }
  664. if tokenID != 0 {
  665. tx = tx.Where("token_id = ?", tokenID)
  666. }
  667. if user != "" {
  668. tx = tx.Where("user = ?", user)
  669. }
  670. // Handle keyword search for zero value fields
  671. if keyword != "" {
  672. var (
  673. conditions []string
  674. values []any
  675. )
  676. if requestID == "" {
  677. conditions = append(conditions, "request_id = ?")
  678. values = append(values, keyword)
  679. }
  680. if group == "" {
  681. conditions = append(conditions, "group_id = ?")
  682. values = append(values, keyword)
  683. }
  684. if modelName == "" {
  685. conditions = append(conditions, "model = ?")
  686. values = append(values, keyword)
  687. }
  688. if tokenName == "" {
  689. conditions = append(conditions, "token_name = ?")
  690. values = append(values, keyword)
  691. }
  692. // if num := String2Int(keyword); num != 0 {
  693. // if channelID == 0 {
  694. // conditions = append(conditions, "channel_id = ?")
  695. // values = append(values, num)
  696. // }
  697. // }
  698. // if ip != "" {
  699. // conditions = append(conditions, "ip = ?")
  700. // values = append(values, ip)
  701. // }
  702. // slow query
  703. // if common.UsingPostgreSQL {
  704. // conditions = append(conditions, "content ILIKE ?")
  705. // } else {
  706. // conditions = append(conditions, "content LIKE ?")
  707. // }
  708. // values = append(values, "%"+keyword+"%")
  709. if len(conditions) > 0 {
  710. tx = tx.Where(fmt.Sprintf("(%s)", strings.Join(conditions, " OR ")), values...)
  711. }
  712. }
  713. return tx
  714. }
  715. func searchLogs(
  716. group string,
  717. keyword string,
  718. requestID string,
  719. tokenID int,
  720. tokenName string,
  721. modelName string,
  722. startTimestamp time.Time,
  723. endTimestamp time.Time,
  724. channelID int,
  725. order string,
  726. codeType CodeType,
  727. code int,
  728. withBody bool,
  729. ip string,
  730. user string,
  731. page int,
  732. perPage int,
  733. ) (int64, []*Log, error) {
  734. var (
  735. total int64
  736. logs []*Log
  737. )
  738. g := new(errgroup.Group)
  739. g.Go(func() error {
  740. return buildSearchLogsQuery(
  741. group,
  742. keyword,
  743. requestID,
  744. tokenID,
  745. tokenName,
  746. modelName,
  747. startTimestamp,
  748. endTimestamp,
  749. channelID,
  750. codeType,
  751. code,
  752. ip,
  753. user,
  754. ).Count(&total).Error
  755. })
  756. g.Go(func() error {
  757. query := buildSearchLogsQuery(
  758. group,
  759. keyword,
  760. requestID,
  761. tokenID,
  762. tokenName,
  763. modelName,
  764. startTimestamp,
  765. endTimestamp,
  766. channelID,
  767. codeType,
  768. code,
  769. ip,
  770. user,
  771. )
  772. if withBody {
  773. query = query.Preload("RequestDetail")
  774. } else {
  775. query = query.Preload("RequestDetail", func(db *gorm.DB) *gorm.DB {
  776. return db.Select("id", "log_id")
  777. })
  778. }
  779. limit, offset := toLimitOffset(page, perPage)
  780. return query.
  781. Order(getLogOrder(order)).
  782. Limit(limit).
  783. Offset(offset).
  784. Find(&logs).Error
  785. })
  786. if err := g.Wait(); err != nil {
  787. return 0, nil, err
  788. }
  789. return total, logs, nil
  790. }
  791. func SearchLogs(
  792. keyword string,
  793. requestID string,
  794. tokenID int,
  795. modelName string,
  796. startTimestamp time.Time,
  797. endTimestamp time.Time,
  798. channelID int,
  799. order string,
  800. codeType CodeType,
  801. code int,
  802. withBody bool,
  803. ip string,
  804. user string,
  805. page int,
  806. perPage int,
  807. ) (*GetLogsResult, error) {
  808. var (
  809. total int64
  810. logs []*Log
  811. channels []int
  812. )
  813. g := new(errgroup.Group)
  814. g.Go(func() error {
  815. var err error
  816. total, logs, err = searchLogs(
  817. "",
  818. keyword,
  819. requestID,
  820. tokenID,
  821. "",
  822. modelName,
  823. startTimestamp,
  824. endTimestamp,
  825. channelID,
  826. order,
  827. codeType,
  828. code,
  829. withBody,
  830. ip,
  831. user,
  832. page,
  833. perPage,
  834. )
  835. return err
  836. })
  837. g.Go(func() error {
  838. var err error
  839. channels, err = GetUsedChannels(startTimestamp, endTimestamp)
  840. return err
  841. })
  842. if err := g.Wait(); err != nil {
  843. return nil, err
  844. }
  845. result := &GetLogsResult{
  846. Logs: logs,
  847. Total: total,
  848. Channels: channels,
  849. }
  850. return result, nil
  851. }
  852. func SearchGroupLogs(
  853. group string,
  854. keyword string,
  855. requestID string,
  856. tokenID int,
  857. tokenName string,
  858. modelName string,
  859. startTimestamp time.Time,
  860. endTimestamp time.Time,
  861. order string,
  862. codeType CodeType,
  863. code int,
  864. withBody bool,
  865. ip string,
  866. user string,
  867. page int,
  868. perPage int,
  869. ) (*GetGroupLogsResult, error) {
  870. if group == "" {
  871. return nil, errors.New("group is required")
  872. }
  873. var (
  874. total int64
  875. logs []*Log
  876. tokenNames []string
  877. models []string
  878. )
  879. g := new(errgroup.Group)
  880. g.Go(func() error {
  881. var err error
  882. total, logs, err = searchLogs(group,
  883. keyword,
  884. requestID,
  885. tokenID,
  886. tokenName,
  887. modelName,
  888. startTimestamp,
  889. endTimestamp,
  890. 0,
  891. order,
  892. codeType,
  893. code,
  894. withBody,
  895. ip,
  896. user,
  897. page,
  898. perPage,
  899. )
  900. return err
  901. })
  902. g.Go(func() error {
  903. var err error
  904. tokenNames, err = GetGroupUsedTokenNames(group, startTimestamp, endTimestamp)
  905. return err
  906. })
  907. g.Go(func() error {
  908. var err error
  909. models, err = GetGroupUsedModels(group, tokenName, startTimestamp, endTimestamp)
  910. return err
  911. })
  912. if err := g.Wait(); err != nil {
  913. return nil, err
  914. }
  915. result := &GetGroupLogsResult{
  916. GetLogsResult: GetLogsResult{
  917. Logs: logs,
  918. Total: total,
  919. },
  920. Models: models,
  921. TokenNames: tokenNames,
  922. }
  923. return result, nil
  924. }
  925. func DeleteOldLog(timestamp time.Time) (int64, error) {
  926. result := LogDB.Where("created_at < ?", timestamp).Delete(&Log{})
  927. return result.RowsAffected, result.Error
  928. }
  929. func DeleteGroupLogs(groupID string) (int64, error) {
  930. if groupID == "" {
  931. return 0, errors.New("group is required")
  932. }
  933. result := LogDB.Where("group_id = ?", groupID).Delete(&Log{})
  934. return result.RowsAffected, result.Error
  935. }
  936. func GetIPGroups(threshold int, start, end time.Time) (map[string][]string, error) {
  937. if threshold < 1 {
  938. threshold = 1
  939. }
  940. var selectClause string
  941. if common.UsingSQLite {
  942. selectClause = "ip, GROUP_CONCAT(DISTINCT group_id) as groups"
  943. } else {
  944. selectClause = "ip, STRING_AGG(DISTINCT group_id, ',') as groups"
  945. }
  946. db := LogDB.Model(&Log{}).
  947. Select(selectClause).
  948. Group("ip").
  949. Having("COUNT(DISTINCT group_id) >= ?", threshold)
  950. switch {
  951. case !start.IsZero() && !end.IsZero():
  952. db = db.Where("created_at BETWEEN ? AND ?", start, end)
  953. case !start.IsZero():
  954. db = db.Where("created_at >= ?", start)
  955. case !end.IsZero():
  956. db = db.Where("created_at <= ?", end)
  957. }
  958. db.Where("ip IS NOT NULL AND ip != '' AND group_id != ''")
  959. result := make(map[string][]string)
  960. rows, err := db.Rows()
  961. if err != nil {
  962. return nil, err
  963. }
  964. defer rows.Close()
  965. for rows.Next() {
  966. var (
  967. ip string
  968. groups string
  969. )
  970. err = rows.Scan(&ip, &groups)
  971. if err != nil {
  972. return nil, err
  973. }
  974. result[ip] = strings.Split(groups, ",")
  975. }
  976. return result, nil
  977. }