1package logging
2
3import (
4 "fmt"
5 "io"
6 "os"
7 "path/filepath"
8
9 "github.com/rs/zerolog"
10 "github.com/rs/zerolog/log"
11 "gopkg.in/natefinch/lumberjack.v2"
12 "gordon/internal/config"
13)
14
15var (
16 MainLogger zerolog.Logger
17 ProxyLogger zerolog.Logger
18)
19
20// Setup initializes the logging system based on the configuration
21func Setup(cfg *config.Config) error {
22 if !cfg.Logging.Enabled {
23 // Keep console logging only
24 log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
25 MainLogger = log.Logger
26 ProxyLogger = log.Logger
27 return nil
28 }
29
30 // Create logs directory with secure permissions (0700 - owner only)
31 if err := os.MkdirAll(cfg.Logging.Dir, 0700); err != nil {
32 return fmt.Errorf("failed to create logs directory: %w", err)
33 }
34
35 // Create container logs directory with secure permissions
36 containerLogDir := filepath.Join(cfg.Logging.Dir, cfg.Logging.ContainerLogDir)
37 if err := os.MkdirAll(containerLogDir, 0700); err != nil {
38 return fmt.Errorf("failed to create container logs directory: %w", err)
39 }
40
41 // Set log level
42 level, err := zerolog.ParseLevel(cfg.Logging.Level)
43 if err != nil {
44 level = zerolog.InfoLevel
45 log.Warn().Str("invalid_level", cfg.Logging.Level).Msg("Invalid log level, using info")
46 }
47 zerolog.SetGlobalLevel(level)
48
49 // Configure time format
50 zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
51
52 // Setup main application logger
53 mainLogFile := filepath.Join(cfg.Logging.Dir, cfg.Logging.MainLogFile)
54 mainFileWriter := &lumberjack.Logger{
55 Filename: mainLogFile,
56 MaxSize: cfg.Logging.MaxSize,
57 MaxBackups: cfg.Logging.MaxBackups,
58 MaxAge: cfg.Logging.MaxAge,
59 Compress: cfg.Logging.Compress,
60 }
61
62 // Set file permissions to be secure (readable only by owner)
63 if err := os.Chmod(mainLogFile, 0600); err != nil && !os.IsNotExist(err) {
64 log.Warn().Err(err).Str("file", mainLogFile).Msg("Failed to set secure permissions on log file")
65 }
66
67 // Setup proxy logger
68 proxyLogFile := filepath.Join(cfg.Logging.Dir, cfg.Logging.ProxyLogFile)
69 proxyFileWriter := &lumberjack.Logger{
70 Filename: proxyLogFile,
71 MaxSize: cfg.Logging.MaxSize,
72 MaxBackups: cfg.Logging.MaxBackups,
73 MaxAge: cfg.Logging.MaxAge,
74 Compress: cfg.Logging.Compress,
75 }
76
77 // Set file permissions to be secure (readable only by owner)
78 if err := os.Chmod(proxyLogFile, 0600); err != nil && !os.IsNotExist(err) {
79 log.Warn().Err(err).Str("file", proxyLogFile).Msg("Failed to set secure permissions on log file")
80 }
81
82 // Create multi-writers (console + file)
83 consoleWriter := zerolog.ConsoleWriter{Out: os.Stderr}
84
85 mainMultiWriter := io.MultiWriter(consoleWriter, mainFileWriter)
86 proxyMultiWriter := io.MultiWriter(consoleWriter, proxyFileWriter)
87
88 // Initialize loggers
89 MainLogger = zerolog.New(mainMultiWriter).With().Timestamp().Logger()
90 ProxyLogger = zerolog.New(proxyMultiWriter).With().Timestamp().Logger()
91
92 // Set the global logger to the main logger
93 log.Logger = MainLogger
94
95 log.Info().
96 Str("main_log", mainLogFile).
97 Str("proxy_log", proxyLogFile).
98 Str("container_log_dir", containerLogDir).
99 Str("level", level.String()).
100 Msg("File logging initialized")
101
102 return nil
103}
104
105// GetContainerLogWriter returns a log writer for a specific container
106func GetContainerLogWriter(cfg *config.Config, containerID string, containerName string) (*lumberjack.Logger, error) {
107 if !cfg.Logging.Enabled {
108 return nil, fmt.Errorf("logging is disabled")
109 }
110
111 containerLogDir := filepath.Join(cfg.Logging.Dir, cfg.Logging.ContainerLogDir)
112
113 // Ensure container log directory exists with secure permissions
114 if err := os.MkdirAll(containerLogDir, 0700); err != nil {
115 return nil, fmt.Errorf("failed to create container log directory: %w", err)
116 }
117
118 // Use container ID as the main log file name
119 logFile := filepath.Join(containerLogDir, fmt.Sprintf("%s.log", containerID))
120
121 writer := &lumberjack.Logger{
122 Filename: logFile,
123 MaxSize: cfg.Logging.MaxSize,
124 MaxBackups: cfg.Logging.MaxBackups,
125 MaxAge: cfg.Logging.MaxAge,
126 Compress: cfg.Logging.Compress,
127 }
128
129 // Set file permissions to be secure (readable only by owner)
130 if err := os.Chmod(logFile, 0600); err != nil && !os.IsNotExist(err) {
131 log.Warn().Err(err).Str("file", logFile).Msg("Failed to set secure permissions on container log file")
132 }
133
134 // Create a symlink for easier access using container name (if provided)
135 if containerName != "" && containerName != containerID {
136 symlinkPath := filepath.Join(containerLogDir, fmt.Sprintf("%s.log", containerName))
137
138 // Remove existing symlink if it exists
139 if _, err := os.Lstat(symlinkPath); err == nil {
140 os.Remove(symlinkPath)
141 }
142
143 // Create new symlink
144 relativeLogFile := fmt.Sprintf("%s.log", containerID)
145 if err := os.Symlink(relativeLogFile, symlinkPath); err != nil {
146 log.Warn().
147 Err(err).
148 Str("symlink", symlinkPath).
149 Str("target", relativeLogFile).
150 Msg("Failed to create container name symlink")
151 }
152 }
153
154 return writer, nil
155}
156
157// Close gracefully closes all logging writers
158func Close() {
159 // Lumberjack loggers will be closed automatically when the program exits
160 // This function is provided for future extensibility
161}