File size: 7,800 Bytes
bf9e111
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
// Package logging provides structured logging infrastructure with correlation ID support.
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"
)

// contextKey is used for storing values in context
type contextKey string

const (
	// CorrelationIDKey is the context key for correlation IDs
	CorrelationIDKey contextKey = "correlation_id"
	// ServiceKey is the context key for service name
	ServiceKey contextKey = "service_name"
	// OperationKey is the context key for operation name
	OperationKey contextKey = "operation_name"
)

// StructuredLogger provides structured logging with correlation ID support
type StructuredLogger struct {
	logger    *logrus.Logger
	mu        sync.RWMutex
	logWriter *lumberjack.Logger
}

// LogEntry represents a structured log entry
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"`
}

// NewStructuredLogger creates a new structured logger
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,
	}
}

// Configure configures the logger with the given configuration
func (l *StructuredLogger) Configure(cfg *config.Config) error {
	l.mu.Lock()
	defer l.mu.Unlock()

	// Set log level
	if cfg.Debug {
		l.logger.SetLevel(logrus.DebugLevel)
	} else {
		l.logger.SetLevel(logrus.InfoLevel)
	}

	// Configure file logging if enabled
	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, // MB
			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
}

// resolveLogDirectory determines the log directory from configuration
func (l *StructuredLogger) resolveLogDirectory(cfg *config.Config) string {
	if cfg.AuthDir != "" {
		return filepath.Join(cfg.AuthDir, "logs")
	}
	return "logs"
}

// Close closes the logger and releases resources
func (l *StructuredLogger) Close() error {
	l.mu.Lock()
	defer l.mu.Unlock()

	if l.logWriter != nil {
		return l.logWriter.Close()
	}
	return nil
}

// WithContext creates a logger entry with context values
func (l *StructuredLogger) WithContext(ctx context.Context) *logrus.Entry {
	l.mu.RLock()
	defer l.mu.RUnlock()

	entry := l.logger.WithContext(ctx)

	// Add correlation ID if present
	if correlationID := GetCorrelationID(ctx); correlationID != "" {
		entry = entry.WithField("correlation_id", correlationID)
	}

	// Add service name if present
	if service := GetService(ctx); service != "" {
		entry = entry.WithField("service", service)
	}

	// Add operation name if present
	if operation := GetOperation(ctx); operation != "" {
		entry = entry.WithField("operation", operation)
	}

	return entry
}

// WithField adds a field to the logger
func (l *StructuredLogger) WithField(key string, value interface{}) *logrus.Entry {
	l.mu.RLock()
	defer l.mu.RUnlock()
	return l.logger.WithField(key, value)
}

// WithFields adds multiple fields to the logger
func (l *StructuredLogger) WithFields(fields map[string]interface{}) *logrus.Entry {
	l.mu.RLock()
	defer l.mu.RUnlock()
	return l.logger.WithFields(fields)
}

// WithError adds an error to the logger
func (l *StructuredLogger) WithError(err error) *logrus.Entry {
	l.mu.RLock()
	defer l.mu.RUnlock()
	return l.logger.WithError(err)
}

// Debug logs a debug message
func (l *StructuredLogger) Debug(ctx context.Context, message string) {
	l.WithContext(ctx).Debug(message)
}

// Info logs an info message
func (l *StructuredLogger) Info(ctx context.Context, message string) {
	l.WithContext(ctx).Info(message)
}

// Warn logs a warning message
func (l *StructuredLogger) Warn(ctx context.Context, message string) {
	l.WithContext(ctx).Warn(message)
}

// Error logs an error 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)
}

// Fatal logs a fatal 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)
}

// Debugf logs a formatted debug message
func (l *StructuredLogger) Debugf(ctx context.Context, format string, args ...interface{}) {
	l.WithContext(ctx).Debugf(format, args...)
}

// Infof logs a formatted info message
func (l *StructuredLogger) Infof(ctx context.Context, format string, args ...interface{}) {
	l.WithContext(ctx).Infof(format, args...)
}

// Warnf logs a formatted warning message
func (l *StructuredLogger) Warnf(ctx context.Context, format string, args ...interface{}) {
	l.WithContext(ctx).Warnf(format, args...)
}

// Errorf logs a formatted error message
func (l *StructuredLogger) Errorf(ctx context.Context, format string, args ...interface{}) {
	l.WithContext(ctx).Errorf(format, args...)
}

// WithCorrelationID adds a correlation ID to the context
func WithCorrelationID(ctx context.Context, correlationID string) context.Context {
	return context.WithValue(ctx, CorrelationIDKey, correlationID)
}

// GetCorrelationID retrieves the correlation ID from the context
func GetCorrelationID(ctx context.Context) string {
	if ctx == nil {
		return ""
	}
	if id, ok := ctx.Value(CorrelationIDKey).(string); ok {
		return id
	}
	return ""
}

// WithService adds a service name to the context
func WithService(ctx context.Context, service string) context.Context {
	return context.WithValue(ctx, ServiceKey, service)
}

// GetService retrieves the service name from the context
func GetService(ctx context.Context) string {
	if ctx == nil {
		return ""
	}
	if service, ok := ctx.Value(ServiceKey).(string); ok {
		return service
	}
	return ""
}

// WithOperation adds an operation name to the context
func WithOperation(ctx context.Context, operation string) context.Context {
	return context.WithValue(ctx, OperationKey, operation)
}

// GetOperation retrieves the operation name from the context
func GetOperation(ctx context.Context) string {
	if ctx == nil {
		return ""
	}
	if operation, ok := ctx.Value(OperationKey).(string); ok {
		return operation
	}
	return ""
}

// GenerateCorrelationID generates a new correlation ID
func GenerateCorrelationID() string {
	return logging.GenerateRequestID()
}

// Logger is the global structured logger instance
var (
	globalLogger *StructuredLogger
	loggerOnce   sync.Once
)

// GetLogger returns the global structured logger instance
func GetLogger() *StructuredLogger {
	loggerOnce.Do(func() {
		globalLogger = NewStructuredLogger()
	})
	return globalLogger
}

// SetLogger sets the global structured logger instance
func SetLogger(logger *StructuredLogger) {
	globalLogger = logger
}