f536987a7e
- doc: add comment
58 lines
1.8 KiB
Go
58 lines
1.8 KiB
Go
// Package logging 提供结构化文本日志的创建,同时写入控制台与本地文件。
|
|
package logging
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
// New 创建同时写入 stdout 和本地文件的结构化文本日志。
|
|
// stdout 由 systemd/journald 收集,本地文件用于现场诊断和受控远程读取。
|
|
// 返回的 closer 用于在退出前同步并关闭日志文件。
|
|
func New(filePath string) (*slog.Logger, io.Closer, error) {
|
|
return NewWithConsole(os.Stdout, filePath)
|
|
}
|
|
|
|
// NewWithConsole 允许调用方指定控制台 writer,主要用于测试和嵌入运行。
|
|
// console 与 filePath 均不能为空;日志级别固定为 Info。
|
|
func NewWithConsole(console io.Writer, filePath string) (*slog.Logger, io.Closer, error) {
|
|
if console == nil {
|
|
return nil, nil, errors.New("console writer is required")
|
|
}
|
|
if filePath == "" {
|
|
return nil, nil, errors.New("log file path is required")
|
|
}
|
|
absPath, err := filepath.Abs(filePath)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("resolve log file path: %w", err)
|
|
}
|
|
if err := os.MkdirAll(filepath.Dir(absPath), 0o750); err != nil {
|
|
return nil, nil, fmt.Errorf("create log directory: %w", err)
|
|
}
|
|
file, err := os.OpenFile(absPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o640)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("open log file: %w", err)
|
|
}
|
|
handler := slog.NewTextHandler(io.MultiWriter(console, file), &slog.HandlerOptions{Level: slog.LevelInfo})
|
|
return slog.New(handler), &syncFileCloser{file: file}, nil
|
|
}
|
|
|
|
// syncFileCloser 负责在关闭时先同步再关闭日志文件。
|
|
type syncFileCloser struct {
|
|
file *os.File
|
|
}
|
|
|
|
// Close 同步并关闭日志文件,重复调用是安全的。
|
|
func (c *syncFileCloser) Close() error {
|
|
if c == nil || c.file == nil {
|
|
return nil
|
|
}
|
|
file := c.file
|
|
c.file = nil
|
|
return errors.Join(file.Sync(), file.Close())
|
|
}
|