Files
upstream-mirror b352a9a30d
Some checks failed
test / Lint (push) Failing after 3s
test / Test with Go 1.26.8 (push) Failing after 2s
snapshot of https://github.com/centrifugal/centrifugo.git (master, 2026-09-16; blobs > 131072B omitted)
2026-09-16 01:17:21 +05:00

185 lines
4.7 KiB
Go

package logging
import (
"io"
"os"
"runtime"
"strings"
"github.com/centrifugal/centrifugo/v6/internal/config"
"github.com/centrifugal/centrifugo/v6/internal/logutils"
"github.com/centrifugal/centrifuge"
"github.com/mattn/go-isatty"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
type Level = zerolog.Level
const (
TraceLevel = zerolog.TraceLevel
DebugLevel = zerolog.DebugLevel
InfoLevel = zerolog.InfoLevel
WarnLevel = zerolog.WarnLevel
ErrorLevel = zerolog.ErrorLevel
)
var logLevelMatches = map[string]zerolog.Level{
"NONE": zerolog.Disabled,
"TRACE": zerolog.TraceLevel,
"DEBUG": zerolog.DebugLevel,
"INFO": zerolog.InfoLevel,
"WARN": zerolog.WarnLevel,
"ERROR": zerolog.ErrorLevel,
"FATAL": zerolog.FatalLevel,
}
func CentrifugeLogLevel(level string) centrifuge.LogLevel {
if l, ok := logStringToLevel[strings.ToLower(level)]; ok {
return l
}
return centrifuge.LogLevelInfo
}
// logStringToLevel matches level string to Centrifuge LogLevel.
var logStringToLevel = map[string]centrifuge.LogLevel{
"trace": centrifuge.LogLevelTrace,
"debug": centrifuge.LogLevelDebug,
"info": centrifuge.LogLevelInfo,
"error": centrifuge.LogLevelError,
"none": centrifuge.LogLevelNone,
}
// centrifugeLogHandlerBufferSize is the number of log entries buffered between
// the (many) goroutines producing them and the single goroutine writing them
// out. Handle drops entries when this is full, so the buffer has to absorb
// bursts rather than merely smooth a steady rate.
//
// Bursts are exactly when the logs matter. A Redis failover, for instance, makes
// every shard's client and control PUB/SUB loop log connection errors, subscribe
// errors and lifecycle transitions at nearly the same instant, while the drain
// goroutine is rate-limited by writing to stdout. The previous value of 64 was
// overrun easily in that situation — and with log_level debug, which is what an
// operator turns on to investigate, it fills faster still.
const centrifugeLogHandlerBufferSize = 1024
type centrifugeLogHandler struct {
entries chan centrifuge.LogEntry
}
func newCentrifugeLogHandler() *centrifugeLogHandler {
h := &centrifugeLogHandler{
entries: make(chan centrifuge.LogEntry, centrifugeLogHandlerBufferSize),
}
go h.readEntries()
return h
}
func (h *centrifugeLogHandler) readEntries() {
for entry := range h.entries {
var l *zerolog.Event
switch entry.Level {
case centrifuge.LogLevelTrace:
l = log.Trace()
case centrifuge.LogLevelDebug:
l = log.Debug()
case centrifuge.LogLevelInfo:
l = log.Info()
case centrifuge.LogLevelWarn:
l = log.Warn()
case centrifuge.LogLevelError:
l = log.Error()
default:
continue
}
if entry.Fields != nil {
if entry.Error != nil {
l = l.Err(entry.Error)
delete(entry.Fields, "error")
}
l.Fields(entry.Fields).Msg(entry.Message)
} else {
if entry.Error != nil {
l = l.Err(entry.Error)
delete(entry.Fields, "error")
}
l.Msg(entry.Message)
}
}
}
func (h *centrifugeLogHandler) Handle(entry centrifuge.LogEntry) {
select {
case h.entries <- entry:
default:
return
}
}
func configureConsoleWriter() *zerolog.ConsoleWriter {
if isTerminalAttached() {
return &zerolog.ConsoleWriter{
Out: os.Stdout,
TimeFormat: "2006-01-02 15:04:05",
FormatLevel: logutils.ConsoleFormatLevel(),
FormatErrFieldName: logutils.ConsoleFormatErrFieldName(),
FormatErrFieldValue: logutils.ConsoleFormatErrFieldValue(),
}
}
return nil
}
func isTerminalAttached() bool {
return isatty.IsTerminal(os.Stdout.Fd()) && runtime.GOOS != "windows"
}
func Setup(cfg config.Config) (centrifuge.LogHandler, func()) {
var writers []io.Writer
var file *os.File
if cfg.Log.File != "" {
var err error
file, err = os.OpenFile(cfg.Log.File, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) //nolint:gosec // Common for logging.
if err != nil {
log.Fatal().Err(err).Msg("error opening log file")
}
writers = append(writers, file)
} else {
consoleWriter := configureConsoleWriter()
if consoleWriter != nil {
writers = append(writers, consoleWriter)
} else {
writers = append(writers, os.Stderr)
}
}
levelFound := true
logLevel, ok := logLevelMatches[strings.ToUpper(cfg.Log.Level)]
if !ok {
levelFound = false
logLevel = zerolog.InfoLevel
}
zerolog.SetGlobalLevel(logLevel)
if len(writers) > 0 {
mw := io.MultiWriter(writers...)
log.Logger = log.Output(mw)
}
if !levelFound {
log.Warn().Msgf("unknown log level %s, defaulting to INFO", cfg.Log.Level)
}
return newCentrifugeLogHandler().Handle, func() {
if file != nil {
_ = file.Close()
}
}
}
// Enabled checks if a specific logging level is enabled
func Enabled(level Level) bool {
return level >= zerolog.GlobalLevel()
}