Something went wrong. Try again.
sloproxy
Something went wrong. Try again.
7.7 kB · 207 lines
Go
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208// 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 instancefunc 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 otherwisefunc (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 enabledfunc (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)}