Table of Contents
- HLog - v0.10.4
- Installation
- Key Concepts and Features
- Quick Start
- Configuration
- Log Levels
- Output Modes
- File Management
- Structured Logging
- Advanced Usage
- Integration
- Best Practices
- Troubleshooting
- "LOG_DIR must be set when LOG_OUTPUT is 'file' or 'both'"
- "LOG_FILE_NAME must be set when LOG_OUTPUT is 'file' or 'both'"
- "Invalid log level specified"
- "Invalid LOG_OUTPUT"
- Log file permission errors
- Logs not appearing
- Logs not flushed on crash
- See Also
- Links
HLog - v0.10.4
HLog is a structured logging package built on top of zerolog, providing simple configuration, flexible output options, and automatic log file management for Go applications.
Installation
go get git.haelnorr.com/h/golib/hlog
Key Concepts and Features
HLog provides the following key features:
- Multiple Output Modes: Log to console, file, or both simultaneously
- Configurable Log Levels: Support for trace, debug, info, warn, error, fatal, and panic levels
- Environment-Based Configuration: Easy setup using environment variables
- Programmatic Configuration: Full control via Config struct when needed
- Automatic File Management: Handles log file creation and management
- High Performance: Built on zerolog for minimal overhead
- Error Stack Traces: Automatic stack trace marshaling for errors
- Flexible File Modes: Choose between append or overwrite modes
Quick Start
The simplest way to get started with HLog is using environment variables:
# Set environment variables
export LOG_LEVEL=info
export LOG_OUTPUT=console
package main
import (
"log"
"os"
"git.haelnorr.com/h/golib/hlog"
)
func main() {
// Load configuration from environment
cfg, err := hlog.ConfigFromEnv()
if err != nil {
log.Fatal(err)
}
// Create logger
logger, err := hlog.NewLogger(cfg, os.Stdout)
if err != nil {
log.Fatal(err)
}
defer logger.CloseLogFile()
// Start logging
logger.Info().Msg("Application started")
}
Configuration
HLog can be configured in two ways: via environment variables or programmatically.
Using ConfigFromEnv (Recommended)
The ConfigFromEnv() function reads configuration from environment variables:
cfg, err := hlog.ConfigFromEnv()
if err != nil {
log.Fatal(err)
}
logger, err := hlog.NewLogger(cfg, os.Stdout)
if err != nil {
log.Fatal(err)
}
defer logger.CloseLogFile()
Environment Variables
| Variable | Description | Valid Values | Default | Required |
|---|---|---|---|---|
LOG_LEVEL |
Log level for filtering messages | trace, debug, info, warn, error, fatal, panic | info | No |
LOG_OUTPUT |
Output destination | console, file, both | console | No |
LOG_DIR |
Directory path for log files | Any valid directory path | - | Yes (when LOG_OUTPUT is "file" or "both") |
LOG_FILE_NAME |
Name of the log file | Any valid filename | - | Yes (when LOG_OUTPUT is "file" or "both") |
LOG_APPEND |
Append to existing log file | true, false | true | No |
Programmatic Configuration
For more control, create a Config struct directly:
cfg := &hlog.Config{
LogLevel: hlog.InfoLevel,
LogOutput: "both",
LogDir: "/var/log/myapp",
LogFileName: "application.log",
LogAppend: true,
}
logger, err := hlog.NewLogger(cfg, os.Stdout)
if err != nil {
log.Fatal(err)
}
defer logger.CloseLogFile()
Log Levels
HLog supports seven log levels, from most to least verbose:
| Level | Description | Use Case |
|---|---|---|
trace |
Most verbose | Very detailed debugging, function entry/exit |
debug |
Debug information | Detailed debugging during development |
info |
Informational | General application events |
warn |
Warnings | Potentially harmful situations |
error |
Errors | Error events that might still allow the app to continue |
fatal |
Fatal errors | Severe errors that will cause the application to exit |
panic |
Panic | Panic-level errors that will panic the application |
Setting Log Levels
Via environment:
export LOG_LEVEL=debug
Programmatically:
cfg := &hlog.Config{
LogLevel: hlog.DebugLevel,
// ... other config
}
Logging at Different Levels
logger.Trace().Msg("Entering function")
logger.Debug().Str("user_id", "123").Msg("User details loaded")
logger.Info().Msg("Server started on port 8080")
logger.Warn().Msg("Cache miss, fetching from database")
logger.Error().Err(err).Msg("Failed to connect to database")
logger.Fatal().Msg("Cannot start: configuration file missing")
logger.Panic().Msg("Unrecoverable error occurred")
Output Modes
HLog supports three output modes:
Console Output
Logs are written to the provided io.Writer (typically os.Stdout or os.Stderr) with human-readable formatting:
export LOG_OUTPUT=console
logger, err := hlog.NewLogger(cfg, os.Stdout)
File Output
Logs are written to a file in the specified directory:
export LOG_OUTPUT=file
export LOG_DIR=/var/log/myapp
export LOG_FILE_NAME=server.log
// Note: io.Writer can be nil when using file-only output
logger, err := hlog.NewLogger(cfg, nil)
Both (Console + File)
Logs are written to both console and file simultaneously:
export LOG_OUTPUT=both
export LOG_DIR=/var/log/myapp
export LOG_FILE_NAME=server.log
logger, err := hlog.NewLogger(cfg, os.Stdout)
File Management
Append Mode
By default, HLog appends to existing log files, preserving logs across application restarts:
export LOG_APPEND=true
Overwrite Mode
Set LOG_APPEND=false to truncate the log file on each application start:
export LOG_APPEND=false
Closing Log Files
Always close log files when shutting down to ensure all buffered logs are flushed:
defer logger.CloseLogFile()
Or handle it explicitly:
if err := logger.CloseLogFile(); err != nil {
log.Printf("Error closing log file: %v", err)
}
Structured Logging
HLog leverages zerolog's structured logging capabilities:
// Simple message
logger.Info().Msg("User logged in")
// With fields
logger.Info().
Str("username", "john_doe").
Int("user_id", 12345).
Msg("User logged in")
// With error
logger.Error().
Err(err).
Str("operation", "database_query").
Msg("Operation failed")
// Nested objects
logger.Info().
Str("username", "john_doe").
Dict("metadata", zerolog.Dict().
Str("ip", "192.168.1.1").
Int("port", 8080),
).
Msg("Connection established")
Field Types
HLog supports all zerolog field types:
Str(key, value)- StringInt(key, value)- IntegerFloat64(key, value)- FloatBool(key, value)- BooleanErr(err)- Error with stack traceTime(key, value)- TimestampDur(key, duration)- DurationDict(key, dict)- Nested object
Advanced Usage
Custom Writers
You can use any io.Writer for console output:
// Write to a buffer
var buf bytes.Buffer
logger, err := hlog.NewLogger(cfg, &buf)
// Write to multiple destinations
multiWriter := io.MultiWriter(os.Stdout, &buf)
logger, err := hlog.NewLogger(cfg, multiWriter)
Context Loggers
Create sub-loggers with additional context:
// Create a logger with request context
requestLogger := logger.With().
Str("request_id", requestID).
Str("user_id", userID).
Logger()
requestLogger.Info().Msg("Processing request")
Conditional Logging
Check if a log level is enabled before expensive operations:
if logger.Debug().Enabled() {
// Only compute expensive debug info if debug logging is enabled
debugInfo := computeExpensiveDebugInfo()
logger.Debug().Interface("debug_info", debugInfo).Msg("Debug information")
}
Integration
HLog is designed to work seamlessly with other golib packages:
With ezconf
HLog includes built-in integration with ezconf for unified configuration management. This is the recommended approach when using multiple golib packages:
import (
"log"
"os"
"git.haelnorr.com/h/golib/ezconf"
"git.haelnorr.com/h/golib/hlog"
"git.haelnorr.com/h/golib/hws"
)
func main() {
// Create ezconf loader
loader := ezconf.New()
// Register hlog and other packages
loader.RegisterIntegrations(
hlog.NewEZConfIntegration(),
hws.NewEZConfIntegration(),
)
// Load all configurations
if err := loader.Load(); err != nil {
log.Fatal(err)
}
// Get hlog configuration
hlogCfg, _ := loader.GetConfig("hlog")
cfg := hlogCfg.(*hlog.Config)
// Create logger
logger, _ := hlog.NewLogger(cfg, os.Stdout)
defer logger.CloseLogFile()
logger.Info().Msg("Application started")
}
Benefits of using ezconf:
- Unified configuration across multiple packages
- Automatic environment variable documentation
- Generate and manage .env files
- Print all configuration options
See the ezconf documentation for more details.
With env Package
HLog uses the env package for environment variable parsing in ConfigFromEnv(). The env package provides type-safe environment variable access with defaults.
With hws/hwsauth
HLog integrates directly with the hws web server framework. The hws package provides an AddLogger function that accepts an hlog.Logger for structured logging throughout your web application:
import (
"log"
"os"
"git.haelnorr.com/h/golib/hlog"
"git.haelnorr.com/h/golib/hws"
)
// Configure logger
cfg := &hlog.Config{
LogLevel: hlog.InfoLevel,
LogOutput: "both",
LogDir: "/var/log/myapp",
LogFileName: "server.log",
}
logger, err := hlog.NewLogger(cfg, os.Stdout)
if err != nil {
log.Fatal(err)
}
defer logger.CloseLogFile()
// Create HWS server configuration
hwsConfig := &hws.Config{
// ... your hws config
}
server, err := hws.NewServer(hwsConfig)
if err != nil {
log.Fatal(err)
}
// Add the logger to the server
server.AddLogger(logger)
// The server will now use hlog for all logging
logger.Info().Msg("Starting web server")
server.Start()
The hws framework will automatically use the provided logger for request logging, error logging, and other server events, ensuring consistent structured logging throughout your application.
Best Practices
-
Always close log files: Use
defer logger.CloseLogFile()immediately after creating the logger to ensure logs are flushed on shutdown. -
Use structured logging: Add context fields instead of formatting strings:
// Good logger.Info().Str("user", username).Int("age", age).Msg("User created") // Avoid logger.Info().Msgf("User created: %s, age: %d", username, age) -
Choose appropriate log levels: Don't log everything at
Infolevel. UseDebugfor development details andTracefor very verbose output. -
Use ConfigFromEnv in production: Environment-based configuration makes it easy to change logging behavior without code changes.
-
Add context to loggers: Create sub-loggers with request-specific context rather than adding the same fields to every log call.
-
Avoid logging sensitive data: Never log passwords, tokens, or other sensitive information.
-
Use append mode in production: Set
LOG_APPEND=trueto preserve logs across restarts for better debugging and auditing. -
Check log level before expensive operations: Use
logger.Level().Enabled()to avoid computing log messages that won't be written. -
Centralize logger creation: Create your logger once at application startup and pass it through your application (dependency injection or context).
-
Use appropriate output modes:
- Development:
consolefor immediate feedback - Production:
bothfor real-time monitoring and persistent logs - Services:
filewhen logs are collected by external tools
- Development:
Troubleshooting
"LOG_DIR must be set when LOG_OUTPUT is 'file' or 'both'"
This error occurs when file logging is enabled but no directory is specified. Set the LOG_DIR environment variable:
export LOG_DIR=/var/log/myapp
"LOG_FILE_NAME must be set when LOG_OUTPUT is 'file' or 'both'"
This error occurs when file logging is enabled but no filename is specified. Set the LOG_FILE_NAME environment variable:
export LOG_FILE_NAME=server.log
"Invalid log level specified"
Valid log levels are: trace, debug, info, warn, error, fatal, panic. Check your LOG_LEVEL environment variable:
export LOG_LEVEL=info
"Invalid LOG_OUTPUT"
Valid output modes are: console, file, both. Check your LOG_OUTPUT environment variable:
export LOG_OUTPUT=console
Log file permission errors
Ensure the application has write permissions to the log directory:
mkdir -p /var/log/myapp
chmod 755 /var/log/myapp
Logs not appearing
- Check that your log level includes the messages you're trying to log
- Ensure you're not calling
logger.CloseLogFile()before logging - Verify that stdout/stderr is not being redirected elsewhere
- For file logging, check that the file exists and is being written to
Logs not flushed on crash
When a program crashes or is killed, buffered logs may not be written. Consider:
- Using
bothoutput mode to see logs in real-time on console - Calling
logger.CloseLogFile()explicitly before risky operations - Using
defer logger.CloseLogFile()to ensure cleanup on normal exits
See Also
- env - Environment variable helpers
- hws - HTTP web server framework
- hwsauth - Authentication middleware