Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
c108f8b880
|
|||
|
05c99f72ff
|
15
CHANGELOG.md
15
CHANGELOG.md
@@ -5,6 +5,21 @@ All notable changes to this module will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this module adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this module adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [1.0.0] — 2026-05-12
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `Options.Writer io.Writer` — configurable output destination; `nil` defaults
|
||||||
|
to `os.Stdout` (backward-compatible). Accepts any `io.Writer` implementation:
|
||||||
|
`*os.File`, `bytes.Buffer`, `io.MultiWriter`, custom sinks, etc.
|
||||||
|
|
||||||
|
### Unchanged
|
||||||
|
|
||||||
|
All existing API (`Logger`, `New`, `WithRequestID`, `GetRequestID`, `WithField`,
|
||||||
|
`WithFields`) is API-compatible with v0.9.0.
|
||||||
|
|
||||||
|
[1.0.0]: https://code.nochebuena.dev/go/logz/releases/tag/v1.0.0
|
||||||
|
|
||||||
## [0.9.0] - 2026-03-18
|
## [0.9.0] - 2026-03-18
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -1,3 +1,3 @@
|
|||||||
module code.nochebuena.dev/go/logz
|
module code.nochebuena.dev/go/logz
|
||||||
|
|
||||||
go 1.25
|
go 1.26
|
||||||
|
|||||||
17
logz.go
17
logz.go
@@ -3,6 +3,7 @@ package logz
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
@@ -20,7 +21,7 @@ type errorWithContext interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Options configures a Logger instance.
|
// Options configures a Logger instance.
|
||||||
// The zero value is valid: INFO level, text output, no static args.
|
// The zero value is valid: INFO level, text output, os.Stdout, no static args.
|
||||||
type Options struct {
|
type Options struct {
|
||||||
// Level is the minimum log level. Default: slog.LevelInfo (zero value).
|
// Level is the minimum log level. Default: slog.LevelInfo (zero value).
|
||||||
Level slog.Level
|
Level slog.Level
|
||||||
@@ -28,6 +29,9 @@ type Options struct {
|
|||||||
JSON bool
|
JSON bool
|
||||||
// StaticArgs are key-value pairs attached to every log record.
|
// StaticArgs are key-value pairs attached to every log record.
|
||||||
StaticArgs []any
|
StaticArgs []any
|
||||||
|
// Writer is the output destination. Defaults to os.Stdout when nil.
|
||||||
|
// Accepts any io.Writer: *os.File, bytes.Buffer, io.MultiWriter, etc.
|
||||||
|
Writer io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logger is the interface for structured logging.
|
// Logger is the interface for structured logging.
|
||||||
@@ -56,16 +60,17 @@ type slogLogger struct {
|
|||||||
|
|
||||||
// New returns a new Logger configured by opts.
|
// New returns a new Logger configured by opts.
|
||||||
func New(opts Options) Logger {
|
func New(opts Options) Logger {
|
||||||
handlerOpts := &slog.HandlerOptions{
|
w := opts.Writer
|
||||||
Level: opts.Level,
|
if w == nil {
|
||||||
AddSource: false,
|
w = os.Stdout
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handlerOpts := &slog.HandlerOptions{Level: opts.Level}
|
||||||
var handler slog.Handler
|
var handler slog.Handler
|
||||||
if opts.JSON {
|
if opts.JSON {
|
||||||
handler = slog.NewJSONHandler(os.Stdout, handlerOpts)
|
handler = slog.NewJSONHandler(w, handlerOpts)
|
||||||
} else {
|
} else {
|
||||||
handler = slog.NewTextHandler(os.Stdout, handlerOpts)
|
handler = slog.NewTextHandler(w, handlerOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
base := slog.New(handler)
|
base := slog.New(handler)
|
||||||
|
|||||||
27
logz_test.go
27
logz_test.go
@@ -36,9 +36,8 @@ func (e *errFull) ErrorContext() map[string]any { return e.fields }
|
|||||||
// Helper: logger that writes to a buffer for inspection
|
// Helper: logger that writes to a buffer for inspection
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
|
|
||||||
func newTestLogger(buf *bytes.Buffer, level slog.Level) *slogLogger {
|
func newTestLogger(buf *bytes.Buffer, level slog.Level) Logger {
|
||||||
handler := slog.NewJSONHandler(buf, &slog.HandlerOptions{Level: level})
|
return New(Options{Writer: buf, Level: level, JSON: true})
|
||||||
return &slogLogger{logger: slog.New(handler)}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
@@ -56,10 +55,8 @@ func TestNew_ZeroOptions(t *testing.T) {
|
|||||||
|
|
||||||
func TestNew_JSONFormat(t *testing.T) {
|
func TestNew_JSONFormat(t *testing.T) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
sl := &slogLogger{
|
l := New(Options{JSON: true, Writer: &buf})
|
||||||
logger: slog.New(slog.NewJSONHandler(&buf, &slog.HandlerOptions{Level: slog.LevelInfo})),
|
l.Info("json test", "key", "value")
|
||||||
}
|
|
||||||
sl.Info("json test", "key", "value")
|
|
||||||
|
|
||||||
var out map[string]any
|
var out map[string]any
|
||||||
if err := json.Unmarshal(buf.Bytes(), &out); err != nil {
|
if err := json.Unmarshal(buf.Bytes(), &out); err != nil {
|
||||||
@@ -70,13 +67,19 @@ func TestNew_JSONFormat(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNew_Writer(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
l := New(Options{Writer: &buf})
|
||||||
|
l.Info("writer test")
|
||||||
|
if !strings.Contains(buf.String(), "writer test") {
|
||||||
|
t.Errorf("output not written to provided writer: %s", buf.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestNew_StaticArgs(t *testing.T) {
|
func TestNew_StaticArgs(t *testing.T) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
handler := slog.NewJSONHandler(&buf, &slog.HandlerOptions{Level: slog.LevelInfo})
|
l := New(Options{Writer: &buf, JSON: true, StaticArgs: []any{"service", "test-svc"}})
|
||||||
base := slog.New(handler).With("service", "test-svc")
|
l.Info("static args test")
|
||||||
sl := &slogLogger{logger: base}
|
|
||||||
|
|
||||||
sl.Info("static args test")
|
|
||||||
|
|
||||||
if !strings.Contains(buf.String(), "test-svc") {
|
if !strings.Contains(buf.String(), "test-svc") {
|
||||||
t.Errorf("static arg not found in output: %s", buf.String())
|
t.Errorf("static arg not found in output: %s", buf.String())
|
||||||
|
|||||||
Reference in New Issue
Block a user