analytics_service_core.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. package nginx_log
  2. import (
  3. "context"
  4. "fmt"
  5. "time"
  6. "github.com/uozi-tech/cosy/logger"
  7. )
  8. // AnalyticsService provides log analytics functionality
  9. type AnalyticsService struct {
  10. indexer *LogIndexer
  11. parser *OptimizedLogParser
  12. }
  13. // NewAnalyticsService creates a new analytics service
  14. func NewAnalyticsService() *AnalyticsService {
  15. // Create user agent parser
  16. userAgent := NewSimpleUserAgentParser()
  17. parser := NewOptimizedLogParser(userAgent)
  18. return &AnalyticsService{
  19. parser: parser,
  20. }
  21. }
  22. // SetIndexer sets the log indexer for the service
  23. func (s *AnalyticsService) SetIndexer(indexer *LogIndexer) {
  24. s.indexer = indexer
  25. }
  26. // GetIndexer returns the log indexer
  27. func (s *AnalyticsService) GetIndexer() *LogIndexer {
  28. return s.indexer
  29. }
  30. // AnalyzeLogFile analyzes a log file and returns statistics
  31. func (s *AnalyticsService) AnalyzeLogFile(logPath string) (*LogAnalytics, error) {
  32. if !IsLogPathUnderWhiteList(logPath) {
  33. return nil, fmt.Errorf("log path is not under whitelist")
  34. }
  35. // Return empty analytics as file parsing is handled by indexer
  36. return &LogAnalytics{
  37. TotalRequests: 0,
  38. StatusCodes: make(map[int]int),
  39. Countries: make(map[string]int),
  40. Browsers: make(map[string]int),
  41. OperatingSystems: make(map[string]int),
  42. UniqueIPs: make(map[string]int),
  43. Devices: make(map[string]int),
  44. }, nil
  45. }
  46. // SearchLogs searches for log entries using the indexer
  47. func (s *AnalyticsService) SearchLogs(ctx context.Context, req *QueryRequest) (*QueryResult, error) {
  48. logger.Infof("AnalyticsService: Searching logs with query: %+v", req)
  49. if err := s.validateAndNormalizeSearchRequest(req); err != nil {
  50. return nil, fmt.Errorf("invalid search request: %w", err)
  51. }
  52. // Delegate to indexer for actual search
  53. if s.indexer != nil {
  54. return s.indexer.SearchLogs(ctx, req)
  55. }
  56. return nil, fmt.Errorf("indexer not available")
  57. }
  58. // validateAndNormalizeSearchRequest validates and normalizes the search request
  59. func (s *AnalyticsService) validateAndNormalizeSearchRequest(req *QueryRequest) error {
  60. if req == nil {
  61. return fmt.Errorf("request is nil")
  62. }
  63. // Validate log path
  64. if req.LogPath != "" {
  65. if err := s.ValidateLogPath(req.LogPath); err != nil {
  66. return fmt.Errorf("invalid log path: %w", err)
  67. }
  68. }
  69. // Ensure positive limit with reasonable limits
  70. if req.Limit <= 0 {
  71. req.Limit = 50 // Default limit
  72. } else if req.Limit > 1000 {
  73. req.Limit = 1000 // Max limit
  74. }
  75. // Ensure non-negative offset
  76. if req.Offset < 0 {
  77. req.Offset = 0
  78. }
  79. // Validate time range
  80. if req.StartTime != 0 && req.EndTime != 0 && req.StartTime > req.EndTime {
  81. return fmt.Errorf("start time cannot be after end time")
  82. }
  83. return nil
  84. }
  85. // ValidateLogPath validates that a log path is allowed
  86. func (s *AnalyticsService) ValidateLogPath(logPath string) error {
  87. if logPath == "" {
  88. return nil // Empty path is allowed for searching all logs
  89. }
  90. // Check whitelist
  91. if !IsLogPathUnderWhiteList(logPath) {
  92. return fmt.Errorf("log path %s is not under whitelist", logPath)
  93. }
  94. // Additional validation can be added here
  95. if !isValidLogPath(logPath) {
  96. return fmt.Errorf("invalid log path format: %s", logPath)
  97. }
  98. return nil
  99. }
  100. // GetTimeRange returns the overall time range of all indexed logs
  101. func (s *AnalyticsService) GetTimeRange() (start, end time.Time) {
  102. if s.indexer != nil {
  103. return s.indexer.GetTimeRange()
  104. }
  105. return time.Time{}, time.Time{}
  106. }
  107. // GetTimeRangeForPath returns the time range for a specific log path
  108. func (s *AnalyticsService) GetTimeRangeForPath(logPath string) (start, end time.Time) {
  109. if s.indexer != nil {
  110. return s.indexer.GetTimeRangeForPath(logPath)
  111. }
  112. return time.Time{}, time.Time{}
  113. }
  114. // GetTimeRangeFromSummaryStatsForPath returns the time range from summary stats for a specific log path
  115. func (s *AnalyticsService) GetTimeRangeFromSummaryStatsForPath(logPath string) (start, end time.Time) {
  116. if s.indexer != nil {
  117. return s.indexer.GetTimeRangeFromSummaryStatsForPath(logPath)
  118. }
  119. return time.Time{}, time.Time{}
  120. }
  121. // Global analytics service instance
  122. var analyticsService *AnalyticsService
  123. // InitAnalyticsService initializes the global analytics service
  124. func InitAnalyticsService() {
  125. analyticsService = NewAnalyticsService()
  126. logger.Info("Analytics service initialized")
  127. }
  128. // GetAnalyticsService returns the global analytics service instance
  129. func GetAnalyticsService() *AnalyticsService {
  130. return analyticsService
  131. }
  132. // SetAnalyticsServiceIndexer sets the indexer for the global analytics service
  133. func SetAnalyticsServiceIndexer(indexer *LogIndexer) {
  134. if analyticsService != nil {
  135. analyticsService.SetIndexer(indexer)
  136. logger.Info("Analytics service indexer set")
  137. }
  138. }