You've already forked agentic-coding-workflow
feat(logger): slog-based logger with JSON and text handlers
This commit is contained in:
@@ -0,0 +1,63 @@
|
|||||||
|
// Package logger 提供基于标准库 log/slog 的结构化日志器封装:
|
||||||
|
// 统一的 Format / Level 选项,以及按环境区分默认值的 FromEnv 便利函数。
|
||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Format 表示日志输出格式。
|
||||||
|
type Format string
|
||||||
|
|
||||||
|
// 支持的输出格式。
|
||||||
|
const (
|
||||||
|
FormatJSON Format = "json"
|
||||||
|
FormatText Format = "text"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Level 是 slog.Level 的类型别名,方便外部直接使用本包常量而无需引入 log/slog。
|
||||||
|
type Level = slog.Level
|
||||||
|
|
||||||
|
// 常用日志级别(与 slog 同名常量等价)。
|
||||||
|
const (
|
||||||
|
LevelDebug = slog.LevelDebug
|
||||||
|
LevelInfo = slog.LevelInfo
|
||||||
|
LevelWarn = slog.LevelWarn
|
||||||
|
LevelError = slog.LevelError
|
||||||
|
)
|
||||||
|
|
||||||
|
// Options 控制日志器的输出格式、最低级别与目标 Writer。
|
||||||
|
// Writer 留空时默认写入 os.Stdout。
|
||||||
|
type Options struct {
|
||||||
|
Format Format
|
||||||
|
Level Level
|
||||||
|
Writer io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// New 按 Options 构造一个 *slog.Logger。Format 非 FormatText 时一律使用 JSON handler。
|
||||||
|
func New(opt Options) *slog.Logger {
|
||||||
|
w := opt.Writer
|
||||||
|
if w == nil {
|
||||||
|
w = os.Stdout
|
||||||
|
}
|
||||||
|
hopts := &slog.HandlerOptions{Level: opt.Level, AddSource: false}
|
||||||
|
var h slog.Handler
|
||||||
|
switch opt.Format {
|
||||||
|
case FormatText:
|
||||||
|
h = slog.NewTextHandler(w, hopts)
|
||||||
|
default:
|
||||||
|
h = slog.NewJSONHandler(w, hopts)
|
||||||
|
}
|
||||||
|
return slog.New(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromEnv 根据环境名返回合适默认:
|
||||||
|
// development → text + debug;其他 → json + info。
|
||||||
|
func FromEnv(env string, w io.Writer) *slog.Logger {
|
||||||
|
if env == "development" {
|
||||||
|
return New(Options{Format: FormatText, Level: LevelDebug, Writer: w})
|
||||||
|
}
|
||||||
|
return New(Options{Format: FormatJSON, Level: LevelInfo, Writer: w})
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNew_JSONHandler_WritesStructured(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
log := New(Options{Format: FormatJSON, Level: LevelInfo, Writer: &buf})
|
||||||
|
log.Info("hello", "key", "value")
|
||||||
|
|
||||||
|
var got map[string]any
|
||||||
|
require.NoError(t, json.Unmarshal(buf.Bytes(), &got))
|
||||||
|
require.Equal(t, "hello", got["msg"])
|
||||||
|
require.Equal(t, "value", got["key"])
|
||||||
|
require.Equal(t, "INFO", got["level"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNew_TextHandler_HumanReadable(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
log := New(Options{Format: FormatText, Level: LevelDebug, Writer: &buf})
|
||||||
|
log.Debug("dev message", "x", 1)
|
||||||
|
require.Contains(t, buf.String(), "dev message")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNew_LevelFilter(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
log := New(Options{Format: FormatJSON, Level: LevelWarn, Writer: &buf})
|
||||||
|
log.Info("should be filtered")
|
||||||
|
require.Empty(t, strings.TrimSpace(buf.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFromEnv_DevelopmentUsesText(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
log := FromEnv("development", &buf)
|
||||||
|
log.Debug("dev", "k", "v")
|
||||||
|
out := buf.String()
|
||||||
|
require.Contains(t, out, "dev")
|
||||||
|
// text handler outputs key=value pairs (not JSON braces)
|
||||||
|
require.NotContains(t, out, `"msg":`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFromEnv_NonDevelopmentUsesJSON(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
log := FromEnv("production", &buf)
|
||||||
|
// Debug should be filtered (JSON default level is Info)
|
||||||
|
log.Debug("filtered")
|
||||||
|
require.Empty(t, strings.TrimSpace(buf.String()))
|
||||||
|
|
||||||
|
log.Info("prod")
|
||||||
|
var got map[string]any
|
||||||
|
require.NoError(t, json.Unmarshal(buf.Bytes(), &got))
|
||||||
|
require.Equal(t, "prod", got["msg"])
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user