123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153 |
- package analytics
- import (
- "context"
- "fmt"
- "github.com/0xJacky/Nginx-UI/internal/nginx_log/searcher"
- "github.com/0xJacky/Nginx-UI/internal/nginx_log/utils"
- )
- // Service defines the interface for analytics operations
- type Service interface {
- GetDashboardAnalytics(ctx context.Context, req *DashboardQueryRequest) (*DashboardAnalytics, error)
- GetLogEntriesStats(ctx context.Context, req *searcher.SearchRequest) (*EntriesStats, error)
- GetGeoDistribution(ctx context.Context, req *GeoQueryRequest) (*GeoDistribution, error)
- GetGeoDistributionByCountry(ctx context.Context, req *GeoQueryRequest, countryCode string) (*GeoDistribution, error)
- GetTopCountries(ctx context.Context, req *GeoQueryRequest) ([]CountryStats, error)
- GetTopCities(ctx context.Context, req *GeoQueryRequest) ([]CityStats, error)
- GetGeoStatsForIP(ctx context.Context, req *GeoQueryRequest, ip string) (*CityStats, error)
- GetTopPaths(ctx context.Context, req *TopListRequest) ([]KeyValue, error)
- GetTopIPs(ctx context.Context, req *TopListRequest) ([]KeyValue, error)
- GetTopUserAgents(ctx context.Context, req *TopListRequest) ([]KeyValue, error)
- ValidateLogPath(logPath string) error
- ValidateTimeRange(startTime, endTime int64) error
- Stop() error
- }
- // service implements the Service interface
- type service struct {
- searcher searcher.Searcher
- cardinalityCounter *searcher.CardinalityCounter
- }
- // NewService creates a new analytics service
- func NewService(s searcher.Searcher) Service {
- // Try to extract shards from distributed searcher for cardinality counting
- var cardinalityCounter *searcher.CardinalityCounter
- if ds, ok := s.(*searcher.DistributedSearcher); ok {
- shards := ds.GetShards()
- if len(shards) > 0 {
- cardinalityCounter = searcher.NewCardinalityCounter(shards)
- }
- }
- return &service{
- searcher: s,
- cardinalityCounter: cardinalityCounter,
- }
- }
- // Stop gracefully stops the analytics service and its components
- func (s *service) Stop() error {
- if s.cardinalityCounter != nil {
- return s.cardinalityCounter.Stop()
- }
- return nil
- }
- // getCardinalityCounter dynamically creates or returns a cardinality counter
- // This is necessary because shards may be updated after service initialization
- func (s *service) getCardinalityCounter() *searcher.CardinalityCounter {
- // If we already have a cardinality counter and it's still valid, use it
- if s.cardinalityCounter != nil {
- return s.cardinalityCounter
- }
- // Try to create a new cardinality counter from current shards
- if ds, ok := s.searcher.(*searcher.DistributedSearcher); ok {
- shards := ds.GetShards()
- if len(shards) > 0 {
- // Update our cached cardinality counter
- s.cardinalityCounter = searcher.NewCardinalityCounter(shards)
- return s.cardinalityCounter
- }
- }
- return nil
- }
- // ValidateLogPath validates the log path against whitelist
- func (s *service) ValidateLogPath(logPath string) error {
- if logPath == "" {
- return nil // Empty path is acceptable for global search
- }
- if !utils.IsValidLogPath(logPath) {
- return fmt.Errorf("log path is not under whitelist")
- }
- return nil
- }
- // ValidateTimeRange validates the time range parameters
- func (s *service) ValidateTimeRange(startTime, endTime int64) error {
- if startTime < 0 || endTime < 0 {
- return fmt.Errorf("time values cannot be negative")
- }
- if startTime > 0 && endTime > 0 && startTime >= endTime {
- return fmt.Errorf("start time must be before end time")
- }
- return nil
- }
- // buildBaseSearchRequest builds a base search request with common parameters
- func (s *service) buildBaseSearchRequest(startTime, endTime int64, logPath string) *searcher.SearchRequest {
- req := &searcher.SearchRequest{
- Limit: DefaultLimit,
- Offset: 0,
- UseCache: true,
- }
- if startTime > 0 {
- req.StartTime = &startTime
- }
- if endTime > 0 {
- req.EndTime = &endTime
- }
- if logPath != "" {
- req.LogPaths = []string{logPath}
- }
- return req
- }
- // validateAndNormalizeSearchRequest validates and normalizes a search request
- func (s *service) validateAndNormalizeSearchRequest(req *searcher.SearchRequest) error {
- if req == nil {
- return fmt.Errorf("request cannot be nil")
- }
- if req.Limit <= 0 {
- req.Limit = DefaultLimit
- }
- if req.Limit > MaxLimit {
- req.Limit = MaxLimit
- }
- if req.Offset < 0 {
- req.Offset = 0
- }
- return nil
- }
|