ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

日志模块(core/logs)

2026/9/13 23:37:28 拓冰建站 浏览量
日志模块(core/logs) 1. 核心结构(log.go:111-130)type BeeLogger struct { lock sync.Mutex // 保护 outputs init bool // 首次 SetLogger 时清默认 console enableFuncCallDepth bool asynchronous bool logWithNonBlocking bool wg sync.WaitGroup // Flush/Close 与消费协程同步 level int // 全局级别闸门 loggerFuncCallDepth int // 默认 3 prefix string msgChanLen int64 // 默认 1000 msgChan chan *LogMsg // 异步通道 closeChan/flushChan chan bool outputs []*nameLogger // type nameLogger struct { Logger; name string } globalFormatter string }级别常量(log.go:45-58):数字越大级别越低(LevelEmergency0 … LevelDebug7);闸门判断if LevelX bl.level { return }。Logger接口(log.go:83-89)——所有输出器的契约:type Logger interface { Init(config string) error WriteMsg(lm *LogMsg) error Destroy(); Flush(); SetFormatter(f LogFormatter) }注册表(log.go:91-107):adapters map[string]newLoggerFuncRegister(重名 panic);各适配器 init 自注册(console/file/multifile/smtp/conn/es/slack/jianliao/alils)。2. 一条日志的一生:writeMsg(log.go:287-344)以logs.Error(x%v, v)(471-475)为例:Error(msg, args) ├─ 闸门:LevelError(3) bl.level? → return ├─ 构造 LogMsg{Level, Msg, When, Args}(不格式化,留给输出器) └─ writeMsg(lm) ├─ a. !init → 懒加载默认 console(288-292) ├─ b. runtime.Caller(loggerFuncCallDepth) 填 FilePath/LineNumber(300-310) │ (性能点:Caller 很贵;EnableFuncCallDepth(false) 可关) ├─ c. levelLoggerImpl(-1) 归一化为 LevelEmergency(313) │ (访问日志等无视级别的输出用 -1 特权通过) ├─ d. 异步:logMsgPool.Get() 复用 LogMsg → msgChan │ select-default:logWithNonBlocking 时满则丢弃(318-340) └─ e. 同步:writeToLoggers(lm) 遍历 outputs.WriteMsg(342)异步消费协程 startLogger(381-408)for { select { case bm : -bl.msgChan: → writeToLoggers Put 回池 case -bl.closeChan: → flush 全部 Destroy return case -bl.flushChan: → flush } }Flush()(593):发 flushChan wg.Wait();Close()(604)再关两个 chan。退出必须 Close,否则丢尾部日志。LogMsg 对象池(log_msg.go Async 初始化)logMsgPool *sync.Pool在 Async() 时创建——高频日志下省掉每条一次分配。3. 适配器:FileLogWriter(file.go)——本模块精华3.1 结构与配置(33-94)配置即 JSON(Init 里json.Unmarshal(config, w),122-145):Filename/Rotate/Daily/Hourly/MaxLines(默认千万)/MaxSize(默认 256MB)/MaxDays(7)/MaxHours(168)/MaxFiles(999)/Level/Perm 等。3.2 Init 流程Init:解析 JSON → 拆文件名/后缀 → startLogger ├─ createLogFile(216-237):MkdirAll OpenFile(APPEND|CREATE) Chmod(防 umask) └─ initFd(239-264): Stat 恢复 maxSizeCurSize 已有内容且 MaxLines0 → lines() 数 \n 恢复行数(295-320,32KB buffer) Hourly/Daily → go hourlyRotate/dailyRotate 定时协程3.3 WriteMsg(173-214)与轮转判定if lm.Level w.Level { return } // 输出器级闸门(第二道) // RLock 快速判定 needRotateDaily/Hourly(160-171:行数满/大小满/跨天) if need { 升级写锁 → 二次检查 → doRotate // 双重检查锁! } 写入;maxLinesCurLines;maxSizeCurSize len双重检查锁定(DCL):读锁判脏、写锁复核,避免每次写都抢写锁——教科书级并发模式,在 174-204 行落地。3.4 doRotate(324-391)目标名:Daily→2006-01-02;Hourly→2006-0215 MaxLines/MaxSize 任一启用:找 xx.日期.NNN.log 的空闲序号(从 maxFilesCurFiles1 递增) 否则:xx.openTime日期.num.log Close → os.Rename → Chmod(0440) defer:任何错误也要 startLogger 重开 fd go deleteOldLog()(393-426:Walk 按 MaxDays/MaxHours 清理,回调带 recover 防御)3.5 multiFileLogWriter(multifile.go)writers [9]*fileLogWriter(下标级别,末位全量)Separate [error,debug];WriteMsg(102-114):全量必写 精确等于该级别的文件再写一份(不是 )。注册名 multifile。4. 格式化(formatter.go / log_msg.go)LogFormatter{Format(*LogMsg) string};RegisterFormatter/GetFormatter全局表PatternLogFormatter:模式串%F %L %T %M…默认LogMsg.OldStyleFormat():[prefix][I] file:line msglogger.go 不含注册逻辑,是底层工具:logWriter(加锁 writeln)、formatTimeHeader(手写零分配时间头,预生成数字表 y1~ns1,59-93)、ANSI 颜色(ColorByStatus/ColorByMethod,130-152——dev 模式彩色请求日志的来源)。5. 访问日志(access_log.go)type AccessLogRecord struct { RemoteAddr, RequestMethod, Request, Host, Status, BodyBytesSent, ElapsedTime, HTTPReferrer, HTTPUserAgent ... } func AccessLog(r AccessLogRecord, format string) // 直接走全局 beeLogger.writeMsg两格式:APACHE_FORMAT / JSON_FORMAT(默认,json.Encoder SetEscapeHTML(false))。web 侧 server.go LogAccess(860-893)在 Admin 段调用,DefaultAccessLogFilter跳过 favicon/robots/static 前缀(router.go:87-100)。6. 与标准库互操作beeLogger 实现 io.Writer(Write,265) logs.GetLogger(module)(665): log.New(beeLogger, prefix, 0) # 包装成 *log.Logger beeLoggerMap 双检锁缓存 # 多模块复用这是app.Server.ErrorLog logs.GetLogger(HTTP)能工作的原因。7. 并发要点清单(改码必读)机制位置保护对象lock sync.MutexwriteMsg/writeToLoggers/SetLoggeroutputs 切片DCL(读锁写锁)file.go WriteMsg轮转竞态sync.PoolAsync 后LogMsg 复用WaitGroupAsync/Close/Flush消费协程生命周期定时器协程dailyRotate/hourlyRotate跨天触发(到点后仍 DCL 复核)8. 设计赏析两级闸门(logger 级 writer 级):全局 Debug 但某文件只收 Error 是零成本组合。Args 延迟格式化:writeMsg 不 Sprintf,格式化下沉到每个输出器(带 Formatter 的可以按自己的格式拼)——高并发下省 CPU。非阻塞可选:logWithNonBlocking 允许宁丢勿等,日志系统绝不反噬业务。错误吞进 stderr:writeToLoggers 失败 fmt.Fprintln(os.Stderr)(256-263),不让一条坏日志炸掉进程。