207 lines · 7.6 KB · Go
RawDownload
// 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)}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t