Comprehensive logging configuration for CLI applications
The Glazed logging section provides comprehensive logging configuration for CLI applications through command-line fields, environment variables, and configuration files. The section handles setup for console output and file logging while supporting multiple output formats and verbosity levels.
graph TD
A[CLI Fields] --> B[Logging Section]
B --> C[Console Output]
B --> D[File Output]
C --> F[Human-readable text]
D --> G[Rotating log files]
The logging section transforms command-line fields into configured log outputs, supporting development, testing, and production deployment scenarios.
Add the logging section to any Glazed command:
import (
"github.com/go-go-golems/glazed/pkg/cmds/logging"
"github.com/rs/zerolog/log"
)
func NewMyCommand() (*MyCommand, error) {
loggingSection, err := logging.NewLoggingSection()
if err != nil {
return nil, fmt.Errorf("failed to create logging section: %w", err)
}
cmdDesc := cmds.NewCommandDescription(
"my-command",
cmds.WithShort("Command with logging support"),
cmds.WithSectionsList(loggingSection),
)
return &MyCommand{CommandDescription: cmdDesc}, nil
}
func (c *MyCommand) RunIntoGlazeProcessor(
ctx context.Context,
parsedSections *values.Values,
gp middlewares.Processor,
) error {
// Initialize logging settings from parsed sections
var settings logging.LoggingSettings
if err := parsedSections.DecodeSectionInto(logging.LoggingSectionSlug, &settings); err != nil {
return fmt.Errorf("failed to get logging settings: %w", err)
}
if err := logging.InitLoggerFromSettings(&settings); err != nil {
return fmt.Errorf("failed to initialize logger: %w", err)
}
log.Info().Msg("Processing started")
// Command implementation
return nil
}
Use structured fields for effective logging:
func processFile(fileName string) error {
start := time.Now()
log.Debug().
Str("file", fileName).
Msg("Starting file processing")
data, err := os.ReadFile(fileName)
if err != nil {
log.Error().
Str("file", fileName).
Err(err).
Msg("Failed to read file")
return fmt.Errorf("reading file %s: %w", fileName, err)
}
log.Info().
Str("file", fileName).
Int("bytes_processed", len(data)).
Dur("duration", time.Since(start)).
Msg("File processed successfully")
return nil
}
Create loggers with persistent context for complex operations:
func processUser(userID string) error {
userLogger := log.With().
Str("user_id", userID).
Str("operation", "user_processing").
Logger()
userLogger.Info().Msg("Starting user processing")
if err := validateUser(userID); err != nil {
userLogger.Error().
Err(err).
Msg("User validation failed")
return err
}
userLogger.Info().Msg("User processing completed")
return nil
}
| Field | Type | Default | Description |
|---|---|---|---|
--log-level | choice | info | Verbosity level (trace, debug, info, warn, error, fatal) |
--log-format | choice | text | Output format (text, json) |
--log-file | string | "" | Output file path with automatic rotation |
--with-caller | bool | false | Include source file and line number |
--log-to-stdout | bool | false | Force output to stdout regardless of other settings |
--log-config | string list | [] | Additional logcopter profile/config files, loaded in command-line order |
--log-area | key-value | {} | Per-area log level override, for example app.view:debug or app.db=warn |
--strict-log-areas | bool | false | Fail if configured areas do not match known generated logcopter areas |
Glazed configures logcopter's default manager in-place. Applications keep using
github.com/go-go-golems/glazed/pkg/cmds/logging; generated package loggers use
github.com/go-go-golems/logcopter/pkg/logcopter.
Application YAML can use a canonical logging.areas map:
logging:
log-level: info
log-format: text
areas:
app.view.render: trace
app.db: warn
lib.protocol: debug
CLI overrides accept both colon and equals syntax:
myapp serve \
--log-area app.view:debug \
--log-area app.db=warn
--log-area is a repeatable key-value flag. Cobra/pflag also accepts comma
splitting for StringSlice flags:
myapp serve --log-area app.view=debug,app.db=warn
Reusable logcopter profile files can be loaded explicitly with --log-config:
myapp serve --log-config ~/.config/logcopter/profiles/dev.yaml
Glazed supports both application-style wrapped profiles:
logging:
log-level: info
areas:
app.view.render: trace
app.db: warn
and direct logcopter-only profiles:
level: info
format: text
areas:
app.view.render: trace
app.db: warn
In the Cobra initialization path, merge order is:
--log-config files, in command-line order;--log-level and --log-area.Logcopter uses per-area child logger levels. Glazed therefore keeps zerolog's
global level permissive enough not to suppress a trace-enabled area while the
conventional global logger remains filtered at --log-level.
| Level | Purpose | Usage |
|---|---|---|
trace | Extremely detailed execution flow | Performance debugging |
debug | Detailed diagnostic information | Development troubleshooting |
info | General application progress | Default production level |
warn | Unexpected conditions that don't halt execution | Monitoring degraded performance |
error | Error conditions with continued execution | Problem tracking |
fatal | Critical errors requiring application termination | System failures |
Text format (human-readable):
INFO 2023-12-07T10:30:00Z Processing started
DEBUG 2023-12-07T10:30:01Z File loaded file=data.csv rows=1000
JSON format (machine-readable):
{"level":"info","time":"2023-12-07T10:30:00Z","message":"Processing started"}
{"level":"debug","time":"2023-12-07T10:30:01Z","message":"File loaded","file":"data.csv","rows":1000}
Configure automatic log rotation for production:
myapp process-data \
--log-level info \
--log-format json \
--log-file /var/log/myapp/application.log
File logging features:
Configure logging through environment variables:
export MYAPP_LOG_LEVEL=info
export MYAPP_LOG_FORMAT=json
export MYAPP_LOG_FILE=/var/log/myapp.log
type LoggingSettings struct {
WithCaller bool `glazed:"with-caller"`
LogLevel string `glazed:"log-level"`
LogFormat string `glazed:"log-format"`
LogFile string `glazed:"log-file"`
LogToStdout bool `glazed:"log-to-stdout"`
LogConfigFiles []string `glazed:"log-config"`
LogAreas map[string]string `glazed:"log-area"`
Areas map[string]string `glazed:"areas"`
StrictAreas bool `glazed:"strict-log-areas"`
}
To initialize logging from parsed sections, use the standard pattern:
// Extract logging settings from parsed sections
var settings logging.LoggingSettings
if err := parsedSections.DecodeSectionInto(logging.LoggingSectionSlug, &settings); err != nil {
return fmt.Errorf("failed to get logging settings: %w", err)
}
// Initialize logger from settings
if err := logging.InitLoggerFromSettings(&settings); err != nil {
return fmt.Errorf("failed to initialize logger: %w", err)
}
func InitLoggerFromSettings(settings *LoggingSettings) error
Configures global logger from logging settings struct.
Usage:
if err := logging.InitLoggerFromSettings(&settings); err != nil {
return fmt.Errorf("failed to initialize logger: %w", err)
}
For custom validation of logging settings:
var settings logging.LoggingSettings
if err := parsedSections.DecodeSectionInto(logging.LoggingSectionSlug, &settings); err != nil {
return fmt.Errorf("failed to extract logging settings: %w", err)
}
// Custom validation
return logging.SetupLogging(settings)
func NewLoggingSection() (schema.Section, error)
Creates field section for command definitions.
Usage:
loggingSection, err := logging.NewLoggingSection()
if err != nil {
return nil, err
}
cmdDesc := cmds.NewCommandDescription(
"my-command",
cmds.WithSectionsList(loggingSection),
)
Avoid performance penalties for debug logging:
// Efficient: Check if debug is enabled before expensive operations
if log.Debug().Enabled() {
expensiveData := calculateComplexDebuggingInfo()
log.Debug().
Interface("debug_data", expensiveData).
Msg("Detailed debug information")
}
// Always efficient: Simple field logging
log.Info().
Str("user", userID).
Int("count", itemCount).
Dur("elapsed", duration).
Msg("Operation completed")
logging:
log-level: debug
log-format: text
with-caller: true
logging:
log-level: info
log-format: json
log-file: /var/log/application.log
Symptoms: Application runs but produces no log output
Solutions:
var settings logging.LoggingSettings
parsedSections.DecodeSectionInto(logging.LoggingSectionSlug, &settings)
logging.InitLoggerFromSettings(&settings)
--log-level debug--log-file destinationsSymptoms: Unexpected output format (JSON instead of text or vice versa)
Solutions:
--log-format json or --log-format textLOG_FORMAT may override settings