Logging Guidelines
January 13, 2026 ยท View on GitHub
This codebase uses Go's structured logging (log/slog) with charmbracelet/log as the handler backend for human-friendly formatted output.
Configuration
Command-line Flags
--log-level Log level: debug, info, warn, error (default: "info")
--log-format Log format: text, json, logfmt (default: "text")
Environment Variables
Environment variables override command-line flags:
LOG_LEVEL Overrides --log-level
LOG_FORMAT Overrides --log-format
Examples
# Run with debug logging
./windshift --log-level=debug
# Run with JSON output (useful for log aggregation)
./windshift --log-format=json
# Using environment variables
LOG_LEVEL=debug LOG_FORMAT=json ./windshift
Log Levels
| Level | When to Use |
|---|---|
| debug | Detailed traces, performance metrics, request/response details. Only shown with --log-level=debug. |
| info | Normal operational events: startup, shutdown, significant state changes. Default visible level. |
| warn | Recoverable errors, deprecation notices, unexpected but handled conditions. |
| error | Failures requiring attention, unrecoverable errors, permission denials. |
Structured Logging
All log messages use structured attributes for better filtering and analysis:
slog.Debug("item create request received")
slog.Info("service started", slog.String("version", version))
slog.Warn("cache initialization failed", slog.Any("error", err))
slog.Error("permission check failed",
slog.Int("user_id", userID),
slog.Int("workspace_id", workspaceID),
slog.Any("error", err))
Common Attributes
| Attribute | Type | Description |
|---|---|---|
component | string | Subsystem identifier (e.g., "sso", "jira", "scm") |
error | any | Error value for failures |
user_id | int | User ID for user-related operations |
workspace_id | int | Workspace ID for workspace operations |
item_id | int | Item ID for item operations |
Component Tags
Use slog.String("component", "name") to identify subsystems:
| Component | Description |
|---|---|
sso | SSO/OIDC authentication |
jira | Jira integration |
scm | SCM providers (GitHub, GitLab, etc.) |
notifications | Notification service |
permissions | Permission cache and checks |
attachments | File attachments |
activity | Activity tracking |
database | Database operations |
Output Formats
Text (Default)
Human-readable colored output, ideal for development:
INFO service started version=1.0.0
DEBUG creating session component=sso user_id=123
WARN cache initialization failed error="connection refused"
JSON
Machine-readable format for log aggregation systems:
{"level":"INFO","msg":"service started","version":"1.0.0"}
{"level":"DEBUG","msg":"creating session","component":"sso","user_id":123}
Logfmt
Key-value format, compatible with many log analysis tools:
level=INFO msg="service started" version=1.0.0
level=DEBUG msg="creating session" component=sso user_id=123
Performance Logging
Performance-critical sections use grouped timing attributes:
slog.Debug("item creation performance",
slog.Int("item_id", itemID),
slog.Group("timings_ms",
slog.Float64("validation", 1.23),
slog.Float64("transaction", 4.56),
slog.Float64("total", 7.89),
))
Test Files
In test files, use t.Logf() instead of slog:
func TestSomething(t *testing.T) {
t.Logf("Debug info: %v", data)
}
Best Practices
-
Use appropriate log levels: Debug for development details, Info for operational events, Warn for recoverable issues, Error for failures.
-
Include context: Always include relevant IDs (user_id, workspace_id, item_id) to aid debugging.
-
Use component tags: Add
slog.String("component", "name")to identify the subsystem. -
Avoid sensitive data: Never log passwords, tokens, or other secrets.
-
Be concise: Log messages should be lowercase and descriptive without prefixes like "Error:".
Logger Initialization
The logger is initialized in internal/logger/logger.go and set as the default slog logger during application startup. All code can use slog.* functions directly without needing a logger instance.