snapshot of https://github.com/centrifugal/centrifugo.git (master, 2026-09-16; blobs > 131072B omitted)
This commit is contained in:
184
internal/logging/logging.go
Normal file
184
internal/logging/logging.go
Normal file
@@ -0,0 +1,184 @@
|
||||
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 := ¢rifugeLogHandler{
|
||||
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()
|
||||
}
|
||||
Reference in New Issue
Block a user