Documentation
¶
Overview ¶
Package app provides core application utilities including configuration management, structured logging with context propagation, and initialization helpers.
Configuration Management ¶
The package uses Viper to provide layered configuration from multiple sources. Configuration is loaded in the following priority order (highest priority last):
1. Global base: default.yaml 2. Global environment: {MODE}.yaml (development.yaml, production.yaml, etc.) 3. Command base: {cmdName}/default.yaml (replaces all previous config if exists) 4. Command environment: {cmdName}/{MODE}.yaml (merges with command base) 5. Environment variables: Final overrides using __ as separator (e.g., LOG__LEVEL)
IMPORTANT: Command-specific default.yaml completely replaces global configuration rather than merging with it. Use command-specific configs only when you need completely different settings for specific commands.
Configuration files should be placed in a configs/ directory. The Init() function automatically looks for configs in the working directory, or you can specify a custom path with InitWithConfigPath().
Example config structure:
configs/
├── default.yaml # Global defaults
├── development.yaml # Global development overrides
├── production.yaml # Global production overrides
├── myapp/
│ ├── default.yaml # App-specific config (replaces global!)
│ ├── development.yaml # App-specific development overrides
│ └── production.yaml # App-specific production overrides
└── worker/
├── default.yaml # Worker-specific config (replaces global!)
└── production.yaml # Worker-specific production overrides
Example default.yaml:
log: level: info format: tint database: driver: postgres host: localhost port: 5432
Initialization ¶
Initialize the application at startup with one of two methods:
func main() {
// Method 1: Auto-detect config path from working directory
app.Init("myapp") // Loads config from ./configs/
// Method 2: Specify custom config path
app.InitWithConfigPath("myapp", "/etc/myapp/configs")
// ... rest of application
}
The command name passed to Init() functions serves multiple purposes:
- Appears in all log messages as the "cmd" field
- Used to find command-specific configuration files
- Helps identify different services/commands in logs
The MODE environment variable controls which config file is loaded:
export MODE=production # Loads production.yaml + myapp/production.yaml export MODE=development # Loads development.yaml + myapp/development.yaml (default)
Logging ¶
The package provides structured logging using Go's standard log/slog with automatic context propagation and enhanced features:
Supported log formats (configured via log.format in config):
- tint: Pretty colored console output (default)
- json: JSON structured logs
- plain-text: Plain text logs
Log levels: debug, info, warn, error
Basic logging:
import "log/slog"
slog.Info("Server started", "port", 8080)
slog.Error("Database connection failed", "error", err)
Context-aware logging:
ctx = app.WithLogAttrs(ctx, slog.String("user_id", "123"))
ctx = app.WithLogAttrs(ctx, slog.String("request_id", "abc"))
slog.InfoContext(ctx, "Processing request") // Includes user_id and request_id
All log messages automatically include:
- cmd: Command name (passed to Init functions)
- hostname: Current hostname
- Any attributes added to the context via WithLogAttrs
Configuration Access ¶
Access configuration values using the Config() function:
cfg := app.Config()
dbHost := cfg.GetString("database.host")
dbPort := cfg.GetInt("database.port")
enableFeature := cfg.GetBool("features.new_feature")
Environment variables override config file values:
export LOG__LEVEL=debug # Overrides log.level export DATABASE__HOST=prod-db # Overrides database.host
Best Practices ¶
- Call Init() or InitWithConfigPath() once at application startup
- Use descriptive command names to identify different services/commands in logs
- Use global configs for shared settings, command-specific configs sparingly
- Remember that command-specific default.yaml replaces (not merges) global config
- Add request/user context via WithLogAttrs for better traceability
- Use structured logging (key-value pairs) instead of string formatting
- Configure log format and level via config files, not hardcoded
Example ¶
Example demonstrates basic application initialization with configuration and logging setup.
package main
import (
"log/slog"
)
func main() {
// Initialize application with command name (loads config from ./configs/)
// Note: This would normally be called with a valid config directory
// app.Init("myapp")
// Access configuration
// cfg := app.Config()
// dbHost := cfg.GetString("database.host")
// logLevel := cfg.GetString("log.level")
// Use structured logging
slog.Info("Application started", "version", "1.0.0")
}
Example (Configuration) ¶
Example_configuration demonstrates accessing configuration values from different sources (config files and environment variables).
package main
import ()
func main() {
// Initialize application with command name
// app.Init("myapp")
// Get configuration
// cfg := app.Config()
// Access string values
// dbHost := cfg.GetString("database.host")
// fmt.Println("Database host:", dbHost)
// Access integer values
// dbPort := cfg.GetInt("database.port")
// fmt.Println("Database port:", dbPort)
// Access boolean values
// enableDebug := cfg.GetBool("debug")
// fmt.Println("Debug enabled:", enableDebug)
// Access with defaults
// timeout := cfg.GetInt("timeout")
// if timeout == 0 {
// timeout = 30
// }
// Environment variables override config files
// export DATABASE__HOST=prod-db # Overrides database.host
// export LOG__LEVEL=debug # Overrides log.level
}
Example (CustomConfigPath) ¶
Example_customConfigPath demonstrates using a custom configuration directory path instead of the default ./configs/ location.
package main
import ()
func main() {
// Initialize with custom config path
// app.InitWithConfigPath("worker", "/etc/myapp/configs")
// This will load configuration in the following order:
// 1. /etc/myapp/configs/default.yaml
// 2. /etc/myapp/configs/{MODE}.yaml (e.g., production.yaml)
// 3. /etc/myapp/configs/worker/default.yaml
// 4. /etc/myapp/configs/worker/{MODE}.yaml
// 5. Environment variables (highest priority)
// Access the merged configuration
// cfg := app.Config()
// workerThreads := cfg.GetInt("worker.threads")
// slog.Info("Worker initialized", "threads", workerThreads)
}
Example (WithLogAttrs) ¶
Example_withLogAttrs demonstrates adding contextual information to logs that automatically propagates through the request lifecycle.
package main
import (
"context"
"log/slog"
"github.com/poly-workshop/go-webmods/app"
)
func main() {
ctx := context.Background()
// Add user context
ctx = app.WithLogAttrs(ctx, slog.String("user_id", "123"))
ctx = app.WithLogAttrs(ctx, slog.String("action", "login"))
// All logs with this context will include user_id and action
slog.InfoContext(ctx, "User logged in")
// Add more context as needed
ctx = app.WithLogAttrs(ctx, slog.String("session_id", "abc-def"))
slog.InfoContext(ctx, "Session created")
}
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func InitWithConfigPath ¶ added in v0.3.0
Types ¶
type ContextKey ¶
type ContextKey string