Files
loggerx/options.go
T
2026-09-14 00:14:30 +08:00

326 lines
8.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package loggerx
import (
"compress/gzip"
"io"
"os"
"time"
)
type loggerOption struct {
prefix string // 日志前缀
format Format // json / text
dir string // 文件目录
isGinLog bool
isGid bool
isPrintFile bool
asGlobalLog bool // 是否接管全局 stdlib log(默认 false
writeType writeType // 是否异步罗盘
traceField string // trace字段
errorToInfo bool // 错误日志是否写入info日志
days int // 日志保存天数
drivers []io.Writer // 文件落盘驱动器
fileSplit FileSplit // 文件切割规则
sizeSplit int // 根据文件大小切割(字节,<=0 不切割)
compress bool // 归档文件是否压缩为 .gz
compressLvl int // gzip 压缩级别
flushEvery time.Duration // 定时刷盘间隔,0 表示不开启
minLevel Level // 最低输出级别
onError func(error) // 内部错误回调
timeZone *time.Location // 时区
escapeHTML bool
expandData map[string]string // 扩展字段
}
type writeType uint8
const (
// 0.默认(同步) 1.指定同步 2.指定异步
writeTypeDefault writeType = iota
writeTypeSync
writeTypeAsync
)
func defaultOptions() loggerOption {
return loggerOption{
isGinLog: false, // 默认不接管 gin 写手:非 Gin 服务不该被改全局状态
isGid: true,
isPrintFile: true,
writeType: writeTypeDefault, // 默认同步
format: FormatJSON,
dir: "./log",
traceField: "trace_id",
days: 7,
fileSplit: FileSplitTimeE,
compress: true,
compressLvl: gzip.BestSpeed,
timeZone: time.Local,
escapeHTML: true,
expandData: make(map[string]string),
}
}
type Option func(*loggerOption)
// trace字段
func SetTraceField(traceField string) Option {
return func(o *loggerOption) {
o.traceField = traceField
}
}
// 1.info 2.error 3.debug 4.warn 5.fatal
// 附加字段
func SetExpandData(key string, value string) Option {
return func(o *loggerOption) {
if o.expandData == nil {
o.expandData = make(map[string]string)
}
o.expandData[key] = value
}
}
// 是否异步写入
func SetWriteAsync() Option {
return func(o *loggerOption) {
o.writeType = writeTypeAsync
}
}
// 打印到控制台
func SetToConsole() Option {
return func(o *loggerOption) {
o.drivers = append(o.drivers, os.Stdout)
}
}
// 错误日志是否写入info日志
func SetErrorToInfo() Option {
return func(o *loggerOption) {
o.errorToInfo = true
}
}
// 日志前缀
// 会加在每行日志最前面(JSON 与 text 都生效),便于多实例共用一个目录时区分来源
func SetPrefix(prefix string) Option {
return func(o *loggerOption) {
o.prefix = prefix
}
}
// 输出格式
type Format string
const (
// FormatJSON 每行一条 JSON(默认,推荐给采集端)
FormatJSON Format = "json"
// FormatText 每行一条紧凑文本,适合人直接看
FormatText Format = "text"
)
// 日志格式(默认 FormatJSON
// 传非法值时保持原值,不会静默切到别的格式
func SetFormat(format Format) Option {
return func(o *loggerOption) {
if format == FormatJSON || format == FormatText {
o.format = format
}
}
}
// 设置是否打印到文件
func SetPrintFile(print bool) Option {
return func(o *loggerOption) {
o.isPrintFile = print
}
}
// 是否保存gin的日志
func SetGinLog(open bool) Option {
return func(o *loggerOption) {
o.isGinLog = open
}
}
// 日志级别。级别低于设定值的日志会被直接丢弃,不落盘
type Level uint8
const (
LevelDebug Level = iota
LevelInfo
LevelWarn
LevelError
// LevelOff 关闭所有级别(连 error 也不写)
LevelOff
)
// String 便于打印
func (lv Level) String() string {
switch lv {
case LevelDebug:
return "debug"
case LevelInfo:
return "info"
case LevelWarn:
return "warn"
case LevelError:
return "error"
case LevelOff:
return "off"
default:
return "unknown"
}
}
// SetMinLevel 设置最低输出级别(默认 LevelDebug,即全部输出)
//
// 生产环境通常设成 LevelInfo:这样代码里的 Debug 调用不会白算一遍
// JSON、也不会落盘占空间
func SetMinLevel(lv Level) Option {
return func(o *loggerOption) {
o.minLevel = lv
}
}
// SetErrorHandler 注册内部错误处理回调(默认 nil)
//
// 用于把「日志库自身的故障」暴露给运维:磁盘满、句柄失效、归档压缩失败、
// 定时刷盘失败等。不注册时这些错误只体现在返回值里,而调用方通常忽略返回值,
// 问题就完全不可见了。
//
// 建议接到告警通道或一个「不会失败」的输出(例如 stderr):
//
// loggerx.NewLogger(ctx, loggerx.SetErrorHandler(func(err error) {
// fmt.Fprintln(os.Stderr, "loggerx:", err)
// }))
//
// 回调在写日志的调用栈上同步执行,务必保持轻量,
// 且不要在里面再调用本实例的日志方法(可能递归)
func SetErrorHandler(fn func(error)) Option {
return func(o *loggerOption) {
o.onError = fn
}
}
// 是否把全局标准库 log 的输出接管到本实例(默认 false,不接管)
//
// 默认不接管是刻意的:日志库在 import / NewLogger 时静默劫持宿主全局 log
// 会带来两个生产事故场景 —— 多个实例时后建的把先建的顶掉;
// Close 之后全局 log 仍指向已关闭实例,之后的日志被静默丢弃。
// 确需接管(例如老代码大量使用 log.Printf)时再显式打开。
func SetAsGlobalLog() Option {
return func(o *loggerOption) {
o.asGlobalLog = true
}
}
// 文件路径
func SetDir(dir string) Option {
return func(o *loggerOption) {
if dir != "" {
o.dir = dir
}
}
}
// 保存goroutine的ID信息
func SetGID(open bool) Option {
return func(o *loggerOption) {
o.isGid = open
}
}
// 日志保存天数
func SetDays(days int) Option {
return func(o *loggerOption) {
o.days = days
}
}
// 设置时区
func SetTimeZone(loc *time.Location) Option {
return func(o *loggerOption) {
o.timeZone = loc
}
}
// 文件额外的驱动
func SetExtraDriver(ds ...io.Writer) Option {
return func(o *loggerOption) {
for _, d := range ds {
if d != nil {
o.drivers = append(o.drivers, d)
}
}
}
}
// 文件切割规则
// 1.文件大小
// 2.时间A(年/月/日/时)
// 3.时间B(年/月-日)
// 4.时间C(年-月-日-时)
// 5.时间D(年-月-日)
func SetFileSplit(split FileSplit) Option {
return func(o *loggerOption) {
o.fileSplit = split
}
}
type FileSplit string
const (
FileSplitNone FileSplit = "none" // 不切割
FileSplitTimeA FileSplit = "timeA" // (年/月/日/时)
FileSplitTimeB FileSplit = "timeB" // (年/月/日)
FileSplitTimeC FileSplit = "timeC" // (年/月-日)
FileSplitTimeD FileSplit = "timeD" // (年-月-日-时)
FileSplitTimeE FileSplit = "timeE" // (年-月-日)
)
// 根据文件大小切割
// m 为单个文件的大小上限(字节);<=0 表示不按大小切割
// 超过上限时当前文件会被改名归档(并可选压缩),随后写入新文件
func SetSizeSplit(m int) Option {
return func(o *loggerOption) {
o.sizeSplit = m
}
}
// 归档文件是否压缩成 .gz(默认开启)
// 压缩在后台协程完成,不阻塞写入
func SetCompress(open bool) Option {
return func(o *loggerOption) {
o.compress = open
}
}
// 压缩级别 1~9,数字越大压缩率越高、CPU 开销越大,默认 gzip.BestSpeed
func SetCompressLevel(level int) Option {
return func(o *loggerOption) {
if level >= gzip.HuffmanOnly && level <= gzip.BestCompression {
o.compressLvl = level
}
}
}
// 定时刷盘间隔(默认 0 = 不开启)
// 开启后会按间隔把内存缓冲刷到磁盘,把「进程崩溃时可能丢失的数据量」
// 从「最多 32KB 缓冲」压缩到「一个间隔内产生的日志量」
func SetFlushInterval(d time.Duration) Option {
return func(o *loggerOption) {
if d > 0 {
o.flushEvery = d
}
}
}
func SetEscapeHTML(b bool) Option {
return func(o *loggerOption) {
o.escapeHTML = b
}
}