Skip to content

Logger Package Implementation Requirements #471

@alfredosa

Description

@alfredosa

Logger Package Implementation Requirements

Overview

Implement a flexible, extensible logging package for the Mage project that wraps Go's slog library with enhanced features including colored output, artifact logging, GitHub Actions integration, and per-devtool configurability.

Core Requirements

1. Colored Output (Label-Only Coloring)

  • Requirement: Color only the log level labels (DEBUG, INFO, WARN, ERROR), not the entire message
  • Rationale: Maintains readability while providing visual distinction between log levels
  • Implementation: Use slog.HandlerOptions.ReplaceAttr to intercept and colorize level labels
  • Color Scheme:
    • DEBUG: Gray + Bold
    • INFO: Blue + Bold
    • WARN: Yellow + Bold
    • ERROR: Red + Bold
  • Auto-detection: Automatically detect TTY and disable colors for non-terminal outputs
  • Override: Support explicit enable/disable via configuration

2. Artifact Logging

  • Requirement: Write logs to both console and artifact file simultaneously
  • Use Case: CI/CD pipelines need persistent log files for debugging and archival
  • Behavior:
    • Multi-writer support (console + file)
    • Disable colors in artifact files (plain text only)
    • Append mode for artifact files
    • Optional path configuration via LOG_ARTIFACT_PATH environment variable
  • File Management: Handle file creation, permissions (0644), and proper opening

3. GitHub Actions Integration

  • Requirement: Support GitHub Actions-specific logging formats
  • Features Needed:
    • Workflow Commands: Emit ::error::, ::warning::, ::notice::, ::debug:: commands
    • Step Summary: Write markdown summaries to $GITHUB_STEP_SUMMARY
    • Log Grouping: Support collapsible log groups with ::group:: and ::endgroup::
    • Annotations: File/line annotations for errors and warnings
  • Detection: Auto-detect GitHub Actions environment via GITHUB_ACTIONS=true
  • Format Example:
    ::error file=app.go,line=10,col=5::Connection failed
    ::group::Building Go modules
    ... nested logs ...
    ::endgroup::
    

4. Per-Devtool Configurability

  • Requirement: Different devtools need different logging configurations
  • Configuration Dimensions:
    • Log Level: Each devtool may have different verbosity needs
    • Output Format: Some tools output JSON, others text
    • Artifact Paths: Tool-specific artifact files (e.g., golangci-lint.log, prettier.log)
    • Source Location: File/line information (expensive, only when needed)
    • Output Parsing: Some tools need their output parsed and reformatted
  • Configuration Sources (priority order):
    1. Explicit code configuration (highest priority)
    2. Environment variables (e.g., LOG_LEVEL_GOLANG=debug)
    3. Configuration file (future: .mage/logging.yaml)
    4. Global defaults (lowest priority)
  • Example Configuration:
    // Global default
    logger.SetGlobalLevel(slog.LevelInfo)
    
    // Per-devtool override
    golangLogger := logger.New(
        logger.WithDevTool("golang"),
        logger.WithLevel(slog.LevelDebug),
        logger.WithArtifact("artifacts/golang.log"),
        logger.WithSource(true),
    )

5. Structured Logging with Context

  • Requirement: Support key-value pairs and context propagation
  • Features:
    • Structured key-value logging: log.Info("message", "key", value)
    • Context variants: InfoContext(ctx, "message", "key", value)
    • Logger chaining with attributes: log.With("package", "golang").Info("message")
    • Attribute inheritance through context
  • Use Cases for CLI Dev Tooling:
    • Track which devtool is running (e.g., tool=golangci-lint)
    • Track operation/command being executed (e.g., operation=lint, command=check)
    • Track file paths or directories being processed (e.g., directory=/repo/cmd)
    • Track GitHub Actions job/step context (e.g., job=build, step=3)
    • Correlate logs across multiple tool invocations in a pipeline
    • Track performance metrics (e.g., duration=2.3s, files_processed=42)

6. Environment Variable Configuration

  • Required Variables:
    • LOG_LEVEL: Global log level (debug, info, warn, error)
    • LOG_ARTIFACT_PATH: Path to artifact file
    • LOG_FORMAT: Output format (text, json, github) [future]
    • LOG_COLOR: Force enable/disable colors (true, false, auto)
    • LOG_LEVEL_<DEVTOOL>: Per-devtool level overrides (e.g., LOG_LEVEL_GOLANG=debug)
  • Behavior: Environment variables should be read once at initialization, not on every log call

7. Output Format Support

  • Current: Text format via slog.TextHandler
  • Future Requirements:
    • JSON Format: Machine-parseable structured logs
    • GitHub Format: GitHub Actions workflow commands
    • Custom Formats: Pluggable handler interface
  • Format Selection: Via LOG_FORMAT env var or WithFormat() option

Technical Constraints

1. Use Standard Library slog

  • Rationale: Go 1.21+ standard library, no external dependencies
  • Approach: Wrap slog rather than replace it
  • Benefits: Familiar API, battle-tested, forward-compatible

2. Zero Breaking Changes

  • Requirement: Logger API must be stable from day one
  • Strategy: Use options pattern for all configuration
  • Versioning: Consider this v1.0 API - no breaking changes allowed

3. Performance Considerations

  • Requirement: Minimal overhead, especially when logs are disabled
  • Constraints:
    • Level checks should be fast (slog handles this)
    • Color detection done once at initialization
    • No string formatting when log level disabled
    • File I/O should not block logging calls

Integration Points

1. Global vs Local Loggers

  • Global Default: logger.Default() for simple cases
  • Package Loggers: logger.Default().With("package", "golang") for package-level
  • Operation Loggers: logger.New(opts...) for specific operations
  • Context Loggers: Pass loggers through context when needed

File Structure

internal/logger/
├── logger.go       
├── options.go     
├── colors.go     
├── github.go      
├── formats.go     
└── logger_test.go  

Open Questions & Trade-offs

1. File Lifecycle Management

Question: How should artifact files be closed?

  • Option A: Keep file handle open for duration of program (simpler)
  • Option B: Add Close() method, require manual cleanup (cleaner)
  • Option C: Use sync.Pool for file handles (complex)
  • Recommendation: Start with Option A, add Option B if needed

2. Log Level Granularity

Question: Should we support more granular levels than debug/info/warn/error?

  • Option A: Stick with standard slog levels (simpler)
  • Option B: Add custom levels like TRACE, FATAL (more flexible)
  • Recommendation: Start with Option A, slog's four levels are sufficient

3. Configuration File Support

Question: Should we support a configuration file (e.g., .mage/logging.yaml)?

  • Pros: Centralized configuration, version controlled
  • Cons: Additional complexity, file parsing overhead
  • Recommendation: Environment variables

4. Performance vs. Features

Note: Synchronous logging features might block or introduce small perf impact.
I don't think this is a big deal now. Just putting it down as a note.

5. GitHub Actions Auto-Detection

Question: Should GitHub format be auto-enabled when GITHUB_ACTIONS=true?

  • Option A: Auto-enable (convenience, less configuration)
  • Option B: Require explicit opt-in (explicit, user control)
  • Recommendation: Auto-enable with ability to override

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions