Go developers often face a trade-off when it comes to logging: readable logs that are great for humans but lack structured data for queries, or structured logs that are great for machines but verbose and awkward to read in raw form.
With mtlog, you don't have to choose.
Why mtlog?
mtlog brings message template logging—popularized by Serilog in the .NET ecosystem—to Go. The result: logs that read like prose at 2 AM and contain rich structured properties for 9 AM production queries.
// Human-friendly and structured
log.Info("User {UserId} purchased {@Order} from {IP} in {Duration:F2}ms",
userId, order, ipAddress, duration)
Humans see:
User 123 purchased {Id:456 Total:99.95 Items:[...]} from 192.168.1.5 in 153ms
Machines see:
{"UserId":123,"Order":{"Id":456,"Total":99.95,"Items":[...]},"IP":"192.168.1.5","Duration":153}
Key Features
- Message templates with
{Property}
, OTEL-style{http.method}
names, capture hints (@
for destructuring,$
for stringification), and format specifiers. - Clear syntax for built-ins:
${Message}
,${Timestamp}
,${Level}
—no collisions with user properties. - Zero allocations for simple log events (≈17 ns/op).
- Adapters for slog and logr so you can integrate without rewriting your whole app.
- A full sink ecosystem: console (themes), rolling file, Seq (CLEF), Elasticsearch, Splunk, async, and durable buffering.
- Dynamic level control at runtime—locally or via Seq.
Built with Lessons Learned
mtlog's design is informed by years of structured logging experience in other ecosystems:
- OTEL dotted names (
{http.method}
,{service.name}
) treated as flat property names for compatibility. ${...}
output template syntax avoids{Message}
collisions and keeps future extensibility open.- Format specifiers that actually work—
{Duration:F2}
,{Price:F2}
,{Percent:P1}
—not just afterthoughts. - Clear capture semantics—
@
means capture structured data,$
means stringify.
Tooling From Day One
Logging isn't just about API design—it's about using it correctly, everywhere.
mtlog ships with the mtlog-analyzer, a static analysis tool that:
- Catches template/argument mismatches
- Flags duplicate or misnamed properties
- Suggests capture hints for complex types
- Warns about error logs without error values
- Detects dynamic templates that can't be analyzed
And it's not just CLI—real-time feedback is available via:
VS Code extension
Squiggly underlines & quick fixes
GoLand plugin
Real-time annotations
Neovim plugin
LSP integration & code actions
In all of them, you'll see squiggly underlines, quick-fixes, and even interactive help with wrong/right examples.
Output Templates Without Collisions
In most logging libraries, output templates share syntax with message templates, which can lead to conflicts. mtlog avoids this entirely:
// Built-ins are `${...}`
mtlog.WithConsoleTemplate("[${Timestamp:HH:mm:ss} ${Level:u3}] {SourceContext}: ${Message}")
${Message}
— the rendered message{SourceContext}
— a property from your event- No risk of accidentally overwriting your own data
Context That Flows
mtlog's LogContext lets you attach scoped properties to a context.Context
that automatically flow through log events:
ctx := mtlog.PushProperty(context.Background(), "RequestId", "abc-123")
logger := baseLogger.WithContext(ctx)
logger.Info("Processing request") // Includes RequestId=abc-123
Useful for request tracing, async workflows, and multi-tenant apps.
ForType — Strongly-Typed SourceContext
Avoid hard-coded strings for source categories:
serviceLogger := mtlog.ForType[UserService](logger)
serviceLogger.Info("User created")
// SourceContext = "UserService"
Cached for near-zero overhead after the first call.
Example: From Verbose to Clear
Before:
log.Info("user logged in", "user_id", 123, "ip", "192.168.1.5")
After (mtlog):
log.Info("User {UserId} logged in from {IP}", 123, "192.168.1.5")
Same structure in JSON output, but dramatically more readable in plain text.
Getting Started
go get github.com/willibrandon/mtlog
log := mtlog.New(mtlog.WithConsole())
log.Info("App started")
log.Info("User {UserId} purchased {@Order}", 42, order)
See the Quick Start guide and examples directory for more.
Try mtlog
mtlog is feature-complete and already being used in real projects.
We're in the final review phase for the public API before the 1.0 release — so now is the perfect time to try it, kick the tires, and help shape the final version.
Readable logs. Structured data. Static analysis. All in Go.