| |
| |
| package logging |
|
|
| import ( |
| "os" |
| "path/filepath" |
| "sync/atomic" |
| "time" |
|
|
| "github.com/router-for-me/CLIProxyAPI/v6/internal/domain/ports" |
| ) |
|
|
| |
| type MetricsEnabledFileStreamingLogWriter struct { |
| *FileStreamingLogWriter |
| metrics ports.MetricsService |
| dropCount atomic.Uint64 |
| startTime time.Time |
| bytesWritten atomic.Int64 |
| } |
|
|
| |
| func NewMetricsEnabledFileStreamingLogWriter( |
| base *FileStreamingLogWriter, |
| metrics ports.MetricsService, |
| ) *MetricsEnabledFileStreamingLogWriter { |
| if metrics == nil { |
| metrics = &ports.NoOpMetricsService{} |
| } |
|
|
| writer := &MetricsEnabledFileStreamingLogWriter{ |
| FileStreamingLogWriter: base, |
| metrics: metrics, |
| startTime: time.Now(), |
| } |
|
|
| |
| if base != nil && base.chunkChan != nil { |
| metrics.RecordLogQueueDepth(len(base.chunkChan)) |
| } |
|
|
| return writer |
| } |
|
|
| |
| func (w *MetricsEnabledFileStreamingLogWriter) WriteChunkAsync(chunk []byte) { |
| if w.FileStreamingLogWriter == nil { |
| return |
| } |
|
|
| |
| if w.chunkChan != nil { |
| w.metrics.RecordLogQueueDepth(len(w.chunkChan)) |
| } |
|
|
| |
| select { |
| case w.chunkChan <- chunk: |
| |
| w.bytesWritten.Add(int64(len(chunk))) |
| default: |
| |
| w.dropCount.Add(1) |
| w.metrics.RecordLogDropCount(1) |
| } |
|
|
| |
| if w.chunkChan != nil { |
| w.metrics.RecordLogQueueDepth(len(w.chunkChan)) |
| } |
| } |
|
|
| |
| func (w *MetricsEnabledFileStreamingLogWriter) WriteStatus(status int, headers map[string][]string) error { |
| if w.FileStreamingLogWriter == nil { |
| return nil |
| } |
|
|
| return w.FileStreamingLogWriter.WriteStatus(status, headers) |
| } |
|
|
| |
| func (w *MetricsEnabledFileStreamingLogWriter) WriteAPIRequest(apiRequest []byte) error { |
| if w.FileStreamingLogWriter == nil { |
| return nil |
| } |
|
|
| if len(apiRequest) > 0 { |
| w.bytesWritten.Add(int64(len(apiRequest))) |
| } |
|
|
| return w.FileStreamingLogWriter.WriteAPIRequest(apiRequest) |
| } |
|
|
| |
| func (w *MetricsEnabledFileStreamingLogWriter) WriteAPIResponse(apiResponse []byte) error { |
| if w.FileStreamingLogWriter == nil { |
| return nil |
| } |
|
|
| if len(apiResponse) > 0 { |
| w.bytesWritten.Add(int64(len(apiResponse))) |
| } |
|
|
| return w.FileStreamingLogWriter.WriteAPIResponse(apiResponse) |
| } |
|
|
| |
| func (w *MetricsEnabledFileStreamingLogWriter) Close() error { |
| if w.FileStreamingLogWriter == nil { |
| return nil |
| } |
|
|
| |
| duration := time.Since(w.startTime) |
| w.metrics.RecordLogProcessingLatency(duration) |
|
|
| |
| w.metrics.RecordLogWrite(w.bytesWritten.Load()) |
|
|
| |
| drops := w.dropCount.Load() |
| if drops > 0 { |
| w.metrics.RecordLogDropCount(drops) |
| } |
|
|
| |
| if w.chunkChan != nil { |
| w.metrics.RecordLogQueueDepth(len(w.chunkChan)) |
| } |
|
|
| return w.FileStreamingLogWriter.Close() |
| } |
|
|
| |
| func (w *MetricsEnabledFileStreamingLogWriter) GetDropCount() uint64 { |
| return w.dropCount.Load() |
| } |
|
|
| |
| func (w *MetricsEnabledFileStreamingLogWriter) GetBytesWritten() int64 { |
| return w.bytesWritten.Load() |
| } |
|
|
| |
| func (w *MetricsEnabledFileStreamingLogWriter) GetProcessingDuration() time.Duration { |
| return time.Since(w.startTime) |
| } |
|
|
| |
| type FileRequestLoggerWithMetrics struct { |
| *FileRequestLogger |
| metrics ports.MetricsService |
| } |
|
|
| |
| func NewFileRequestLoggerWithMetrics( |
| base *FileRequestLogger, |
| metrics ports.MetricsService, |
| ) *FileRequestLoggerWithMetrics { |
| if metrics == nil { |
| metrics = &ports.NoOpMetricsService{} |
| } |
|
|
| return &FileRequestLoggerWithMetrics{ |
| FileRequestLogger: base, |
| metrics: metrics, |
| } |
| } |
|
|
| |
| func (l *FileRequestLoggerWithMetrics) LogStreamingRequest( |
| url, method string, |
| headers map[string][]string, |
| body []byte, |
| requestID string, |
| ) (StreamingLogWriter, error) { |
| if !l.enabled { |
| return &NoOpStreamingLogWriter{}, nil |
| } |
|
|
| |
| baseWriter, err := l.createBaseStreamingWriter(url, method, headers, body, requestID) |
| if err != nil { |
| l.metrics.RecordLogError(err) |
| return nil, err |
| } |
|
|
| |
| return NewMetricsEnabledFileStreamingLogWriter(baseWriter, l.metrics), nil |
| } |
|
|
| |
| func (l *FileRequestLoggerWithMetrics) createBaseStreamingWriter( |
| url, method string, |
| headers map[string][]string, |
| body []byte, |
| requestID string, |
| ) (*FileStreamingLogWriter, error) { |
| |
| if err := l.ensureLogsDir(); err != nil { |
| return nil, err |
| } |
|
|
| filename := l.generateFilename(url, requestID) |
| filePath := filepath.Join(l.logsDir, filename) |
|
|
| requestHeaders := make(map[string][]string, len(headers)) |
| for key, values := range headers { |
| headerValues := make([]string, len(values)) |
| copy(headerValues, values) |
| requestHeaders[key] = headerValues |
| } |
|
|
| requestBodyPath, errTemp := l.writeRequestBodyTempFile(body) |
| if errTemp != nil { |
| return nil, errTemp |
| } |
|
|
| responseBodyFile, errCreate := os.CreateTemp(l.logsDir, "response-body-*.tmp") |
| if errCreate != nil { |
| _ = os.Remove(requestBodyPath) |
| return nil, errCreate |
| } |
| responseBodyPath := responseBodyFile.Name() |
|
|
| writer := &FileStreamingLogWriter{ |
| logFilePath: filePath, |
| url: url, |
| method: method, |
| timestamp: time.Now(), |
| requestHeaders: requestHeaders, |
| requestBodyPath: requestBodyPath, |
| responseBodyPath: responseBodyPath, |
| responseBodyFile: responseBodyFile, |
| chunkChan: make(chan []byte, 100), |
| closeChan: make(chan struct{}), |
| errorChan: make(chan error, 1), |
| } |
|
|
| go writer.asyncWriter() |
|
|
| return writer, nil |
| } |
|
|
| |
| func (l *FileRequestLoggerWithMetrics) GetMetrics() *ports.LogMetrics { |
| if l.metrics != nil { |
| return l.metrics.GetMetrics() |
| } |
| return &ports.LogMetrics{} |
| } |
|
|
| |
| type MetricsCollectorAdapter struct { |
| metrics ports.MetricsService |
| } |
|
|
| |
| func NewMetricsCollectorAdapter(metrics ports.MetricsService) *MetricsCollectorAdapter { |
| if metrics == nil { |
| metrics = &ports.NoOpMetricsService{} |
| } |
| return &MetricsCollectorAdapter{metrics: metrics} |
| } |
|
|
| |
| func (a *MetricsCollectorAdapter) OnQueueDepthChanged(depth int) { |
| a.metrics.RecordLogQueueDepth(depth) |
| } |
|
|
| |
| func (a *MetricsCollectorAdapter) OnProcessingCompleted(duration time.Duration, bytesProcessed int64, err error) { |
| a.metrics.RecordLogProcessingLatency(duration) |
| a.metrics.RecordLogWrite(bytesProcessed) |
| if err != nil { |
| a.metrics.RecordLogError(err) |
| } |
| } |
|
|
| |
| func (a *MetricsCollectorAdapter) OnItemsDropped(count uint64) { |
| a.metrics.RecordLogDropCount(count) |
| } |
|
|
| |
| var _ ports.MetricsCollector = (*MetricsCollectorAdapter)(nil) |
|
|