14 KiB
14 KiB
starlog 使用指南
本文档覆盖从接入到生产落地的常见场景。
- 模块:
b612.me/starlog - Go 版本:
1.16+
1. 快速开始
package main
import (
"os"
"b612.me/starlog"
)
func main() {
log := starlog.NewStarlog(os.Stdout)
log.SetName("demo")
log.SetShowStd(true)
log.SetColorMode(starlog.ColorModeLevelOnly)
log.Info("service start")
log.WithField("user_id", 42).Info("login ok")
}
2. 推荐初始化方式
2.1 开发环境
log := starlog.NewDevelopment(os.Stdout)
log.SetName("my-service")
log.SetShowStd(true)
特点:
- 默认
Debug级别 - 颜色模式:
ColorModeLevelOnly - 显示源码位置
2.2 生产环境
log := starlog.NewProduction(nil)
log.SetName("my-service")
if err := starlog.SetLogFile("./logs/app.log", log, true); err != nil {
panic(err)
}
特点:
- 默认
Info级别 - 默认
JSONFormatter - 默认不直出控制台
2.3 对现有 logger 套用预设
log.ApplyProductionConfig()
// 或
log.ApplyDevelopmentConfig()
3. 输出 API 与换行规则
3.1 输出函数
Info/Error/...:fmt.Sprint语义Infof/Errorf/...:fmt.Sprintf语义Infoln/Errorln/...:fmt.Sprintln语义
默认不会自动补 \n(ln 系列除外)。
3.2 自动补换行
log.SetAutoAppendNewline(true)
行为:
- 末尾无
\n:自动补一个 - 末尾有
\n:保持不变
4. 结构化日志
4.1 字段与错误
log.WithField("order_id", "O-1001").Info("create order")
log.WithFields(starlog.Fields{
"trace_id": "t-001",
"module": "payment",
}).WithError(err).Error("charge failed")
4.2 Context 注入
log.SetContextFieldExtractor(func(ctx context.Context) starlog.Fields {
traceID, _ := ctx.Value("trace_id").(string)
if traceID == "" {
return nil
}
return starlog.Fields{"trace_id": traceID}
})
log.WithContext(ctx).Info("request done")
log.InfoContext(ctx, "request done")
5. 级别过滤
log.SetLevel(starlog.LvInfo)
if log.IsLevelEnabled(starlog.LvDebug) {
log.Debug("expensive debug")
}
lv, err := starlog.ParseLevel("warn") // LvWarning
_ = lv
_ = err
SetStdErrLevel 只控制 stdout/stderr 分流阈值,不是日志过滤阈值。
6. 显示、颜色与格式化
6.1 颜色模式
log.SetColorMode(starlog.ColorModeOff)
log.SetColorMode(starlog.ColorModeFullLine)
log.SetColorMode(starlog.ColorModeLevelOnly)
6.2 关键词和字段着色
log.SetKeywordColor("timeout", []starlog.Attr{starlog.FgRed, starlog.Bold})
log.SetShowFieldColor(true)
log.SetFieldKeyColor([]starlog.Attr{starlog.FgHiBlue})
log.SetFieldTypeColor(starlog.FieldTypeNumber, []starlog.Attr{starlog.FgYellow})
log.SetFieldValueColor("user_id", []starlog.Attr{starlog.FgCyan})
6.3 关键词预设(Moba 风格)
log.ApplyKeywordPreset(starlog.KeywordPresetMobaLite) // 覆盖现有关键词映射
// 或
log.MergeKeywordPreset(starlog.KeywordPresetMobaFull) // 在现有映射上合并
// 可选匹配模式(默认关闭,兼容旧行为)
log.SetKeywordMatchOptions(starlog.KeywordMatchOptions{
IgnoreCase: true, // 忽略大小写
WholeWord: true, // 仅匹配完整单词
})
说明:
MobaLite:常用词高亮(如error/warn/true/false/success)MobaFull:在 Lite 基础上增加更多运行态词汇- 预设后仍可继续
SetKeywordColor(...)覆盖单个关键词颜色
6.4 Formatter
log.SetFormatter(starlog.NewTextFormatter())
log.SetFormatter(starlog.NewJSONFormatter())
自定义 formatter:
type Formatter interface { Format(*Entry) ([]byte, error) }
7. 输出管道(Writer / Sink / MultiSink)
7.1 Writer
log.SetWriter(os.Stdout)
7.2 Sink
type Sink interface {
Write([]byte) error
Close() error
}
log.SetSink(mySink)
7.3 MultiSink
multi := starlog.NewMultiSink(fileSink, networkSink)
multi.SetContinueOnError(true)
log.SetSink(multi)
stats := multi.GetStats()
_ = stats
8. 按级别分流(RouteHandler)
示例:info+notice -> brief.log,error+ -> err.log。
brief, _ := os.OpenFile("./logs/brief.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
errf, _ := os.OpenFile("./logs/err.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
route := starlog.NewRouteHandler(
starlog.Route{
Name: "brief",
Match: starlog.MatchLevels(starlog.LvInfo, starlog.LvNotice),
Formatter: starlog.NewTextFormatter(),
Sink: starlog.NewWriterSink(brief),
},
starlog.Route{
Name: "error",
Match: starlog.MatchAtLeast(starlog.LvError),
Formatter: starlog.NewJSONFormatter(),
Sink: starlog.NewWriterSink(errf),
},
)
log.SetEntryHandler(route)
如果要“主日志 + 分流日志”同时保留,用 ChainHandler 组合 handler。
9. 轮转与归档
9.1 RotatePolicy 语义
type RotatePolicy interface {
ShouldRotate(starlog.FileInfo, *starlog.Entry) bool
NextPath(string, time.Time) string
}
// 可选扩展接口
type RotateArchivePathProvider interface {
ArchivePath(string, time.Time) string
}
语义说明:
ShouldRotate:判定是否触发轮转NextPath:返回“被切走旧日志”的归档路径- 若策略实现了
ArchivePath,框架优先使用ArchivePath - 若
ArchivePath返回空字符串,回退到NextPath
9.2 主日志轮转(推荐入口)
if err := starlog.StartRotatePolicy(log, policy, 1); err != nil {
panic(err)
}
内置模板:
_ = starlog.StartRotateByTime(log, 24*time.Hour, 10)
_ = starlog.StartRotateBySize(log, 200*1024*1024, 5)
_ = starlog.StartRotateByTimeSize(log, 24*time.Hour, 200*1024*1024, 5)
9.3 管理增强(保留、压缩)
opts := starlog.RotateManageOptions{
MaxBackups: 7,
MaxAge: 7 * 24 * time.Hour,
Compress: true,
Pattern: "20060102-150405",
}
if err := starlog.StartManagedRotatePolicy(log, policy, 1, opts); err != nil {
panic(err)
}
模板增强版:
_ = starlog.StartManagedRotateByTime(log, 24*time.Hour, 10, opts)
_ = starlog.StartManagedRotateBySize(log, 200*1024*1024, 5, opts)
_ = starlog.StartManagedRotateByTimeSize(log, 24*time.Hour, 200*1024*1024, 5, opts)
9.4 RouteHandler 分流文件轮转
分流 sink 可直接使用轮转 sink:
debugSink, err := starlog.NewManagedRotateByTimeSink(
"./logs/debug.log",
true,
24*time.Hour,
30*time.Second,
starlog.RotateManageOptions{MaxBackups: 30, Compress: true, Pattern: "20060102"},
)
if err != nil {
panic(err)
}
route := starlog.NewRouteHandler(
starlog.Route{
Name: "debug-info",
Match: starlog.MatchLevels(starlog.LvDebug, starlog.LvInfo),
Formatter: starlog.NewTextFormatter(),
Sink: debugSink,
},
)
log.SetEntryHandler(route)
9.5 自定义轮转策略,同时应用到两种文件
type HourOrSizePolicy struct {
HourInterval time.Duration
MaxBytes int64
}
func (p HourOrSizePolicy) ShouldRotate(fi starlog.FileInfo, _ *starlog.Entry) bool {
if p.MaxBytes > 0 && fi.Size >= p.MaxBytes {
return true
}
if p.HourInterval > 0 && fi.ModTime.Add(p.HourInterval).Before(time.Now()) {
return true
}
return false
}
func (p HourOrSizePolicy) NextPath(path string, now time.Time) string {
ext := filepath.Ext(path)
base := strings.TrimSuffix(path, ext)
return base + "." + now.Format("20060102-150405") + ext
}
应用到主日志:
mainLog := starlog.NewProduction(nil)
_ = starlog.SetLogFile("./logs/app.log", mainLog, true)
policy := HourOrSizePolicy{HourInterval: 6 * time.Hour, MaxBytes: 300 * 1024 * 1024}
_ = starlog.StartManagedRotatePolicy(mainLog, policy, 1, starlog.RotateManageOptions{
MaxBackups: 20,
MaxAge: 14 * 24 * time.Hour,
Compress: true,
Pattern: "20060102-150405",
})
应用到分流文件:
debugSink, _ := starlog.NewManagedRotatePolicySink(
"./logs/debug.log",
true,
policy,
30*time.Second,
starlog.RotateManageOptions{
MaxBackups: 30,
MaxAge: 30 * 24 * time.Hour,
Compress: true,
Pattern: "20060102-150405",
},
)
route := starlog.NewRouteHandler(
starlog.Route{
Name: "debug-info",
Match: starlog.MatchLevels(starlog.LvDebug, starlog.LvInfo),
Formatter: starlog.NewTextFormatter(),
Sink: debugSink,
},
)
mainLog.SetEntryHandler(route)
9.6 兼容入口
StartArchive 仍可使用,但新代码建议优先 StartRotatePolicy / StartManagedRotatePolicy。
10. 异步、回调与 pending
log.SetHandler(func(data starlog.LogData) {
// 异步显示回调
})
log.SetAsyncFallbackToSync(true)
log.SetAsyncHandlerTimeout(100 * time.Millisecond)
log.SetEntryHandler(myHandler)
log.SetEntryHandlerTimeout(200 * time.Millisecond)
log.SetPendingWriteLimit(1024)
log.SetPendingDropPolicy(starlog.PendingDropOldest)
可观测项:
GetAsyncDropCount()GetPendingStats()
11. 高频防爆(去重 / 采样 / 限流)
主链路顺序:去重 -> 采样 -> 限流。
log.SetDedupConfig(starlog.DedupConfig{
Enable: true,
Levels: []int{starlog.LvInfo, starlog.LvNotice},
Window: 2 * time.Second,
Scope: starlog.DedupScopeByKey,
})
log.SetSamplingConfig(starlog.SamplingConfig{
Enable: true,
Levels: []int{starlog.LvInfo},
Rate: 0.2,
Scope: starlog.SamplingScopeByKey,
})
log.SetRateLimitConfig(starlog.RateLimitConfig{
Enable: true,
Levels: []int{starlog.LvInfo},
Rate: 50,
Burst: 100,
Scope: starlog.RateLimitScopeByKey,
})
12. 脱敏
log.AddRedactRule(starlog.NewSensitiveFieldRule("[MASK]", "password", "token"))
log.AddRedactRule(starlog.NewMessageRegexRule(regexp.MustCompile(`\b1\d{10}\b`), "[PHONE]"))
log.SetRedactFailMode(starlog.RedactFailMaskAll)
失败策略:
RedactFailMaskAll(推荐)RedactFailOpenRedactFailDrop
13. 指标观测
snapshot := log.GetMetricsSnapshot()
_ = snapshot
常用统计:
GetPendingStats()GetSamplingStats()GetDedupStats()GetRateLimitStats()GetWriteErrorCount()GetAsyncDropCount()
14. 生命周期与退出
语义:
Flush():刷写 pendingSync():Flush + 底层 Sync()(若支持)Close():关闭资源,不等待异步队列 drainShutdown(ctx):等待异步 drain,再关闭资源
推荐:
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
defer log.Shutdown(ctx)
全局 logger 可使用:starlog.Shutdown(ctx)。
15. 标准库桥接
stdLogger := log.AsStdlibLoggerWithOptions(starlog.LvInfo,
starlog.WithStdlibPrefix("http "),
starlog.WithStdlibFlags(0),
starlog.WithStdlibTrimNewline(true),
)
stdLogger.Println("server started")
也可以桥接为 io.Writer:AsWriter(level) / AsWriterWithOptions(...)。
16. 测试工具
16.1 Observer
obs := starlog.NewObserverWithLimit(200)
log.SetEntryHandler(obs)
entries := obs.Entries()
_ = entries
16.2 TestHook
hook := starlog.NewTestHook(log)
defer hook.Close()
log.Info("hello")
if hook.Count() == 0 {
panic("expect log")
}
17. 配置快照 API(并发配置推荐)
log.UpdateConfig(func(cfg *starlog.Config) {
cfg.Level = starlog.LvInfo
cfg.ShowColor = false
cfg.AutoAppendNewline = true
cfg.Sampling = starlog.SamplingConfig{Enable: true, Rate: 0.3}
cfg.Dedup = starlog.DedupConfig{Enable: true, Window: time.Second}
})
配套:
GetConfig()ApplyConfig(cfg)DefaultConfig()
18. 生产最小模板
func NewProdLogger(path string) *starlog.StarLogger {
log := starlog.NewProduction(nil)
log.SetName("svc")
log.SetAutoAppendNewline(true)
if err := starlog.SetLogFile(path, log, true); err != nil {
panic(err)
}
_ = starlog.StartManagedRotateBySize(log, 200*1024*1024, 5, starlog.RotateManageOptions{
MaxBackups: 14,
MaxAge: 14 * 24 * time.Hour,
Compress: true,
Pattern: "20060102-150405",
})
return log
}
19. 完整实战示例(主日志 + 协程分隔 + 双文件轮转)
覆盖目标:
- 使用
starlog - 协程中使用新
LOG分隔符(NewFlag()自动随机) - 设置主日志文件
- 主日志使用压缩轮转
debug/info额外保存到debug.logdebug.log使用独立轮转策略
package main
import (
"context"
"os"
"path/filepath"
"sync"
"time"
"b612.me/starlog"
)
func main() {
_ = os.MkdirAll("./logs", 0755)
mainLog := starlog.NewProduction(nil)
mainLog.SetName("app-main")
mainLog.SetAutoAppendNewline(true)
mainPath := filepath.Clean("./logs/app.log")
if err := starlog.SetLogFile(mainPath, mainLog, true); err != nil {
panic(err)
}
if err := starlog.StartManagedRotateBySize(mainLog, 200*1024*1024, 5, starlog.RotateManageOptions{
MaxBackups: 14,
MaxAge: 14 * 24 * time.Hour,
Compress: true,
Pattern: "20060102-150405",
}); err != nil {
panic(err)
}
debugPath := filepath.Clean("./logs/debug.log")
debugSink, err := starlog.NewManagedRotateByTimeSink(debugPath, true, 24*time.Hour, 30*time.Second, starlog.RotateManageOptions{
MaxBackups: 30,
MaxAge: 30 * 24 * time.Hour,
Compress: true,
Pattern: "20060102",
})
if err != nil {
panic(err)
}
route := starlog.NewRouteHandler(
starlog.Route{
Name: "debug-info-to-debug-log",
Match: starlog.MatchLevels(starlog.LvDebug, starlog.LvInfo),
Formatter: starlog.NewTextFormatter(),
Sink: debugSink,
},
)
mainLog.SetEntryHandler(route)
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go func(worker int) {
defer wg.Done()
workerLog := mainLog.NewFlag()
workerLog.WithField("worker", worker).Debug("worker start")
workerLog.WithField("worker", worker).Info("worker running")
if worker == 1 {
workerLog.WithField("worker", worker).Error("worker failed")
}
}(i)
}
wg.Wait()
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
_ = mainLog.Shutdown(ctx)
}
20. 常见问题
Q1:Infof/Errorf 需要手动加 \n 吗?
默认需要。若开启 SetAutoAppendNewline(true),末尾无 \n 会自动补齐。
Q2:异步模式会丢日志吗?
- 显示回调链路:可能因超时或队列满发生丢弃,可通过计数观测
- writer/sink 主写链路:只要未被过滤(级别/去重/采样/限流)且写入正常,会落盘
Q3:新项目应该选 Archive 还是 RotatePolicy?
优先 RotatePolicy;Archive 作为兼容路径保留。