modelRT/logger/zap.go

125 lines
3.7 KiB
Go

// Package logger define log struct of modelRT project
package logger
import (
"fmt"
"os"
"sync"
"time"
"modelRT/config"
"modelRT/constants"
"github.com/natefinch/lumberjack"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var (
once sync.Once
_globalLoggerMu sync.RWMutex
_globalLogger *zap.Logger
)
// getEncoder returns a console encoder for development (human-readable, colored) and a JSON encoder
// for container modes (parseable by Promtail pipeline_stages).
func getEncoder(mode string) zapcore.Encoder {
cfg := zap.NewProductionEncoderConfig()
cfg.EncodeTime = zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05")
cfg.TimeKey = "time"
cfg.EncodeCaller = zapcore.ShortCallerEncoder
if mode == constants.DevelopmentLogMode {
cfg.EncodeLevel = zapcore.CapitalColorLevelEncoder
return zapcore.NewConsoleEncoder(cfg)
}
cfg.EncodeLevel = zapcore.CapitalLevelEncoder
return zapcore.NewJSONEncoder(cfg)
}
// getWriteSyncer returns write targets based on mode:
// - development: stdout + optional Loki direct-push (when loki.endpoint is set)
// - container modes: stdout always (Promtail collects) + rotating file (when filepath is set)
func getWriteSyncer(lCfg config.LoggerConfig) zapcore.WriteSyncer {
stdout := zapcore.AddSync(os.Stdout)
if lCfg.Mode == constants.DevelopmentLogMode {
if lCfg.Loki.Endpoint == "" {
return stdout
}
return zapcore.NewMultiWriteSyncer(stdout, newLokiSyncer(lCfg.Loki))
}
syncers := []zapcore.WriteSyncer{stdout}
if lCfg.FilePath != "" {
dateStr := time.Now().Format("2006-01-02 15:04:05")
syncers = append(syncers, zapcore.AddSync(&lumberjack.Logger{
Filename: fmt.Sprintf(lCfg.FilePath, dateStr),
MaxSize: lCfg.MaxSize,
MaxAge: lCfg.MaxAge,
MaxBackups: lCfg.MaxBackups,
Compress: lCfg.Compress,
}))
}
return zapcore.NewMultiWriteSyncer(syncers...)
}
// containerFields reads K8s Downward API environment variables and returns them as global zap fields.
// These fields appear on every log line, allowing Loki/Grafana to filter by pod, namespace, and node.
// Inject them in the Deployment manifest:
//
// env:
// - name: K8S_NAMESPACE
// valueFrom: {fieldRef: {fieldPath: metadata.namespace}}
// - name: K8S_NODE_NAME
// valueFrom: {fieldRef: {fieldPath: spec.nodeName}}
func containerFields() []zap.Field {
var fields []zap.Field
// HOSTNAME is automatically set to the pod name by Kubernetes.
if pod := os.Getenv("HOSTNAME"); pod != "" {
fields = append(fields, zap.String("pod", pod))
}
if ns := os.Getenv("K8S_NAMESPACE"); ns != "" {
fields = append(fields, zap.String("namespace", ns))
}
if node := os.Getenv("K8S_NODE_NAME"); node != "" {
fields = append(fields, zap.String("node", node))
}
return fields
}
// initLogger return successfully initialized zap logger
func initLogger(lCfg config.LoggerConfig) *zap.Logger {
writeSyncer := getWriteSyncer(lCfg)
encoder := getEncoder(lCfg.Mode)
l := new(zapcore.Level)
if err := l.UnmarshalText([]byte(lCfg.Level)); err != nil {
panic(err)
}
core := zapcore.NewCore(encoder, writeSyncer, l)
opts := []zap.Option{zap.AddCaller()}
if lCfg.Mode != constants.DevelopmentLogMode {
opts = append(opts, zap.Fields(containerFields()...))
}
logger := zap.New(core, opts...)
zap.ReplaceGlobals(logger)
return logger
}
// InitLoggerInstance define func of return instance of zap logger
func InitLoggerInstance(lCfg config.LoggerConfig) {
once.Do(func() {
_globalLogger = initLogger(lCfg)
})
}
// GetLoggerInstance define func of returns the global logger instance It's safe for concurrent use.
func GetLoggerInstance() *zap.Logger {
_globalLoggerMu.RLock()
logger := _globalLogger
_globalLoggerMu.RUnlock()
return logger
}