Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 11, 21:00 UTC
logging: Buffer the logs before config is loaded (#7245)
Francis Lavoie committed
Sep 10, 2025 at 12:03 UTC
012b4b3d40dae32502503c9d6bc6333acb135cb3
3 files changed
+107
-1
cmd/commandfuncs.go
+18
-1
@@ -172,7 +172,14 @@ func cmdStart(fl Flags) (int, error) {
172
func cmdRun(fl Flags) (int, error) {
173
caddy.TrapSignals()
174
175
- logger := caddy.Log()
175
+ // set up buffered logging for early startup
176
+ // so that we can hold onto logs until after
177
+ // the config is loaded (or fails to load)
178
+ // so that we can write the logs to the user's
179
+ // configured output. we must be sure to flush
180
+ // on any error before the config is loaded.
181
+ logger, defaultLogger, logBuffer := caddy.BufferedLog()
182
+
183
undoMaxProcs := setResourceLimits(logger)
184
defer undoMaxProcs()
185
@@ -187,6 +194,7 @@ func cmdRun(fl Flags) (int, error) {
194
// load all additional envs as soon as possible
195
err := handleEnvFileFlag(fl)
196
if err != nil {
197
+ logBuffer.FlushTo(defaultLogger)
198
return caddy.ExitCodeFailedStartup, err
199
}
200
@@ -204,6 +212,7 @@ func cmdRun(fl Flags) (int, error) {
212
logger.Info("no autosave file exists", zap.String("autosave_file", caddy.ConfigAutosavePath))
213
resumeFlag = false
214
} else if err != nil {
215
+ logBuffer.FlushTo(defaultLogger)
216
return caddy.ExitCodeFailedStartup, err
217
} else {
218
if configFlag == "" {
@@ -222,6 +231,7 @@ func cmdRun(fl Flags) (int, error) {
231
if !resumeFlag {
232
config, configFile, err = LoadConfig(configFlag, configAdapterFlag)
233
if err != nil {
234
+ logBuffer.FlushTo(defaultLogger)
235
return caddy.ExitCodeFailedStartup, err
236
}
237
}
@@ -239,8 +249,15 @@ func cmdRun(fl Flags) (int, error) {
249
// run the initial config
250
err = caddy.Load(config, true)
251
if err != nil {
252
+ logBuffer.FlushTo(defaultLogger)
253
return caddy.ExitCodeFailedStartup, fmt.Errorf("loading initial config: %v", err)
254
}
255
+
256
+ // at this stage the config will have replaced
257
+ // the default logger to the configured one, so
258
+ // we can now flush the buffered logs, then log
259
+ // that the config is running.
260
+ logBuffer.FlushTo(caddy.Log())
261
logger.Info("serving initial configuration")
262
263
// if we are to report to another process the successful start
internal/logbuffer.go
new
+72
@@ -0,0 +1,72 @@
1
+// Copyright 2015 Matthew Holt and The Caddy Authors
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License");
4
+// you may not use this file except in compliance with the License.
5
+// You may obtain a copy of the License at
6
+//
7
+// http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS,
11
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+// See the License for the specific language governing permissions and
13
+// limitations under the License.
14
+
15
+package internal
16
+
17
+import (
18
+ "sync"
19
+
20
+ "go.uber.org/zap"
21
+ "go.uber.org/zap/zapcore"
22
+)
23
+
24
+// LogBufferCore is a zapcore.Core that buffers log entries in memory.
25
+type LogBufferCore struct {
26
+ mu sync.Mutex
27
+ entries []zapcore.Entry
28
+ fields [][]zapcore.Field
29
+ level zapcore.LevelEnabler
30
+}
31
+
32
+func NewLogBufferCore(level zapcore.LevelEnabler) *LogBufferCore {
33
+ return &LogBufferCore{
34
+ level: level,
35
+ }
36
+}
37
+
38
+func (c *LogBufferCore) Enabled(lvl zapcore.Level) bool {
39
+ return c.level.Enabled(lvl)
40
+}
41
+
42
+func (c *LogBufferCore) With(fields []zapcore.Field) zapcore.Core {
43
+ return c
44
+}
45
+
46
+func (c *LogBufferCore) Check(entry zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
47
+ if c.Enabled(entry.Level) {
48
+ return ce.AddCore(entry, c)
49
+ }
50
+ return ce
51
+}
52
+
53
+func (c *LogBufferCore) Write(entry zapcore.Entry, fields []zapcore.Field) error {
54
+ c.mu.Lock()
55
+ defer c.mu.Unlock()
56
+ c.entries = append(c.entries, entry)
57
+ c.fields = append(c.fields, fields)
58
+ return nil
59
+}
60
+
61
+func (c *LogBufferCore) Sync() error { return nil }
62
+
63
+// FlushTo flushes buffered logs to the given zap.Logger.
64
+func (c *LogBufferCore) FlushTo(logger *zap.Logger) {
65
+ c.mu.Lock()
66
+ defer c.mu.Unlock()
67
+ for idx, entry := range c.entries {
68
+ logger.WithOptions().Check(entry.Level, entry.Message).Write(c.fields[idx]...)
69
+ }
70
+ c.entries = nil
71
+ c.fields = nil
72
+}
logging.go
+17
@@ -28,6 +28,8 @@ import (
28
"go.uber.org/zap"
29
"go.uber.org/zap/zapcore"
30
"golang.org/x/term"
31
+
32
+ "github.com/caddyserver/caddy/v2/internal"
33
)
34
35
func init() {
@@ -773,6 +775,21 @@ func Log() *zap.Logger {
775
return defaultLogger.logger
776
}
777
778
+// BufferedLog sets the default logger to one that buffers
779
+// logs before a config is loaded.
780
+// Returns the buffered logger, the original default logger
781
+// (for flushing on errors), and the buffer core so that the
782
+// caller can flush the logs after the config is loaded or
783
+// fails to load.
784
+func BufferedLog() (*zap.Logger, *zap.Logger, *internal.LogBufferCore) {
785
+ defaultLoggerMu.Lock()
786
+ defer defaultLoggerMu.Unlock()
787
+ origLogger := defaultLogger.logger
788
+ bufferCore := internal.NewLogBufferCore(zap.InfoLevel)
789
+ defaultLogger.logger = zap.New(bufferCore)
790
+ return defaultLogger.logger, origLogger, bufferCore
791
+}
792
+
793
var (
794
coloringEnabled = os.Getenv("NO_COLOR") == "" && os.Getenv("TERM") != "xterm-mono"
795
defaultLogger, _ = newDefaultProductionLog()