Files
yms-daemon/internal/logging/logger.go
T

58 lines
1.8 KiB
Go
Raw Normal View History

// Package logging 提供结构化文本日志的创建,同时写入控制台与本地文件。
2026-08-15 02:30:36 +08:00
package logging
import (
"errors"
"fmt"
"io"
"log/slog"
"os"
"path/filepath"
)
// New 创建同时写入 stdout 和本地文件的结构化文本日志。
// stdout 由 systemd/journald 收集,本地文件用于现场诊断和受控远程读取。
// 返回的 closer 用于在退出前同步并关闭日志文件。
2026-08-15 02:30:36 +08:00
func New(filePath string) (*slog.Logger, io.Closer, error) {
return NewWithConsole(os.Stdout, filePath)
}
// NewWithConsole 允许调用方指定控制台 writer,主要用于测试和嵌入运行。
// console 与 filePath 均不能为空;日志级别固定为 Info。
2026-08-15 02:30:36 +08:00
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 负责在关闭时先同步再关闭日志文件。
2026-08-15 02:30:36 +08:00
type syncFileCloser struct {
file *os.File
}
// Close 同步并关闭日志文件,重复调用是安全的。
2026-08-15 02:30:36 +08:00
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())
}