-
Notifications
You must be signed in to change notification settings - Fork 5
Description
Note to iloom
Execute in TRIVIAL mode due to plan being specified below:
Problem
When --json flag is used with CLI commands, all logging should go to stderr so that only valid JSON is output to stdout. The current implementation requires passing a custom logger through every layer of the call stack ("prop drilling"):
cli.ts → StartCommand → LoomManager → DatabaseManager → NeonProvider
→ IssueTrackerFactory → GitHubService
→ installDependencies()
This approach:
- Requires modifying constructors and method signatures throughout the codebase
- Is error-prone (easy to forget to pass the logger somewhere)
- Makes the code harder to maintain
- Couples unrelated components to logging concerns
Solution
Use Node.js's built-in AsyncLocalStorage to create a React Context-like pattern for logging. This allows any code in the async call stack to access the correct logger without explicit parameter passing.
Implementation Plan
1. Create logger context module
Create src/utils/logger-context.ts:
import { AsyncLocalStorage } from 'node:async_hooks'
import { logger as defaultLogger, type Logger } from './logger.js'
const loggerStorage = new AsyncLocalStorage<Logger>()
// Get current logger from context, or fall back to default
export function getLogger(): Logger {
return loggerStorage.getStore() ?? defaultLogger
}
// Run code with a specific logger in context
export function withLogger<T>(logger: Logger, fn: () => T | Promise<T>): T | Promise<T> {
return loggerStorage.run(logger, fn)
}2. Update CLI entry point
Wrap command execution in withLogger() when --json flag is used:
// cli.ts
if (options.json) {
const jsonLogger = createStderrLogger()
await withLogger(jsonLogger, () => command.execute())
} else {
await command.execute()
}3. Update all logging call sites
Replace direct logger imports with getLogger() calls in:
-
src/lib/GitHubService.ts -
src/lib/IssueTrackerFactory.ts -
src/lib/LoomManager.ts -
src/lib/DatabaseManager.ts -
src/lib/providers/NeonProvider.ts -
src/utils/package-manager.ts -
src/utils/neon-helpers.ts -
src/commands/start.ts -
src/commands/finish.ts -
src/commands/cleanup.ts - Any other files using the logger
4. Remove prop drilling code
Revert/remove the custom logger parameters added to:
- Constructor parameters (
logger?: Logger) - Method parameters
- Instance properties (
private logger: Logger)
5. Update tests
- Add tests for
logger-context.ts - Verify that
getLogger()returns the correct logger in different contexts - Test that
withLogger()properly scopes the logger to the async context
Benefits
- Cleaner API: No need to pass loggers through constructors/methods
- Less error-prone: Can't forget to pass the logger
- Easier to maintain: Adding logging to new code just requires
getLogger() - Built-in: Uses Node.js native
AsyncLocalStorage, no external dependencies - Familiar pattern: Similar to React Context for frontend developers
Files Changed (Current Prop Drilling Approach to Revert)
The following files were modified with the prop drilling approach and should be reverted/refactored:
src/cli.ts- passes jsonLogger to IssueTrackerFactorysrc/commands/finish.ts- passes logger to installDependenciessrc/commands/start.ts- passes logger to createNeonProviderFromSettingssrc/lib/GitHubService.ts- added logger constructor paramsrc/lib/IssueTrackerFactory.ts- added logger param to create()src/lib/LoomManager.ts- passes logger to installDependenciessrc/lib/providers/NeonProvider.ts- added logger constructor paramsrc/utils/neon-helpers.ts- added logger paramsrc/utils/package-manager.ts- added customLogger param
Testing
- Run
il start <issue> --jsonand verify only JSON goes to stdout - Run
il start <issue> --json 2>/dev/null | jq .to confirm valid JSON - Run without
--jsonand verify normal logging behavior
Metadata
Metadata
Assignees
Labels
Type
Projects
Status