feat: transaction implement
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
package logging
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// New 创建同时写入 stdout 和本地文件的结构化文本日志。
|
||||
// stdout 由 systemd/journald 收集,本地文件用于现场诊断和受控远程读取。
|
||||
func New(filePath string) (*slog.Logger, io.Closer, error) {
|
||||
return NewWithConsole(os.Stdout, filePath)
|
||||
}
|
||||
|
||||
// NewWithConsole 允许调用方指定控制台 writer,主要用于测试和嵌入运行。
|
||||
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
|
||||
}
|
||||
|
||||
type syncFileCloser struct {
|
||||
file *os.File
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
Reference in New Issue
Block a user