Skip to content

Commit c7147d6

Browse files
Copilotlpcox
andcommitted
Phase 2: Document fallback behavior patterns and initialization
Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
1 parent 9eb086f commit c7147d6

1 file changed

Lines changed: 130 additions & 2 deletions

File tree

internal/logger/common.go

Lines changed: 130 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,128 @@ import (
7272
//
7373
// When adding a new logger type, follow this pattern to ensure consistent behavior.
7474

75+
// Initialization Pattern for Logger Types
76+
//
77+
// The logger package follows a consistent initialization pattern across all logger types,
78+
// with support for customizable fallback behavior. This section documents the pattern
79+
// and explains the different fallback strategies used by each logger type.
80+
//
81+
// Standard Initialization Pattern:
82+
//
83+
// All logger types use the initLogger() generic helper function for initialization:
84+
//
85+
// func Init*Logger(logDir, fileName string) error {
86+
// logger, err := initLogger(
87+
// logDir, fileName, fileFlags,
88+
// setupFunc, // Configure logger after file is opened
89+
// errorHandler, // Handle initialization failures
90+
// )
91+
// initGlobal*Logger(logger)
92+
// return err
93+
// }
94+
//
95+
// The initLogger() helper:
96+
// 1. Attempts to create the log directory (if needed)
97+
// 2. Opens the log file with specified flags (os.O_APPEND, os.O_TRUNC, etc.)
98+
// 3. Calls setupFunc to configure the logger instance
99+
// 4. On error, calls errorHandler to implement fallback behavior
100+
// 5. Returns the initialized logger and any error
101+
//
102+
// Fallback Behavior Strategies:
103+
//
104+
// Different logger types implement different fallback strategies based on their purpose:
105+
//
106+
// 1. FileLogger - Stdout Fallback:
107+
// - Purpose: Operational logs must always be visible
108+
// - Fallback: Redirects to stdout if log directory/file creation fails
109+
// - Error: Returns nil (never fails, always provides output)
110+
// - Use case: Critical operational messages that must be seen
111+
//
112+
// Example error handler:
113+
// func(err error, logDir, fileName string) (*FileLogger, error) {
114+
// log.Printf("WARNING: Failed to initialize log file: %v", err)
115+
// log.Printf("WARNING: Falling back to stdout for logging")
116+
// return &FileLogger{
117+
// logDir: logDir,
118+
// fileName: fileName,
119+
// useFallback: true,
120+
// logger: log.New(os.Stdout, "", 0),
121+
// }, nil
122+
// }
123+
//
124+
// 2. MarkdownLogger - Silent Fallback:
125+
// - Purpose: GitHub workflow preview logs (optional enhancement)
126+
// - Fallback: Sets useFallback flag, produces no output
127+
// - Error: Returns nil (never fails, silently disables)
128+
// - Use case: Nice-to-have logs that shouldn't block operations
129+
//
130+
// Example error handler:
131+
// func(err error, logDir, fileName string) (*MarkdownLogger, error) {
132+
// return &MarkdownLogger{
133+
// logDir: logDir,
134+
// fileName: fileName,
135+
// useFallback: true,
136+
// }, nil
137+
// }
138+
//
139+
// 3. JSONLLogger - Strict Mode:
140+
// - Purpose: Machine-readable RPC message logs for analysis
141+
// - Fallback: None - returns error immediately
142+
// - Error: Returns error to caller
143+
// - Use case: Structured data that requires file storage
144+
//
145+
// Example error handler:
146+
// func(err error, logDir, fileName string) (*JSONLLogger, error) {
147+
// return nil, err
148+
// }
149+
//
150+
// 4. ServerFileLogger - Unified Fallback:
151+
// - Purpose: Per-server log files for troubleshooting
152+
// - Fallback: Sets useFallback flag, logs to unified logger only
153+
// - Error: Returns nil (never fails, falls back to unified logging)
154+
// - Use case: Per-server logs are helpful but not required
155+
//
156+
// Note: ServerFileLogger doesn't use initLogger() because it creates
157+
// files on-demand, but follows the same fallback philosophy.
158+
//
159+
// Global Logger Management:
160+
//
161+
// After initialization, all logger types register themselves as global loggers
162+
// using the generic initGlobal*Logger() helpers from global_helpers.go:
163+
//
164+
// - initGlobalFileLogger()
165+
// - initGlobalJSONLLogger()
166+
// - initGlobalMarkdownLogger()
167+
// - initGlobalServerFileLogger()
168+
//
169+
// These helpers ensure thread-safe initialization with proper cleanup of any
170+
// existing logger instance.
171+
//
172+
// When to Use Each Logger Type:
173+
//
174+
// - FileLogger: Required operational logs (startup, errors, warnings)
175+
// - MarkdownLogger: Optional GitHub workflow preview logs
176+
// - JSONLLogger: Structured RPC message logs for tooling/analysis
177+
// - ServerFileLogger: Per-server troubleshooting logs
178+
//
179+
// Adding a New Logger Type:
180+
//
181+
// When adding a new logger type:
182+
// 1. Implement Close() method following the Close Pattern (above)
183+
// 2. Add type to closableLogger constraint in global_helpers.go
184+
// 3. Use initLogger() for initialization with appropriate fallback strategy
185+
// 4. Add initGlobal*Logger() and closeGlobal*Logger() helpers
186+
// 5. Document the fallback strategy and use case
187+
//
188+
// This consistent pattern ensures:
189+
// - Predictable behavior across all loggers
190+
// - Easy to understand fallback strategies
191+
// - Minimal code duplication
192+
// - Type-safe global logger management
193+
//
194+
// See file_logger.go, jsonl_logger.go, markdown_logger.go, and server_file_logger.go
195+
// for complete implementation examples.
196+
75197
// closeLogFile is a common helper for closing log files with consistent error handling.
76198
// It syncs buffered data before closing and handles errors appropriately.
77199
// The mutex should already be held by the caller.
@@ -148,7 +270,7 @@ type loggerErrorHandlerFunc[T closableLogger] func(err error, logDir, fileName s
148270
// - fileName: Name of the log file
149271
// - flags: File opening flags (e.g., os.O_APPEND, os.O_TRUNC)
150272
// - setup: Function to configure the logger after the file is opened
151-
// - onError: Function to handle initialization errors (can return fallback or error)
273+
// - onError: Function to handle initialization errors (implements fallback strategy)
152274
//
153275
// Returns:
154276
// - T: The initialized logger instance
@@ -157,7 +279,13 @@ type loggerErrorHandlerFunc[T closableLogger] func(err error, logDir, fileName s
157279
// This function:
158280
// 1. Attempts to open the log file with the specified flags
159281
// 2. If successful, calls the setup function to configure the logger
160-
// 3. If unsuccessful, calls the error handler to decide on fallback behavior
282+
// 3. If unsuccessful, calls the error handler to implement the logger's fallback strategy
283+
//
284+
// The onError handler determines the fallback behavior. See "Initialization Pattern for Logger Types"
285+
// documentation above for details on fallback strategies:
286+
// - FileLogger: Falls back to stdout
287+
// - MarkdownLogger: Silent fallback (no output)
288+
// - JSONLLogger: Returns error (no fallback)
161289
func initLogger[T closableLogger](
162290
logDir, fileName string,
163291
flags int,

0 commit comments

Comments
 (0)