package core
import (
"os"
"path/filepath"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"github.com/kardolus/chatgpt-cli/internal"
)
type Logs struct {
Dir string
HumanPath string
DebugPath string
HumanLogger *zap.SugaredLogger
DebugLogger *zap.SugaredLogger
HumanZap *zap.Logger
DebugZap *zap.Logger
humanFile *os.File
debugFile *os.File
}
func (l *Logs) Close() {
if l.HumanZap != nil {
_ = l.HumanZap.Sync()
}
if l.DebugZap != nil {
_ = l.DebugZap.Sync()
}
if l.humanFile != nil {
_ = l.humanFile.Close()
}
if l.debugFile != nil {
_ = l.debugFile.Close()
}
}
func NewLogs() (*Logs, error) {
cacheHome, err := internal.GetCacheHome()
if err != nil {
return nil, err
}
dir := filepath.Join(cacheHome, "agent")
if err := os.MkdirAll(dir, 0o700); err != nil {
return nil, err
}
humanPath := filepath.Join(dir, "agent.transcript.log")
debugPath := filepath.Join(dir, "agent.debug.jsonl")
humanSug, humanZap, humanFile, err := newFileLogger(humanPath, zapcore.InfoLevel, false )
if err != nil {
return nil, err
}
debugSug, debugZap, debugFile, err := newFileLogger(debugPath, zapcore.DebugLevel, true )
if err != nil {
_ = humanZap.Sync()
_ = humanFile.Close()
return nil, err
}
return &Logs{
Dir: dir,
HumanPath: humanPath,
DebugPath: debugPath,
HumanLogger: humanSug,
DebugLogger: debugSug,
HumanZap: humanZap,
DebugZap: debugZap,
humanFile: humanFile,
debugFile: debugFile,
}, nil
}
func newFileLogger(path string, level zapcore.Level, json bool) (*zap.SugaredLogger, *zap.Logger, *os.File, error) {
f, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644)
if err != nil {
return nil, nil, nil, err
}
encCfg := zap.NewProductionEncoderConfig()
encCfg.TimeKey = "ts"
encCfg.EncodeTime = zapcore.ISO8601TimeEncoder
var enc zapcore.Encoder
if json {
enc = zapcore.NewJSONEncoder(encCfg)
} else {
enc = zapcore.NewConsoleEncoder(encCfg)
}
core := zapcore.NewCore(enc, zapcore.AddSync(f), level)
zl := zap.New(core)
return zl.Sugar(), zl, f, nil
}