mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-02-03 22:18:39 +00:00
Without verbose log, we use internal log writer with log level set to debug. However, this will affect other writers, like console log, since they are default to notice level. By adopting FilteredLevelWriter, we can make internal log writer run in debug level, but all others will run in default level instead.
133 lines
3.0 KiB
Go
133 lines
3.0 KiB
Go
package cli
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"io"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/rs/zerolog"
|
|
|
|
"github.com/Control-D-Inc/ctrld"
|
|
)
|
|
|
|
const (
|
|
logWriterSize = 1024 * 1024 * 5 // 5 MB
|
|
logWriterInitialSize = 32 // 32 B
|
|
logSentInterval = time.Minute
|
|
logTruncatedMarker = "...\n"
|
|
)
|
|
|
|
type logViewResponse struct {
|
|
Data string `json:"data"`
|
|
}
|
|
|
|
// logWriter is an internal buffer to keep track of runtime log when no logging is enabled.
|
|
type logWriter struct {
|
|
mu sync.Mutex
|
|
buf bytes.Buffer
|
|
size int
|
|
}
|
|
|
|
// newLogWriter creates an internal log writer with a fixed buffer size.
|
|
func newLogWriter() *logWriter {
|
|
lw := &logWriter{size: logWriterSize}
|
|
return lw
|
|
}
|
|
|
|
func (lw *logWriter) Write(p []byte) (int, error) {
|
|
lw.mu.Lock()
|
|
defer lw.mu.Unlock()
|
|
|
|
// If writing p causes overflows, discard old data.
|
|
if lw.buf.Len()+len(p) > lw.size {
|
|
buf := lw.buf.Bytes()
|
|
buf = buf[:logWriterInitialSize]
|
|
if idx := bytes.LastIndex(buf, []byte("\n")); idx != -1 {
|
|
buf = buf[:idx]
|
|
}
|
|
lw.buf.Reset()
|
|
lw.buf.Write(buf)
|
|
lw.buf.WriteString(logTruncatedMarker) // indicate that the log was truncated.
|
|
}
|
|
// If p is bigger than buffer size, truncate p by half until its size is smaller.
|
|
for len(p)+lw.buf.Len() > lw.size {
|
|
p = p[len(p)/2:]
|
|
}
|
|
return lw.buf.Write(p)
|
|
}
|
|
|
|
// initInternalLogging performs internal logging if there's no log enabled.
|
|
func (p *prog) initInternalLogging(writers []io.Writer) {
|
|
if !p.needInternalLogging() {
|
|
return
|
|
}
|
|
p.initInternalLogWriterOnce.Do(func() {
|
|
mainLog.Load().Notice().Msg("internal logging enabled")
|
|
lw := newLogWriter()
|
|
p.internalLogWriter = lw
|
|
p.internalLogSent = time.Now().Add(-logSentInterval)
|
|
})
|
|
p.mu.Lock()
|
|
lw := p.internalLogWriter
|
|
p.mu.Unlock()
|
|
// If ctrld was run without explicit verbose level,
|
|
// run the internal logging at debug level, so we could
|
|
// have enough information for troubleshooting.
|
|
if verbose == 0 {
|
|
for i := range writers {
|
|
w := &zerolog.FilteredLevelWriter{
|
|
Writer: zerolog.LevelWriterAdapter{Writer: writers[i]},
|
|
Level: zerolog.NoticeLevel,
|
|
}
|
|
writers[i] = w
|
|
}
|
|
zerolog.SetGlobalLevel(zerolog.DebugLevel)
|
|
}
|
|
writers = append(writers, lw)
|
|
multi := zerolog.MultiLevelWriter(writers...)
|
|
l := mainLog.Load().Output(multi).With().Logger()
|
|
mainLog.Store(&l)
|
|
ctrld.ProxyLogger.Store(&l)
|
|
}
|
|
|
|
// needInternalLogging reports whether prog needs to run internal logging.
|
|
func (p *prog) needInternalLogging() bool {
|
|
// Do not run in non-cd mode.
|
|
if cdUID == "" {
|
|
return false
|
|
}
|
|
// Do not run if there's already log file.
|
|
if p.cfg.Service.LogPath != "" {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (p *prog) logContent() ([]byte, error) {
|
|
var data []byte
|
|
if p.needInternalLogging() {
|
|
p.mu.Lock()
|
|
lw := p.internalLogWriter
|
|
p.mu.Unlock()
|
|
if lw == nil {
|
|
return nil, errors.New("nil internal log writer")
|
|
}
|
|
lw.mu.Lock()
|
|
data = lw.buf.Bytes()
|
|
lw.mu.Unlock()
|
|
} else {
|
|
if p.cfg.Service.LogPath == "" {
|
|
return nil, nil
|
|
}
|
|
buf, err := os.ReadFile(p.cfg.Service.LogPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
data = buf
|
|
}
|
|
return data, nil
|
|
}
|