diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index 082e7dc7..ab11bad1 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -6,11 +6,54 @@ package logger import ( + "os" + "time" + "github.com/sirupsen/logrus" prefixed "github.com/x-cray/logrus-prefixed-formatter" "gopkg.in/natefinch/lumberjack.v2" ) +// cstLocation is the CST (UTC+8) location used for log timestamps. +// Initialized once at package init with a graceful fallback for systems +// without tzdata (e.g., minimal OpenWrt/ImmortalWrt builds). +var cstLocation *time.Location + +func init() { + if loc, err := time.LoadLocation("Asia/Shanghai"); err == nil { + cstLocation = loc + } else { + cstLocation = time.FixedZone("CST", 8*3600) + } +} + +// isJournaldStream returns true if dae's stdout/stderr is captured by systemd's +// journald. Systemd sets the JOURNAL_STREAM environment variable for each +// service's standard streams (value is "device:inode"). When true we suppress +// dae's own timestamp to avoid the redundant "journald prefix + CST timestamp" +// double-prefix that PR #1021 introduced by removing --disable-timestamp from +// dae.service. +func isJournaldStream() bool { + return os.Getenv("JOURNAL_STREAM") != "" +} + +// cstFormatter wraps prefixed.TextFormatter to use CST timezone for timestamps +// without modifying the global time.Local, which would affect unrelated code. +type cstFormatter struct { + *prefixed.TextFormatter +} + +// Format overrides the timestamp formatting to use CST timezone. +func (f *cstFormatter) Format(entry *logrus.Entry) ([]byte, error) { + // Create a copy of the entry to avoid modifying the shared entry object + if !f.DisableTimestamp && entry.Time != (time.Time{}) { + modifiedEntry := *entry + modifiedEntry.Time = entry.Time.In(cstLocation) + return f.TextFormatter.Format(&modifiedEntry) + } + return f.TextFormatter.Format(entry) +} + func SetLogger(log *logrus.Logger, logLevel string, disableTimestamp bool, logFileOpt *lumberjack.Logger) { level, err := logrus.ParseLevel(logLevel) if err != nil { @@ -18,10 +61,20 @@ func SetLogger(log *logrus.Logger, logLevel string, disableTimestamp bool, logFi } log.SetLevel(level) - log.SetFormatter(&prefixed.TextFormatter{ - DisableTimestamp: disableTimestamp, - FullTimestamp: true, - TimestampFormat: "Jan 02 15:04:05", + // Auto-disable timestamp under journald to avoid double prefix. + // When writing to a logfile we always emit the CST timestamp (the file + // needs a self-describing timestamp). --disable-timestamp still forces + // suppression for callers that explicitly want it. + if !disableTimestamp && isJournaldStream() && logFileOpt == nil { + disableTimestamp = true + } + log.SetFormatter(&cstFormatter{ + TextFormatter: &prefixed.TextFormatter{ + DisableTimestamp: disableTimestamp, + FullTimestamp: true, + ForceFormatting: true, + TimestampFormat: "2006-01-02 15:04:05", + }, }) if logFileOpt != nil { log.SetOutput(logFileOpt)