Asteria is a structured logging library for Go — simple to start, powerful to scale.
- Zero-config quick start — one import, immediately usable with sensible defaults
- Module-based logging — isolate logs by module with independent config per module
- Structured context — attach key-value fields to any log entry via
WithFields,F,KV, orWith - 8 log levels —
Debug,Info,Notice,Warning,Error,Critical,Alert,Emergency - Pluggable formatters — Text (with color), JSON, JSON with timestamp, or implement your own
- Pluggable writers — Stdout, File, RotatingFile, Syslog, Stream, or implement your own
- StackWriter — fan-out logs to multiple destinations with per-level routing
- Filter chain — intercept, modify, or suppress log entries before output (global or per-module)
- Built-in filters —
WithStacktrace(auto-attach stack traces),EmergencyExit(exit on fatal) - Colorful terminal output — level-colored output with module name abbreviation
- Rotating log files — automatic file rotation with configurable naming and GC for stale handles
- Thread-safe — all operations are safe for concurrent use
- Caller info — optionally include file name, line number, and package in log context
- Installation
- Quick Start
- Module-based Logging
- Configuration
- Formatters
- Writers
- Filters
- Global Fields
- Lifecycle Management
- Complete Example
- Architecture
- License
go get -u github.com/mylxsw/asteria/logpackage main
import "github.com/mylxsw/asteria/log"
func main() {
// Simple logging — works out of the box
log.Debug("application started")
log.Info("processing request")
log.Error("something went wrong")
// Logging with structured context
log.WithFields(log.Fields{
"user_id": 123,
"username": "Tom",
}).Warningf("login attempt failed for user %s", "Tom")
// Shorthand syntax — F() is an alias for WithFields()
log.F(log.M{
"order_id": 456,
"amount": 99.9,
}).Info("order created")
// Key-value pairs — even more concise
log.KV("user_id", 123, "action", "login").Info("user action")
}Organize your logs by module — each module can have its own formatter, writer, and log level:
package main
import "github.com/mylxsw/asteria/log"
// Create a module logger — typically as a package-level variable
var logger = log.Module("app.user.service")
func main() {
logger.Debug("initializing user service")
logger.Error("failed to connect to database")
logger.WithFields(log.Fields{
"user_id": 123,
"username": "Tom",
}).Warningf("user %s has too many failed login attempts", "Tom")
}Control the minimum level of log output:
import "github.com/mylxsw/asteria/level"
// Set globally (affects all existing and new modules)
log.All().LogLevel(level.Warning)
// Set for default module only
log.SetLevel(level.Error)
// Set for a specific module
log.Module("app.db").LogLevel(level.Error)Log levels from highest to lowest severity:
Emergency>Alert>Critical>Error>Warning>Notice>Info>Debug
You can also check whether a level is enabled before constructing expensive log messages:
if log.DebugEnabled() {
log.Debugf("detailed state: %s", expensiveSerialize())
}// Enable globally for all modules
log.DefaultWithFileLine(true)
// Enable for a specific module
log.Module("app.db").WithFileLine(true)When enabled, each log entry automatically includes #file, #line, and #package in the context.
import "time"
// Set globally
log.DefaultLocation(time.UTC)
// Set for a specific module
log.Module("app").Location(time.UTC)Asteria ships with 3 built-in formatters. You can also create your own by implementing the formatter.Formatter interface:
type Formatter interface {
Format(f event.Event) string
}Colorful, human-readable output — perfect for development:
import "github.com/mylxsw/asteria/formatter"
// With color (default)
log.SetFormatter(formatter.NewDefaultFormatter(true))
// Without color (for log files or CI)
log.SetFormatter(formatter.NewDefaultFormatter(false))
// Per-module
log.Module("app").Formatter(formatter.NewDefaultFormatter(true))Output format:
[RFC3339 time] [LEVEL] module.name log message {context}
Context fields prefixed with
#are system-generated (file, line, package). Other fields are user-set viaWithFields.Disable color globally:
log.DefaultLogFormatter(formatter.NewDefaultFormatter(false))
Machine-readable output — ideal for log aggregation systems (ELK, Loki, etc.):
log.SetFormatter(formatter.NewJSONFormatter())
// Or per-module
log.Module("app").Formatter(formatter.NewJSONFormatter())Sample output:
{"module":"app.user","level_name":"ERROR","level":4,"context":{"#file":"main.go","#line":42,"user_id":123},"message":"request failed","datetime":"2024-01-15T10:30:00+08:00"}Same as JSON, but each line is prefixed with a timestamp for easy visual scanning:
log.SetFormatter(formatter.NewJSONWithTimeFormatter())
// Or per-module
log.Module("app").Formatter(formatter.NewJSONWithTimeFormatter())Sample output:
[2024-01-15T10:30:00+08:00] {"module":"app.user","level_name":"ERROR","level":4,...}
Asteria supports multiple log output destinations. Implement the writer.Writer interface for custom writers:
type Writer interface {
Write(le level.Level, module string, message string) error
ReOpen() error
Close() error
}import "github.com/mylxsw/asteria/writer"
log.SetWriter(writer.NewStdoutWriter())
// Or per-module
log.Module("app").Writer(writer.NewStdoutWriter())Write logs to a single file:
// Simple — with sensible defaults
log.SetWriter(writer.NewSingleFileWriter("/var/log/app.log"))
// Custom flags and permissions
log.SetWriter(writer.NewFileWriter("/var/log/app.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644))
// Per-module
log.Module("app").Writer(writer.NewSingleFileWriter("/var/log/app.log"))Automatically rotate log files based on your own rules (e.g., one file per day):
import (
"context"
"fmt"
"time"
)
fw := writer.NewDefaultRotatingFileWriter(context.TODO(), func(le level.Level, module string) string {
return fmt.Sprintf("/var/log/app.%s.log", time.Now().Format("20060102"))
})
log.SetWriter(fw)Inactive file handles are automatically garbage-collected (default interval: 10 minutes).
import "log/syslog"
log.SetWriter(writer.NewSyslogWriter("", "", syslog.LOG_DEBUG|syslog.LOG_SYSLOG, "app"))
// Or per-module
log.Module("app").Writer(writer.NewSyslogWriter("", "", syslog.LOG_DEBUG|syslog.LOG_SYSLOG, "app"))Write to any io.Writer:
log.SetWriter(writer.NewStreamWriter(os.Stderr))
// Or per-module
log.Module("app").Writer(writer.NewStreamWriter(os.Stderr))Fan out logs to multiple destinations with per-level routing:
stack := writer.NewStackWriter()
// Debug logs → stdout only
stack.PushWithLevels(writer.NewStdoutWriter(), level.Debug)
// Error + Emergency → syslog
stack.PushWithLevels(
writer.NewSyslogWriter("", "", syslog.LOG_ERR, "app"),
level.Error, level.Emergency,
)
// All levels → file (no level filter = all levels)
stack.PushWithLevels(writer.NewDefaultFileWriter("/var/log/app.log"))
log.SetWriter(stack)Filters let you intercept, modify, or suppress log entries before they are formatted and written. Filters are chainable — global filters execute before module-level filters.
Applied to all modules:
log.AddGlobalFilter(func(next filter.Filter) filter.Filter {
return func(evt event.Event) {
// Add a field to every log entry
evt.Fields.CustomFields["request_id"] = "abc-123"
// Call next(evt) to continue the chain
// Omitting this call suppresses the log entirely
next(evt)
}
})Applied to a specific module:
logger := log.Module("app.payment")
logger.AddFilter(func(next filter.Filter) filter.Filter {
return func(evt event.Event) {
// Suppress debug logs for this module
if evt.Level == level.Debug {
return
}
next(evt)
}
})WithStacktrace — automatically attach stack traces for specified log levels:
import "github.com/mylxsw/asteria/filter"
log.AddGlobalFilter(filter.WithStacktrace(level.Error, level.Critical, level.Emergency))EmergencyExit — terminate the process after logging an Emergency level message:
log.AddGlobalFilter(filter.EmergencyExit(1)) // exit code 1Attach fields to every log entry across all modules — useful for request IDs, trace IDs, etc.:
log.GlobalFields(func(c event.Fields) {
c.GlobalFields["hostname"] = "web-01"
c.GlobalFields["env"] = "production"
})// Reopen all log files (useful for log rotation with SIGHUP)
log.ReOpenAll()
// Close all loggers gracefully
log.CloseAll()
// Reopen/close a specific module
log.Module("app").ReOpen()
log.Module("app").Close()package main
import (
"context"
"fmt"
"time"
"github.com/mylxsw/asteria/filter"
"github.com/mylxsw/asteria/formatter"
"github.com/mylxsw/asteria/level"
"github.com/mylxsw/asteria/log"
"github.com/mylxsw/asteria/writer"
)
var logger = log.Module("app.main")
func main() {
// Use JSON formatter for production
log.All().LogFormatter(formatter.NewJSONFormatter())
// Rotating daily log files
fw := writer.NewDefaultRotatingFileWriter(context.TODO(), func(le level.Level, module string) string {
return fmt.Sprintf("/var/log/app.%s.log", time.Now().Format("20060102"))
})
log.All().LogWriter(fw)
// Set minimum log level to Info (suppress Debug)
log.All().LogLevel(level.Info)
// Attach stack traces to errors
log.AddGlobalFilter(filter.WithStacktrace(level.Error))
// Auto-exit on emergency
log.AddGlobalFilter(filter.EmergencyExit(1))
// Log with context
logger.WithFields(log.Fields{
"user_id": 123,
"action": "purchase",
}).Info("order placed successfully")
// Don't forget to close loggers on shutdown
defer log.CloseAll()
}asteria/
├── log/ Core API — Logger interface, module management, context logging
├── level/ 8 severity levels (Emergency → Debug)
├── formatter/ Output formatters (Text, JSON, JSONWithTime)
├── writer/ Output writers (Stdout, File, RotatingFile, Syslog, Stream, Stack)
├── filter/ Filter chain (WithStacktrace, EmergencyExit)
├── event/ Log event and fields data structures
├── color/ Terminal color utilities
└── misc/ Internal helpers (caller info, module name abbreviation)
MIT — Copyright (c) 2026 mylxsw
