@marcel91200 / caddy Mirror · read-only / commits / 012b4b3d
Fork 0

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()