service.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. package analytics
  2. import (
  3. "context"
  4. "fmt"
  5. "github.com/0xJacky/Nginx-UI/internal/nginx_log/searcher"
  6. "github.com/0xJacky/Nginx-UI/internal/nginx_log/utils"
  7. )
  8. // Service defines the interface for analytics operations
  9. type Service interface {
  10. GetDashboardAnalytics(ctx context.Context, req *DashboardQueryRequest) (*DashboardAnalytics, error)
  11. GetLogEntriesStats(ctx context.Context, req *searcher.SearchRequest) (*EntriesStats, error)
  12. GetGeoDistribution(ctx context.Context, req *GeoQueryRequest) (*GeoDistribution, error)
  13. GetGeoDistributionByCountry(ctx context.Context, req *GeoQueryRequest, countryCode string) (*GeoDistribution, error)
  14. GetTopCountries(ctx context.Context, req *GeoQueryRequest) ([]CountryStats, error)
  15. GetTopCities(ctx context.Context, req *GeoQueryRequest) ([]CityStats, error)
  16. GetGeoStatsForIP(ctx context.Context, req *GeoQueryRequest, ip string) (*CityStats, error)
  17. GetTopPaths(ctx context.Context, req *TopListRequest) ([]KeyValue, error)
  18. GetTopIPs(ctx context.Context, req *TopListRequest) ([]KeyValue, error)
  19. GetTopUserAgents(ctx context.Context, req *TopListRequest) ([]KeyValue, error)
  20. ValidateLogPath(logPath string) error
  21. ValidateTimeRange(startTime, endTime int64) error
  22. }
  23. // service implements the Service interface
  24. type service struct {
  25. searcher searcher.Searcher
  26. cardinalityCounter *searcher.CardinalityCounter
  27. }
  28. // NewService creates a new analytics service
  29. func NewService(s searcher.Searcher) Service {
  30. // Try to extract shards from distributed searcher for cardinality counting
  31. var cardinalityCounter *searcher.CardinalityCounter
  32. if ds, ok := s.(*searcher.DistributedSearcher); ok {
  33. shards := ds.GetShards()
  34. if len(shards) > 0 {
  35. cardinalityCounter = searcher.NewCardinalityCounter(shards)
  36. }
  37. }
  38. return &service{
  39. searcher: s,
  40. cardinalityCounter: cardinalityCounter,
  41. }
  42. }
  43. // getCardinalityCounter dynamically creates or returns a cardinality counter
  44. // This is necessary because shards may be updated after service initialization
  45. func (s *service) getCardinalityCounter() *searcher.CardinalityCounter {
  46. // If we already have a cardinality counter and it's still valid, use it
  47. if s.cardinalityCounter != nil {
  48. return s.cardinalityCounter
  49. }
  50. // Try to create a new cardinality counter from current shards
  51. if ds, ok := s.searcher.(*searcher.DistributedSearcher); ok {
  52. shards := ds.GetShards()
  53. if len(shards) > 0 {
  54. // Update our cached cardinality counter
  55. s.cardinalityCounter = searcher.NewCardinalityCounter(shards)
  56. return s.cardinalityCounter
  57. }
  58. }
  59. return nil
  60. }
  61. // ValidateLogPath validates the log path against whitelist
  62. func (s *service) ValidateLogPath(logPath string) error {
  63. if logPath == "" {
  64. return nil // Empty path is acceptable for global search
  65. }
  66. if !utils.IsValidLogPath(logPath) {
  67. return fmt.Errorf("log path is not under whitelist")
  68. }
  69. return nil
  70. }
  71. // ValidateTimeRange validates the time range parameters
  72. func (s *service) ValidateTimeRange(startTime, endTime int64) error {
  73. if startTime < 0 || endTime < 0 {
  74. return fmt.Errorf("time values cannot be negative")
  75. }
  76. if startTime > 0 && endTime > 0 && startTime >= endTime {
  77. return fmt.Errorf("start time must be before end time")
  78. }
  79. return nil
  80. }
  81. // buildBaseSearchRequest builds a base search request with common parameters
  82. func (s *service) buildBaseSearchRequest(startTime, endTime int64, logPath string) *searcher.SearchRequest {
  83. req := &searcher.SearchRequest{
  84. Limit: DefaultLimit,
  85. Offset: 0,
  86. UseCache: true,
  87. }
  88. if startTime > 0 {
  89. req.StartTime = &startTime
  90. }
  91. if endTime > 0 {
  92. req.EndTime = &endTime
  93. }
  94. if logPath != "" {
  95. req.LogPaths = []string{logPath}
  96. }
  97. return req
  98. }
  99. // validateAndNormalizeSearchRequest validates and normalizes a search request
  100. func (s *service) validateAndNormalizeSearchRequest(req *searcher.SearchRequest) error {
  101. if req == nil {
  102. return fmt.Errorf("request cannot be nil")
  103. }
  104. if req.Limit <= 0 {
  105. req.Limit = DefaultLimit
  106. }
  107. if req.Limit > MaxLimit {
  108. req.Limit = MaxLimit
  109. }
  110. if req.Offset < 0 {
  111. req.Offset = 0
  112. }
  113. return nil
  114. }