mirror of
https://github.com/cloudflare/cloudflared.git
synced 2025-05-11 04:36:34 +00:00

This addresses a bug where logging would not be output when cloudflared was run as a Windows Service. That was happening because Windows Services have no stderr/out, and the ConsoleWriter log was failing inside zerolog, which would then not proceed to the next logger (the file logger). We now overcome that by using our own multi writer that is resilient to errors.
197 lines
5.0 KiB
Go
197 lines
5.0 KiB
Go
package logger
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
|
|
"github.com/mattn/go-colorable"
|
|
"github.com/rs/zerolog"
|
|
fallbacklog "github.com/rs/zerolog/log"
|
|
"github.com/urfave/cli/v2"
|
|
"gopkg.in/natefinch/lumberjack.v2"
|
|
)
|
|
|
|
const (
|
|
EnableTerminalLog = false
|
|
DisableTerminalLog = true
|
|
|
|
LogLevelFlag = "loglevel"
|
|
LogFileFlag = "logfile"
|
|
LogDirectoryFlag = "log-directory"
|
|
LogTransportLevelFlag = "transport-loglevel"
|
|
|
|
LogSSHDirectoryFlag = "log-directory"
|
|
LogSSHLevelFlag = "log-level"
|
|
|
|
dirPermMode = 0744 // rwxr--r--
|
|
filePermMode = 0644 // rw-r--r--
|
|
)
|
|
|
|
func fallbackLogger(err error) *zerolog.Logger {
|
|
failLog := fallbacklog.With().Logger()
|
|
fallbacklog.Error().Msgf("Falling back to a default logger due to logger setup failure: %s", err)
|
|
|
|
return &failLog
|
|
}
|
|
|
|
type resilientMultiWriter struct {
|
|
writers []io.Writer
|
|
}
|
|
|
|
// This custom resilientMultiWriter is an alternative to zerolog's so that we can make it resilient to individual
|
|
// writer's errors. E.g., when running as a Windows service, the console writer fails, but we don't want to
|
|
// allow that to prevent all logging to fail due to breaking the for loop upon an error.
|
|
func (t resilientMultiWriter) Write(p []byte) (n int, err error) {
|
|
for _, w := range t.writers {
|
|
_, _ = w.Write(p)
|
|
}
|
|
return len(p), nil
|
|
}
|
|
|
|
|
|
func newZerolog(loggerConfig *Config) *zerolog.Logger {
|
|
var writers []io.Writer
|
|
|
|
if loggerConfig.ConsoleConfig != nil {
|
|
writers = append(writers, createConsoleLogger(*loggerConfig.ConsoleConfig))
|
|
}
|
|
|
|
if loggerConfig.FileConfig != nil {
|
|
fileLogger, err := createFileWriter(*loggerConfig.FileConfig)
|
|
if err != nil {
|
|
return fallbackLogger(err)
|
|
}
|
|
|
|
writers = append(writers, fileLogger)
|
|
}
|
|
|
|
if loggerConfig.RollingConfig != nil {
|
|
rollingLogger, err := createRollingLogger(*loggerConfig.RollingConfig)
|
|
if err != nil {
|
|
return fallbackLogger(err)
|
|
}
|
|
|
|
writers = append(writers, rollingLogger)
|
|
}
|
|
|
|
multi := resilientMultiWriter{writers}
|
|
|
|
level, err := zerolog.ParseLevel(loggerConfig.MinLevel)
|
|
if err != nil {
|
|
return fallbackLogger(err)
|
|
}
|
|
log := zerolog.New(multi).With().Timestamp().Logger().Level(level)
|
|
|
|
return &log
|
|
}
|
|
|
|
func CreateTransportLoggerFromContext(c *cli.Context, disableTerminal bool) *zerolog.Logger {
|
|
return createFromContext(c, LogTransportLevelFlag, LogDirectoryFlag, disableTerminal)
|
|
}
|
|
|
|
func CreateLoggerFromContext(c *cli.Context, disableTerminal bool) *zerolog.Logger {
|
|
return createFromContext(c, LogLevelFlag, LogDirectoryFlag, disableTerminal)
|
|
}
|
|
|
|
func CreateSSHLoggerFromContext(c *cli.Context, disableTerminal bool) *zerolog.Logger {
|
|
return createFromContext(c, LogSSHLevelFlag, LogSSHDirectoryFlag, disableTerminal)
|
|
}
|
|
|
|
func createFromContext(
|
|
c *cli.Context,
|
|
logLevelFlagName,
|
|
logDirectoryFlagName string,
|
|
disableTerminal bool,
|
|
) *zerolog.Logger {
|
|
logLevel := c.String(logLevelFlagName)
|
|
logFile := c.String(LogFileFlag)
|
|
logDirectory := c.String(logDirectoryFlagName)
|
|
|
|
loggerConfig := CreateConfig(
|
|
logLevel,
|
|
disableTerminal,
|
|
logDirectory,
|
|
logFile,
|
|
)
|
|
|
|
log := newZerolog(loggerConfig)
|
|
if incompatibleFlagsSet := logFile != "" && logDirectory != ""; incompatibleFlagsSet {
|
|
log.Error().Msgf("Your config includes values for both %s and %s, but they are incompatible. %s takes precedence.", LogFileFlag, logDirectoryFlagName, LogFileFlag)
|
|
}
|
|
return log
|
|
}
|
|
|
|
func Create(loggerConfig *Config) *zerolog.Logger {
|
|
if loggerConfig == nil {
|
|
loggerConfig = &Config{
|
|
defaultConfig.ConsoleConfig,
|
|
nil,
|
|
nil,
|
|
defaultConfig.MinLevel,
|
|
}
|
|
}
|
|
return newZerolog(loggerConfig)
|
|
}
|
|
|
|
func createConsoleLogger(config ConsoleConfig) io.Writer {
|
|
return zerolog.ConsoleWriter{
|
|
Out: colorable.NewColorableStderr(),
|
|
NoColor: config.noColor,
|
|
}
|
|
}
|
|
|
|
func createFileWriter(config FileConfig) (io.Writer, error) {
|
|
var logFile io.Writer
|
|
fullpath := config.Fullpath()
|
|
|
|
// Try to open the existing file
|
|
logFile, err := os.OpenFile(fullpath, os.O_APPEND|os.O_WRONLY, filePermMode)
|
|
if err != nil {
|
|
// If the existing file wasn't found, or couldn't be opened, just ignore
|
|
// it and recreate a new one.
|
|
logFile, err = createDirFile(config)
|
|
// If creating a new logfile fails, then we have no choice but to error out.
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return logFile, nil
|
|
}
|
|
|
|
func createDirFile(config FileConfig) (io.Writer, error) {
|
|
if config.Dirname != "" {
|
|
err := os.MkdirAll(config.Dirname, dirPermMode)
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to create directories for new logfile: %s", err)
|
|
}
|
|
}
|
|
|
|
mode := os.FileMode(filePermMode)
|
|
|
|
fullPath := filepath.Join(config.Dirname, config.Filename)
|
|
logFile, err := os.OpenFile(fullPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, mode)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to create a new logfile: %s", err)
|
|
}
|
|
|
|
return logFile, nil
|
|
}
|
|
|
|
func createRollingLogger(config RollingConfig) (io.Writer, error) {
|
|
if err := os.MkdirAll(config.Dirname, dirPermMode); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &lumberjack.Logger{
|
|
Filename: path.Join(config.Dirname, config.Filename),
|
|
MaxBackups: config.maxBackups,
|
|
MaxSize: config.maxSize,
|
|
MaxAge: config.maxAge,
|
|
}, nil
|
|
}
|