entries.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. package analytics
  2. import (
  3. "context"
  4. "fmt"
  5. "sort"
  6. "github.com/0xJacky/Nginx-UI/internal/nginx_log/searcher"
  7. )
  8. func (s *service) GetLogEntriesStats(ctx context.Context, req *searcher.SearchRequest) (*EntriesStats, error) {
  9. if req == nil {
  10. return nil, fmt.Errorf("request cannot be nil")
  11. }
  12. // Ensure facets are included for stats calculation
  13. req.IncludeFacets = true
  14. req.FacetFields = []string{"status", "method", "path_exact", "ip", "user_agent"}
  15. req.FacetSize = 10 // Top 10 for lists
  16. result, err := s.searcher.Search(ctx, req)
  17. if err != nil {
  18. return nil, fmt.Errorf("failed to search logs for entries stats: %w", err)
  19. }
  20. stats := &EntriesStats{
  21. TotalEntries: int64(result.TotalHits),
  22. StatusCodeDist: make(map[string]int),
  23. MethodDist: make(map[string]int),
  24. TopPaths: make([]KeyValue, 0),
  25. TopIPs: make([]KeyValue, 0),
  26. TopUserAgents: make([]KeyValue, 0),
  27. }
  28. if result.Facets != nil {
  29. if statusFacet, ok := result.Facets["status"]; ok {
  30. for _, term := range statusFacet.Terms {
  31. stats.StatusCodeDist[term.Term] = term.Count
  32. }
  33. }
  34. if methodFacet, ok := result.Facets["method"]; ok {
  35. for _, term := range methodFacet.Terms {
  36. stats.MethodDist[term.Term] = term.Count
  37. }
  38. }
  39. if pathFacet, ok := result.Facets["path_exact"]; ok {
  40. for _, term := range pathFacet.Terms {
  41. stats.TopPaths = append(stats.TopPaths, KeyValue{Key: term.Term, Value: term.Count})
  42. }
  43. }
  44. if ipFacet, ok := result.Facets["ip"]; ok {
  45. for _, term := range ipFacet.Terms {
  46. stats.TopIPs = append(stats.TopIPs, KeyValue{Key: term.Term, Value: term.Count})
  47. }
  48. }
  49. if uaFacet, ok := result.Facets["user_agent"]; ok {
  50. for _, term := range uaFacet.Terms {
  51. stats.TopUserAgents = append(stats.TopUserAgents, KeyValue{Key: term.Term, Value: term.Count})
  52. }
  53. }
  54. }
  55. // Populate stats if available
  56. if result.Stats != nil {
  57. stats.BytesStats = &BytesStatistics{
  58. Total: result.Stats.TotalBytes,
  59. Average: result.Stats.AvgBytes,
  60. Min: result.Stats.MinBytes,
  61. Max: result.Stats.MaxBytes,
  62. }
  63. stats.ResponseTimeStats = &ResponseTimeStatistics{
  64. Average: result.Stats.AvgReqTime,
  65. Min: result.Stats.MinReqTime,
  66. Max: result.Stats.MaxReqTime,
  67. }
  68. }
  69. return stats, nil
  70. }
  71. // getTopKeyValuesFromMap is a helper to convert a map of counts to a sorted KeyValue slice.
  72. func getTopKeyValuesFromMap(counts map[string]int, limit int) []KeyValue {
  73. kvs := make([]KeyValue, 0, len(counts))
  74. for k, v := range counts {
  75. kvs = append(kvs, KeyValue{Key: k, Value: v})
  76. }
  77. sort.Slice(kvs, func(i, j int) bool {
  78. return kvs[i].Value > kvs[j].Value
  79. })
  80. if limit > 0 && len(kvs) > limit {
  81. return kvs[:limit]
  82. }
  83. return kvs
  84. }
  85. func (s *service) GetTopPaths(ctx context.Context, req *TopListRequest) ([]KeyValue, error) {
  86. if req == nil {
  87. return nil, fmt.Errorf("request cannot be nil")
  88. }
  89. if err := s.ValidateTimeRange(req.StartTime, req.EndTime); err != nil {
  90. return nil, err
  91. }
  92. searchReq := &searcher.SearchRequest{
  93. StartTime: &req.StartTime,
  94. EndTime: &req.EndTime,
  95. LogPaths: []string{req.LogPath},
  96. Limit: 0, // We only need facets
  97. IncludeFacets: true,
  98. FacetFields: []string{"path_exact"},
  99. FacetSize: req.Limit,
  100. UseCache: true,
  101. }
  102. result, err := s.searcher.Search(ctx, searchReq)
  103. if err != nil {
  104. return nil, fmt.Errorf("failed to get top paths: %w", err)
  105. }
  106. topPaths := make([]KeyValue, 0)
  107. if result.Facets != nil {
  108. if pathFacet, ok := result.Facets["path_exact"]; ok {
  109. for _, term := range pathFacet.Terms {
  110. topPaths = append(topPaths, KeyValue{Key: term.Term, Value: term.Count})
  111. }
  112. }
  113. }
  114. return topPaths, nil
  115. }
  116. func (s *service) GetTopIPs(ctx context.Context, req *TopListRequest) ([]KeyValue, error) {
  117. if req == nil {
  118. return nil, fmt.Errorf("request cannot be nil")
  119. }
  120. if err := s.ValidateTimeRange(req.StartTime, req.EndTime); err != nil {
  121. return nil, err
  122. }
  123. searchReq := &searcher.SearchRequest{
  124. StartTime: &req.StartTime,
  125. EndTime: &req.EndTime,
  126. LogPaths: []string{req.LogPath},
  127. Limit: 0,
  128. IncludeFacets: true,
  129. FacetFields: []string{"ip"},
  130. FacetSize: req.Limit,
  131. UseCache: true,
  132. }
  133. result, err := s.searcher.Search(ctx, searchReq)
  134. if err != nil {
  135. return nil, fmt.Errorf("failed to get top IPs: %w", err)
  136. }
  137. topIPs := make([]KeyValue, 0)
  138. if result.Facets != nil {
  139. if ipFacet, ok := result.Facets["ip"]; ok {
  140. for _, term := range ipFacet.Terms {
  141. topIPs = append(topIPs, KeyValue{Key: term.Term, Value: term.Count})
  142. }
  143. }
  144. }
  145. return topIPs, nil
  146. }
  147. func (s *service) GetTopUserAgents(ctx context.Context, req *TopListRequest) ([]KeyValue, error) {
  148. if req == nil {
  149. return nil, fmt.Errorf("request cannot be nil")
  150. }
  151. if err := s.ValidateTimeRange(req.StartTime, req.EndTime); err != nil {
  152. return nil, err
  153. }
  154. searchReq := &searcher.SearchRequest{
  155. StartTime: &req.StartTime,
  156. EndTime: &req.EndTime,
  157. LogPaths: []string{req.LogPath},
  158. Limit: 0,
  159. IncludeFacets: true,
  160. FacetFields: []string{"user_agent"},
  161. FacetSize: req.Limit,
  162. UseCache: true,
  163. }
  164. result, err := s.searcher.Search(ctx, searchReq)
  165. if err != nil {
  166. return nil, fmt.Errorf("failed to get top user agents: %w", err)
  167. }
  168. topUserAgents := make([]KeyValue, 0)
  169. if result.Facets != nil {
  170. if uaFacet, ok := result.Facets["user_agent"]; ok {
  171. for _, term := range uaFacet.Terms {
  172. topUserAgents = append(topUserAgents, KeyValue{Key: term.Term, Value: term.Count})
  173. }
  174. }
  175. }
  176. return topUserAgents, nil
  177. }