207 lines
7.6 KiB
Go
207 lines
7.6 KiB
Go
// Package logging provides request logging functionality for the CLI Proxy API server.
|
|
// It handles capturing and storing detailed HTTP request and response data when enabled
|
|
// through configuration, supporting both regular and streaming responses.
|
|
package logging
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces"
|
|
)
|
|
|
|
const (
|
|
WebsocketTimelineSourceContextKey = "WEBSOCKET_TIMELINE_SOURCE"
|
|
APIRequestSourceContextKey = "API_REQUEST_SOURCE"
|
|
DeferredAPIRequestContextKey = "DEFERRED_API_REQUEST"
|
|
APIResponseSourceContextKey = "API_RESPONSE_SOURCE"
|
|
APIResponseCapturedContextKey = "API_RESPONSE_CAPTURED"
|
|
APIWebsocketTimelineSourceContextKey = "API_WEBSOCKET_TIMELINE_SOURCE"
|
|
)
|
|
|
|
// DeferredAPIRequest builds an upstream request log only when an error log needs it.
|
|
type DeferredAPIRequest func() []byte
|
|
|
|
// RequestLogger defines the interface for logging HTTP requests and responses.
|
|
// It provides methods for logging both regular and streaming HTTP request/response cycles.
|
|
type RequestLogger interface {
|
|
// LogRequest logs a complete non-streaming request/response cycle.
|
|
//
|
|
// Parameters:
|
|
// - url: The request URL
|
|
// - method: The HTTP method
|
|
// - requestHeaders: The request headers
|
|
// - body: The request body
|
|
// - statusCode: The response status code
|
|
// - responseHeaders: The response headers
|
|
// - response: The raw response data
|
|
// - websocketTimeline: Optional downstream websocket event timeline
|
|
// - apiRequest: The API request data
|
|
// - apiResponse: The API response data
|
|
// - apiWebsocketTimeline: Optional upstream websocket event timeline
|
|
// - requestID: Optional request ID for log file naming
|
|
// - requestTimestamp: When the request was received
|
|
// - apiResponseTimestamp: When the API response was received
|
|
//
|
|
// Returns:
|
|
// - error: An error if logging fails, nil otherwise
|
|
LogRequest(url, method string, requestHeaders map[string][]string, body []byte, statusCode int, responseHeaders map[string][]string, response, websocketTimeline, apiRequest, apiResponse, apiWebsocketTimeline []byte, apiResponseErrors []*interfaces.ErrorMessage, requestID string, requestTimestamp, apiResponseTimestamp time.Time) error
|
|
|
|
// LogStreamingRequest initiates logging for a streaming request and returns a writer for chunks.
|
|
//
|
|
// Parameters:
|
|
// - url: The request URL
|
|
// - method: The HTTP method
|
|
// - headers: The request headers
|
|
// - body: The request body
|
|
// - requestID: Optional request ID for log file naming
|
|
//
|
|
// Returns:
|
|
// - StreamingLogWriter: A writer for streaming response chunks
|
|
// - error: An error if logging initialization fails, nil otherwise
|
|
LogStreamingRequest(url, method string, headers map[string][]string, body []byte, requestID string) (StreamingLogWriter, error)
|
|
|
|
// IsEnabled returns whether request logging is currently enabled.
|
|
//
|
|
// Returns:
|
|
// - bool: True if logging is enabled, false otherwise
|
|
IsEnabled() bool
|
|
}
|
|
|
|
// StreamingLogWriter handles real-time logging of streaming response chunks.
|
|
// It provides methods for writing streaming response data asynchronously.
|
|
type StreamingLogWriter interface {
|
|
// WriteChunkAsync writes a response chunk asynchronously (non-blocking).
|
|
//
|
|
// Parameters:
|
|
// - chunk: The response chunk to write
|
|
WriteChunkAsync(chunk []byte)
|
|
|
|
// WriteStatus writes the response status and headers to the log.
|
|
//
|
|
// Parameters:
|
|
// - status: The response status code
|
|
// - headers: The response headers
|
|
//
|
|
// Returns:
|
|
// - error: An error if writing fails, nil otherwise
|
|
WriteStatus(status int, headers map[string][]string) error
|
|
|
|
// WriteAPIRequest writes the upstream API request details to the log.
|
|
// This should be called before WriteStatus to maintain proper log ordering.
|
|
//
|
|
// Parameters:
|
|
// - apiRequest: The API request data (typically includes URL, headers, body sent upstream)
|
|
//
|
|
// Returns:
|
|
// - error: An error if writing fails, nil otherwise
|
|
WriteAPIRequest(apiRequest []byte) error
|
|
|
|
// WriteAPIResponse writes the upstream API response details to the log.
|
|
// This should be called after the streaming response is complete.
|
|
//
|
|
// Parameters:
|
|
// - apiResponse: The API response data
|
|
//
|
|
// Returns:
|
|
// - error: An error if writing fails, nil otherwise
|
|
WriteAPIResponse(apiResponse []byte) error
|
|
|
|
// WriteAPIWebsocketTimeline writes the upstream websocket timeline to the log.
|
|
// This should be called when upstream communication happened over websocket.
|
|
//
|
|
// Parameters:
|
|
// - apiWebsocketTimeline: The upstream websocket event timeline
|
|
//
|
|
// Returns:
|
|
// - error: An error if writing fails, nil otherwise
|
|
WriteAPIWebsocketTimeline(apiWebsocketTimeline []byte) error
|
|
|
|
// SetFirstChunkTimestamp sets the TTFB timestamp captured when first chunk was received.
|
|
//
|
|
// Parameters:
|
|
// - timestamp: The time when first response chunk was received
|
|
SetFirstChunkTimestamp(timestamp time.Time)
|
|
|
|
// Close finalizes the log file and cleans up resources.
|
|
//
|
|
// Returns:
|
|
// - error: An error if closing fails, nil otherwise
|
|
Close() error
|
|
}
|
|
|
|
// FileRequestLogger implements RequestLogger using file-based storage.
|
|
// It provides file-based logging functionality for HTTP requests and responses.
|
|
type FileRequestLogger struct {
|
|
// enabled indicates whether request logging is currently enabled.
|
|
enabled bool
|
|
|
|
// logsDir is the directory where log files are stored.
|
|
logsDir string
|
|
|
|
// errorLogsMaxFiles limits the number of error log files retained.
|
|
errorLogsMaxFiles int
|
|
|
|
homeEnabled bool
|
|
}
|
|
|
|
// NewFileRequestLogger creates a new file-based request logger.
|
|
//
|
|
// Parameters:
|
|
// - enabled: Whether request logging should be enabled
|
|
// - logsDir: The directory where log files should be stored (can be relative)
|
|
// - configDir: The directory of the configuration file; when logsDir is
|
|
// relative, it will be resolved relative to this directory
|
|
// - errorLogsMaxFiles: Maximum number of error log files to retain (0 = no cleanup)
|
|
//
|
|
// Returns:
|
|
// - *FileRequestLogger: A new file-based request logger instance
|
|
func NewFileRequestLogger(enabled bool, logsDir string, configDir string, errorLogsMaxFiles int) *FileRequestLogger {
|
|
// Resolve logsDir relative to the configuration file directory when it's not absolute.
|
|
if !filepath.IsAbs(logsDir) {
|
|
// If configDir is provided, resolve logsDir relative to it.
|
|
if configDir != "" {
|
|
logsDir = filepath.Join(configDir, logsDir)
|
|
}
|
|
}
|
|
return &FileRequestLogger{
|
|
enabled: enabled,
|
|
logsDir: logsDir,
|
|
errorLogsMaxFiles: errorLogsMaxFiles,
|
|
homeEnabled: false,
|
|
}
|
|
}
|
|
|
|
// IsEnabled returns whether request logging is currently enabled.
|
|
//
|
|
// Returns:
|
|
// - bool: True if logging is enabled, false otherwise
|
|
func (l *FileRequestLogger) IsEnabled() bool {
|
|
return l.enabled
|
|
}
|
|
|
|
// SetEnabled updates the request logging enabled state.
|
|
// This method allows dynamic enabling/disabling of request logging.
|
|
//
|
|
// Parameters:
|
|
// - enabled: Whether request logging should be enabled
|
|
func (l *FileRequestLogger) SetEnabled(enabled bool) {
|
|
l.enabled = enabled
|
|
}
|
|
|
|
// SetErrorLogsMaxFiles updates the maximum number of error log files to retain.
|
|
func (l *FileRequestLogger) SetErrorLogsMaxFiles(maxFiles int) {
|
|
l.errorLogsMaxFiles = maxFiles
|
|
}
|
|
|
|
// NewFileBodySource creates a temp-backed source under the request log directory.
|
|
func (l *FileRequestLogger) NewFileBodySource(prefix string) (*FileBodySource, error) {
|
|
if l == nil {
|
|
return nil, fmt.Errorf("file request logger is nil")
|
|
}
|
|
if errEnsure := l.ensureLogsDir(); errEnsure != nil {
|
|
return nil, errEnsure
|
|
}
|
|
return NewFileBodySourceInDir(l.logsDir, prefix)
|
|
}
|