// Package logx 提供「控制台 + 按天切分文件」的运行日志。 package logx import ( "io" "log" "os" "path/filepath" "sync" "time" ) type dailyWriter struct { dir string mu sync.Mutex f *os.File day string } func (w *dailyWriter) Write(p []byte) (int, error) { w.mu.Lock() defer w.mu.Unlock() day := time.Now().Format("2006-01-02") if w.f == nil || w.day != day { if w.f != nil { _ = w.f.Close() w.f = nil } if err := os.MkdirAll(w.dir, 0o755); err != nil { return 0, err } f, err := os.OpenFile(filepath.Join(w.dir, "daili-"+day+".log"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644) if err != nil { return 0, err } w.f = f w.day = day } // 写文件失败不能影响控制台输出 n, err := w.f.Write(p) return n, err } func (w *dailyWriter) Close() error { w.mu.Lock() defer w.mu.Unlock() if w.f == nil { return nil } err := w.f.Close() w.f = nil return err } // Setup 把标准日志同时写到控制台与 dir 下按天切分的文件。 // 返回的 Closer 在进程退出前关闭即可。 func Setup(dir string) (io.Closer, error) { if err := os.MkdirAll(dir, 0o755); err != nil { return nil, err } w := &dailyWriter{dir: dir} // 预热:启动即创建当天文件,避免“刚启动就崩、日志却没落盘” if _, err := w.Write(nil); err != nil { return nil, err } log.SetOutput(io.MultiWriter(os.Stderr, w)) return w, nil }