| |
| package logging |
|
|
| import ( |
| "context" |
| "fmt" |
| "os" |
| "path/filepath" |
| "sync" |
| "time" |
|
|
| "github.com/router-for-me/CLIProxyAPI/v6/internal/config" |
| "github.com/router-for-me/CLIProxyAPI/v6/internal/logging" |
| "github.com/sirupsen/logrus" |
| "gopkg.in/natefinch/lumberjack.v2" |
| ) |
|
|
| |
| type contextKey string |
|
|
| const ( |
| |
| CorrelationIDKey contextKey = "request_id" |
| |
| ServiceKey contextKey = "service_name" |
| |
| OperationKey contextKey = "operation_name" |
| ) |
|
|
| |
| type StructuredLogger struct { |
| logger *logrus.Logger |
| mu sync.RWMutex |
| logWriter *lumberjack.Logger |
| } |
|
|
| |
| type LogEntry struct { |
| Timestamp time.Time `json:"timestamp"` |
| Level string `json:"level"` |
| Message string `json:"message"` |
| CorrelationID string `json:"correlation_id,omitempty"` |
| Service string `json:"service,omitempty"` |
| Operation string `json:"operation,omitempty"` |
| Fields map[string]interface{} `json:"fields,omitempty"` |
| } |
|
|
| |
| func NewStructuredLogger() *StructuredLogger { |
| logger := logrus.New() |
| logger.SetFormatter(&logrus.JSONFormatter{ |
| TimestampFormat: time.RFC3339Nano, |
| }) |
| logger.SetOutput(os.Stdout) |
| logger.SetLevel(logrus.InfoLevel) |
|
|
| return &StructuredLogger{ |
| logger: logger, |
| } |
| } |
|
|
| |
| func (l *StructuredLogger) Configure(cfg *config.Config) error { |
| l.mu.Lock() |
| defer l.mu.Unlock() |
|
|
| |
| if cfg.Debug { |
| l.logger.SetLevel(logrus.DebugLevel) |
| } else { |
| l.logger.SetLevel(logrus.InfoLevel) |
| } |
|
|
| |
| if cfg.LoggingToFile { |
| logDir := l.resolveLogDirectory(cfg) |
| if err := os.MkdirAll(logDir, 0755); err != nil { |
| return fmt.Errorf("failed to create log directory: %w", err) |
| } |
|
|
| logPath := filepath.Join(logDir, "main.log") |
|
|
| if l.logWriter != nil { |
| _ = l.logWriter.Close() |
| } |
|
|
| l.logWriter = &lumberjack.Logger{ |
| Filename: logPath, |
| MaxSize: 10, |
| MaxBackups: 0, |
| MaxAge: 0, |
| Compress: false, |
| } |
|
|
| l.logger.SetOutput(l.logWriter) |
| } else { |
| if l.logWriter != nil { |
| _ = l.logWriter.Close() |
| l.logWriter = nil |
| } |
| l.logger.SetOutput(os.Stdout) |
| } |
|
|
| return nil |
| } |
|
|
| |
| func (l *StructuredLogger) resolveLogDirectory(cfg *config.Config) string { |
| if cfg.AuthDir != "" { |
| return filepath.Join(cfg.AuthDir, "logs") |
| } |
| return "logs" |
| } |
|
|
| |
| func (l *StructuredLogger) Close() error { |
| l.mu.Lock() |
| defer l.mu.Unlock() |
|
|
| if l.logWriter != nil { |
| return l.logWriter.Close() |
| } |
| return nil |
| } |
|
|
| |
| func (l *StructuredLogger) WithContext(ctx context.Context) *logrus.Entry { |
| l.mu.RLock() |
| defer l.mu.RUnlock() |
|
|
| entry := l.logger.WithContext(ctx) |
|
|
| |
| if correlationID := GetCorrelationID(ctx); correlationID != "" { |
| entry = entry.WithField("correlation_id", correlationID) |
| } |
|
|
| |
| if service := GetService(ctx); service != "" { |
| entry = entry.WithField("service", service) |
| } |
|
|
| |
| if operation := GetOperation(ctx); operation != "" { |
| entry = entry.WithField("operation", operation) |
| } |
|
|
| return entry |
| } |
|
|
| |
| func (l *StructuredLogger) WithField(key string, value interface{}) *logrus.Entry { |
| l.mu.RLock() |
| defer l.mu.RUnlock() |
| return l.logger.WithField(key, value) |
| } |
|
|
| |
| func (l *StructuredLogger) WithFields(fields map[string]interface{}) *logrus.Entry { |
| l.mu.RLock() |
| defer l.mu.RUnlock() |
| return l.logger.WithFields(fields) |
| } |
|
|
| |
| func (l *StructuredLogger) WithError(err error) *logrus.Entry { |
| l.mu.RLock() |
| defer l.mu.RUnlock() |
| return l.logger.WithError(err) |
| } |
|
|
| |
| func (l *StructuredLogger) Debug(ctx context.Context, message string) { |
| l.WithContext(ctx).Debug(message) |
| } |
|
|
| |
| func (l *StructuredLogger) Info(ctx context.Context, message string) { |
| l.WithContext(ctx).Info(message) |
| } |
|
|
| |
| func (l *StructuredLogger) Warn(ctx context.Context, message string) { |
| l.WithContext(ctx).Warn(message) |
| } |
|
|
| |
| func (l *StructuredLogger) Error(ctx context.Context, message string, err error) { |
| entry := l.WithContext(ctx) |
| if err != nil { |
| entry = entry.WithError(err) |
| } |
| entry.Error(message) |
| } |
|
|
| |
| func (l *StructuredLogger) Fatal(ctx context.Context, message string, err error) { |
| entry := l.WithContext(ctx) |
| if err != nil { |
| entry = entry.WithError(err) |
| } |
| entry.Fatal(message) |
| } |
|
|
| |
| func (l *StructuredLogger) Debugf(ctx context.Context, format string, args ...interface{}) { |
| l.WithContext(ctx).Debugf(format, args...) |
| } |
|
|
| |
| func (l *StructuredLogger) Infof(ctx context.Context, format string, args ...interface{}) { |
| l.WithContext(ctx).Infof(format, args...) |
| } |
|
|
| |
| func (l *StructuredLogger) Warnf(ctx context.Context, format string, args ...interface{}) { |
| l.WithContext(ctx).Warnf(format, args...) |
| } |
|
|
| |
| func (l *StructuredLogger) Errorf(ctx context.Context, format string, args ...interface{}) { |
| l.WithContext(ctx).Errorf(format, args...) |
| } |
|
|
| |
| func WithCorrelationID(ctx context.Context, correlationID string) context.Context { |
| return context.WithValue(ctx, CorrelationIDKey, correlationID) |
| } |
|
|
| |
| func GetCorrelationID(ctx context.Context) string { |
| if ctx == nil { |
| return "" |
| } |
| |
| if id, ok := ctx.Value("request_id").(string); ok { |
| return id |
| } |
| |
| if id, ok := ctx.Value(CorrelationIDKey).(string); ok { |
| return id |
| } |
| return "" |
| } |
|
|
| |
| func WithService(ctx context.Context, service string) context.Context { |
| return context.WithValue(ctx, ServiceKey, service) |
| } |
|
|
| |
| func GetService(ctx context.Context) string { |
| if ctx == nil { |
| return "" |
| } |
| if service, ok := ctx.Value(ServiceKey).(string); ok { |
| return service |
| } |
| return "" |
| } |
|
|
| |
| func WithOperation(ctx context.Context, operation string) context.Context { |
| return context.WithValue(ctx, OperationKey, operation) |
| } |
|
|
| |
| func GetOperation(ctx context.Context) string { |
| if ctx == nil { |
| return "" |
| } |
| if operation, ok := ctx.Value(OperationKey).(string); ok { |
| return operation |
| } |
| return "" |
| } |
|
|
| |
| func GenerateCorrelationID() string { |
| return logging.GenerateRequestID() |
| } |
|
|
| |
| var ( |
| globalLogger *StructuredLogger |
| loggerOnce sync.Once |
| ) |
|
|
| |
| func GetLogger() *StructuredLogger { |
| loggerOnce.Do(func() { |
| globalLogger = NewStructuredLogger() |
| }) |
| return globalLogger |
| } |
|
|
| |
| func SetLogger(logger *StructuredLogger) { |
| globalLogger = logger |
| } |
|
|