internal/logger/logger.go (130 lines of code) (raw):

// Copyright 2020 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package logger import ( "context" "fmt" "io" "log/slog" "log/syslog" "os" "runtime/debug" "github.com/googlecloudplatform/gcsfuse/v2/cfg" "gopkg.in/natefinch/lumberjack.v2" ) // Syslog file contains logs from all different programmes running on the VM. // ProgrammeName is prefixed to all the logs written to syslog. This constant is // used to filter the logs from syslog and write it to respective log files - // gcsfuse.log in case of GCSFuse. const ( ProgrammeName string = "gcsfuse" GCSFuseInBackgroundMode string = "GCSFUSE_IN_BACKGROUND_MODE" textFormat string = "text" ) var ( defaultLoggerFactory *loggerFactory defaultLogger *slog.Logger ) // InitLogFile initializes the logger factory to create loggers that print to // a log file. // In case of empty file, it starts writing the log to syslog file, which // is eventually filtered and redirected to a fixed location using syslog // config. // Here, background true means, this InitLogFile has been called for the // background daemon. func InitLogFile(newLogConfig cfg.LoggingConfig) error { var f *os.File var sysWriter *syslog.Writer var fileWriter *lumberjack.Logger var err error if newLogConfig.FilePath != "" { f, err = os.OpenFile( string(newLogConfig.FilePath), os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644, ) if err != nil { return err } fileWriter = &lumberjack.Logger{ Filename: f.Name(), MaxSize: int(newLogConfig.LogRotate.MaxFileSizeMb), MaxBackups: int(newLogConfig.LogRotate.BackupFileCount), Compress: newLogConfig.LogRotate.Compress, } } else { if _, ok := os.LookupEnv(GCSFuseInBackgroundMode); ok { // Priority consist of facility and severity, here facility to specify the // type of system that is logging the message to syslog and severity is log-level. // User applications are allowed to take facility value between LOG_LOCAL0 // to LOG_LOCAL7. We are using LOG_LOCAL7 as facility and LOG_DEBUG to write // debug messages. // Suppressing the error while creating the syslog, although logger will // be initialised with stdout/err, log will be printed anywhere. Because, // in this case gcsfuse will be running as daemon. sysWriter, _ = syslog.New(syslog.LOG_LOCAL7|syslog.LOG_DEBUG, ProgrammeName) } } defaultLoggerFactory = &loggerFactory{ file: f, sysWriter: sysWriter, fileWriter: fileWriter, format: newLogConfig.Format, level: string(newLogConfig.Severity), logRotate: newLogConfig.LogRotate, } defaultLogger = defaultLoggerFactory.newLogger(string(newLogConfig.Severity)) return nil } // init initializes the logger factory to use stdout and stderr. func init() { logConfig := cfg.DefaultLoggingConfig() defaultLoggerFactory = &loggerFactory{ file: nil, format: logConfig.Format, level: string(logConfig.Severity), // setting log level to INFO by default logRotate: logConfig.LogRotate, } defaultLogger = defaultLoggerFactory.newLogger(cfg.INFO) } // SetLogFormat updates the log format of default logger. func SetLogFormat(format string) { if format == defaultLoggerFactory.format { return } defaultLoggerFactory.format = format defaultLogger = defaultLoggerFactory.newLogger(defaultLoggerFactory.level) } // Tracef prints the message with TRACE severity in the specified format. func Tracef(format string, v ...interface{}) { defaultLogger.Log(context.Background(), LevelTrace, fmt.Sprintf(format, v...)) } // Debugf prints the message with DEBUG severity in the specified format. func Debugf(format string, v ...interface{}) { defaultLogger.Debug(fmt.Sprintf(format, v...)) } // Infof prints the message with INFO severity in the specified format. func Infof(format string, v ...interface{}) { defaultLogger.Info(fmt.Sprintf(format, v...)) } // Info prints the message with info severity. func Info(message string, args ...any) { defaultLogger.Info(message, args...) } // Warnf prints the message with WARNING severity in the specified format. func Warnf(format string, v ...interface{}) { defaultLogger.Warn(fmt.Sprintf(format, v...)) } // Errorf prints the message with ERROR severity in the specified format. func Errorf(format string, v ...interface{}) { defaultLogger.Error(fmt.Sprintf(format, v...)) } // Error prints the message with ERROR severity. func Error(error string) { defaultLogger.Error(error) } // Fatal prints an error log and exits with non-zero exit code. func Fatal(format string, v ...interface{}) { Errorf(format, v...) Error(string(debug.Stack())) os.Exit(1) } type loggerFactory struct { // If nil, log to stdout or stderr. Otherwise, log to this file. file *os.File sysWriter *syslog.Writer format string level string logRotate cfg.LogRotateLoggingConfig fileWriter *lumberjack.Logger } func (f *loggerFactory) newLogger(level string) *slog.Logger { // create a new logger var programLevel = new(slog.LevelVar) logger := slog.New(f.handler(programLevel, "")) slog.SetDefault(logger) setLoggingLevel(level, programLevel) return logger } func (f *loggerFactory) createJsonOrTextHandler(writer io.Writer, levelVar *slog.LevelVar, prefix string) slog.Handler { if f.format == textFormat { return slog.NewTextHandler(writer, getHandlerOptions(levelVar, prefix, f.format)) } return slog.NewJSONHandler(writer, getHandlerOptions(levelVar, prefix, f.format)) } func (f *loggerFactory) handler(levelVar *slog.LevelVar, prefix string) slog.Handler { if f.fileWriter != nil { return f.createJsonOrTextHandler(f.fileWriter, levelVar, prefix) } if f.sysWriter != nil { return f.createJsonOrTextHandler(f.sysWriter, levelVar, prefix) } return f.createJsonOrTextHandler(os.Stdout, levelVar, prefix) }