service.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  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/utlis"
  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. // ValidateLogPath validates the log path against whitelist
  44. func (s *service) ValidateLogPath(logPath string) error {
  45. if logPath == "" {
  46. return nil // Empty path is acceptable for global search
  47. }
  48. if !utlis.IsValidLogPath(logPath) {
  49. return fmt.Errorf("log path is not under whitelist")
  50. }
  51. return nil
  52. }
  53. // ValidateTimeRange validates the time range parameters
  54. func (s *service) ValidateTimeRange(startTime, endTime int64) error {
  55. if startTime < 0 || endTime < 0 {
  56. return fmt.Errorf("time values cannot be negative")
  57. }
  58. if startTime > 0 && endTime > 0 && startTime >= endTime {
  59. return fmt.Errorf("start time must be before end time")
  60. }
  61. return nil
  62. }
  63. // buildBaseSearchRequest builds a base search request with common parameters
  64. func (s *service) buildBaseSearchRequest(startTime, endTime int64, logPath string) *searcher.SearchRequest {
  65. req := &searcher.SearchRequest{
  66. Limit: DefaultLimit,
  67. Offset: 0,
  68. UseCache: true,
  69. }
  70. if startTime > 0 {
  71. req.StartTime = &startTime
  72. }
  73. if endTime > 0 {
  74. req.EndTime = &endTime
  75. }
  76. if logPath != "" {
  77. req.LogPaths = []string{logPath}
  78. }
  79. return req
  80. }
  81. // validateAndNormalizeSearchRequest validates and normalizes a search request
  82. func (s *service) validateAndNormalizeSearchRequest(req *searcher.SearchRequest) error {
  83. if req == nil {
  84. return fmt.Errorf("request cannot be nil")
  85. }
  86. if req.Limit <= 0 {
  87. req.Limit = DefaultLimit
  88. }
  89. if req.Limit > MaxLimit {
  90. req.Limit = MaxLimit
  91. }
  92. if req.Offset < 0 {
  93. req.Offset = 0
  94. }
  95. return nil
  96. }