Skip to content

Commit 012b4b3

Browse files
logging: Buffer the logs before config is loaded (#7245)
1 parent d9cc24f commit 012b4b3

3 files changed

Lines changed: 107 additions & 1 deletion

File tree

cmd/commandfuncs.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,14 @@ func cmdStart(fl Flags) (int, error) {
172172
func cmdRun(fl Flags) (int, error) {
173173
caddy.TrapSignals()
174174

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+
176183
undoMaxProcs := setResourceLimits(logger)
177184
defer undoMaxProcs()
178185

@@ -187,6 +194,7 @@ func cmdRun(fl Flags) (int, error) {
187194
// load all additional envs as soon as possible
188195
err := handleEnvFileFlag(fl)
189196
if err != nil {
197+
logBuffer.FlushTo(defaultLogger)
190198
return caddy.ExitCodeFailedStartup, err
191199
}
192200

@@ -204,6 +212,7 @@ func cmdRun(fl Flags) (int, error) {
204212
logger.Info("no autosave file exists", zap.String("autosave_file", caddy.ConfigAutosavePath))
205213
resumeFlag = false
206214
} else if err != nil {
215+
logBuffer.FlushTo(defaultLogger)
207216
return caddy.ExitCodeFailedStartup, err
208217
} else {
209218
if configFlag == "" {
@@ -222,6 +231,7 @@ func cmdRun(fl Flags) (int, error) {
222231
if !resumeFlag {
223232
config, configFile, err = LoadConfig(configFlag, configAdapterFlag)
224233
if err != nil {
234+
logBuffer.FlushTo(defaultLogger)
225235
return caddy.ExitCodeFailedStartup, err
226236
}
227237
}
@@ -239,8 +249,15 @@ func cmdRun(fl Flags) (int, error) {
239249
// run the initial config
240250
err = caddy.Load(config, true)
241251
if err != nil {
252+
logBuffer.FlushTo(defaultLogger)
242253
return caddy.ExitCodeFailedStartup, fmt.Errorf("loading initial config: %v", err)
243254
}
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())
244261
logger.Info("serving initial configuration")
245262

246263
// if we are to report to another process the successful start

internal/logbuffer.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ import (
2828
"go.uber.org/zap"
2929
"go.uber.org/zap/zapcore"
3030
"golang.org/x/term"
31+
32+
"github.com/caddyserver/caddy/v2/internal"
3133
)
3234

3335
func init() {
@@ -773,6 +775,21 @@ func Log() *zap.Logger {
773775
return defaultLogger.logger
774776
}
775777

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+
776793
var (
777794
coloringEnabled = os.Getenv("NO_COLOR") == "" && os.Getenv("TERM") != "xterm-mono"
778795
defaultLogger, _ = newDefaultProductionLog()

0 commit comments

Comments
 (0)