Skip to content

Commit

Permalink
Make logger state threadsafe
Browse files Browse the repository at this point in the history
Signed-off-by: Andreas Karis <[email protected]>
  • Loading branch information
andreaskaris committed Sep 27, 2023
1 parent 05dd7b4 commit 069d634
Show file tree
Hide file tree
Showing 2 changed files with 204 additions and 75 deletions.
245 changes: 187 additions & 58 deletions logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"path/filepath"
"runtime/debug"
"strings"
"sync"
"time"

lumberjack "gopkg.in/natefinch/lumberjack.v2"
Expand All @@ -39,12 +40,7 @@ const (
structuredPrefixerOddArguments = "prefixer must return an even number of arguments for structured logging"
)

var logger *lumberjack.Logger
var logWriter io.Writer
var logLevel Level
var logToStderr bool
var prefixer Prefixer
var structuredPrefixer StructuredPrefixer
var loggingState state

// LogOptions defines the configuration of the lumberjack logger
type LogOptions struct {
Expand All @@ -59,7 +55,7 @@ func init() {
}

func initLogger() {
logger = &lumberjack.Logger{}
loggingState.setLogger(&lumberjack.Logger{})

// Set default options.
SetLogOptions(nil)
Expand All @@ -74,12 +70,12 @@ func initLogger() {

// SetPrefixer allows overwriting the Prefixer with a custom one.
func SetPrefixer(p Prefixer) {
prefixer = p
loggingState.setPrefixer(p)
}

// SetStructuredPrefixer allows overwriting the StructuredPrefixer with a custom one.
func SetStructuredPrefixer(p StructuredPrefixer) {
structuredPrefixer = p
loggingState.setStructuredPrefixer(p)
}

// SetDefaultPrefixer sets the default Prefixer.
Expand All @@ -101,29 +97,10 @@ func SetDefaultStructuredPrefixer() {

// Set the logging options (LogOptions)
func SetLogOptions(options *LogOptions) {
// give some default value
logger.MaxSize = 100
logger.MaxAge = 5
logger.MaxBackups = 5
logger.Compress = true
if options != nil {
if options.MaxAge != nil {
logger.MaxAge = *options.MaxAge
}
if options.MaxSize != nil {
logger.MaxSize = *options.MaxSize
}
if options.MaxBackups != nil {
logger.MaxBackups = *options.MaxBackups
}
if options.Compress != nil {
logger.Compress = *options.Compress
}
}

loggingState.setLogOptions(options)
// Update the logWriter if necessary.
if isFileLoggingEnabled() {
logWriter = logger
if loggingState.isFileLoggingEnabled() {
loggingState.setLoggerAsLogWriter()
}
}

Expand All @@ -132,10 +109,10 @@ func SetLogFile(filename string) {
// Allow logging to stderr only. Print an error a single time when this is set to the empty string but stderr
// logging is off.
if filename == "" {
if !logToStderr {
if !loggingState.getLogToStderr() {
fmt.Fprint(os.Stderr, logFileReqFailMsg)
}
disableFileLogging()
loggingState.setLogFile("")
return
}

Expand All @@ -150,46 +127,34 @@ func SetLogFile(filename string) {
return
}

logger.Filename = filename
logWriter = logger
}

// disableFileLogging disables file logging.
func disableFileLogging() {
logger.Filename = ""
logWriter = nil
}

// isFileLoggingEnabled returns true if file logging is enabled.
func isFileLoggingEnabled() bool {
return logWriter != nil
loggingState.setLogFile(filename)
}

// GetLogLevel gets current logging level
func GetLogLevel() Level {
return logLevel
return loggingState.getLogLevel()
}

// SetLogLevel sets logging level
func SetLogLevel(level Level) {
if level.IsValid() {
logLevel = level
loggingState.setLogLevel(level)
} else {
fmt.Fprintf(os.Stderr, setLevelFailMsg, level)
}
}

// SetLogStderr sets flag for logging stderr output
func SetLogStderr(enable bool) {
if !enable && !isFileLoggingEnabled() {
if !enable && !loggingState.isFileLoggingEnabled() {
fmt.Fprint(os.Stderr, logFileReqFailMsg)
}
logToStderr = enable
loggingState.setLogToStderr(enable)
}

// SetOutput set custom output WARNING subsequent call to SetLogFile or SetLogOptions invalidates this setting
func SetOutput(out io.Writer) {
logWriter = out
loggingState.setLogWriter(out)
}

// Panicf prints logging plus stack trace. This should be used only for unrecoverable error
Expand Down Expand Up @@ -256,7 +221,7 @@ func DebugStructured(msg string, args ...interface{}) {

// structuredMessage takes msg and an even list of args and returns a structured message.
func structuredMessage(loggingLevel Level, msg string, args ...interface{}) string {
prefixArgs := structuredPrefixer.CreateStructuredPrefix(loggingLevel, msg)
prefixArgs := loggingState.getStructuredPrefixer().CreateStructuredPrefix(loggingLevel, msg)
if len(prefixArgs)%2 != 0 {
panic(fmt.Sprintf("msg=%q logging_failure=%q", msg, structuredPrefixerOddArguments))
}
Expand Down Expand Up @@ -297,24 +262,24 @@ func printf(level Level, format string, a ...interface{}) {
// printWithPrefixf prints log messages if they match the configured log level. Messages are optionally prepended by a
// configured prefix.
func printWithPrefixf(level Level, printPrefix bool, format string, a ...interface{}) {
if level > logLevel {
if level > loggingState.getLogLevel() {
return
}

if !isFileLoggingEnabled() && !logToStderr {
if !loggingState.isFileLoggingEnabled() && !loggingState.getLogToStderr() {
return
}

if printPrefix {
format = prefixer.CreatePrefix(level) + format
format = loggingState.getPrefixer().CreatePrefix(level) + format
}

if logToStderr {
if loggingState.getLogToStderr() {
doWritef(os.Stderr, format, a...)
}

if isFileLoggingEnabled() {
doWritef(logWriter, format, a...)
if loggingState.isFileLoggingEnabled() {
doWritef(loggingState.getLogWriter(), format, a...)
}
}

Expand Down Expand Up @@ -367,3 +332,167 @@ func resolvePath(path string) (string, error) {

return filepath.Clean(path), nil
}

// state is the struct for the loggingState singleton. It allows us to set all logger attributes in a threadsafe manner
// for as long as we always access all of its attributes via its methods.
type state struct {
loggerMutex sync.RWMutex
logger *lumberjack.Logger
logWriter io.Writer
logLevel Level
logToStderr bool
prefixer Prefixer
structuredPrefixer StructuredPrefixer
}

// setLogger sets s's logger.
func (s *state) setLogger(logger *lumberjack.Logger) {
s.loggerMutex.Lock()
defer s.loggerMutex.Unlock()

s.logger = logger
}

// getLogger gets s's logger.
func (s *state) getLogger() *lumberjack.Logger {
s.loggerMutex.RLock()
defer s.loggerMutex.RUnlock()

return s.logger
}

// setLogWriter sets s's logWriter.
func (s *state) setLogWriter(logWriter io.Writer) {
s.loggerMutex.Lock()
defer s.loggerMutex.Unlock()

s.logWriter = logWriter
}

// setLoggerAsLogWriter sets s's logWriter to a copy of its current logger.
func (s *state) setLoggerAsLogWriter() {
s.loggerMutex.Lock()
defer s.loggerMutex.Unlock()

cp := *s.logger
s.logWriter = &cp
}

// getLogWriter gets s's logWriter.
func (s *state) getLogWriter() io.Writer {
s.loggerMutex.RLock()
defer s.loggerMutex.RUnlock()

return s.logWriter
}

// setLogLevel sets s's logLeves.
func (s *state) setLogLevel(logLevel Level) {
s.loggerMutex.Lock()
defer s.loggerMutex.Unlock()

s.logLevel = logLevel
}

// getLogLevel gets s's logLeves.
func (s *state) getLogLevel() Level {
s.loggerMutex.RLock()
defer s.loggerMutex.RUnlock()

return s.logLevel
}

// setLogToStderr sets s's logToStderr.
func (s *state) setLogToStderr(logToStderr bool) {
s.loggerMutex.Lock()
defer s.loggerMutex.Unlock()

s.logToStderr = logToStderr
}

// getLogToStderr gets s's getLogToStderr.
func (s *state) getLogToStderr() bool {
s.loggerMutex.RLock()
defer s.loggerMutex.RUnlock()

return s.logToStderr
}

// setPrefixer sets s's prefixer.
func (s *state) setPrefixer(prefixer Prefixer) {
s.loggerMutex.Lock()
defer s.loggerMutex.Unlock()

s.prefixer = prefixer
}

// getPrefixer gets s's prefixer.
func (s *state) getPrefixer() Prefixer {
s.loggerMutex.RLock()
defer s.loggerMutex.RUnlock()

return s.prefixer
}

// setStructuredPrefixer sets s's structuredPrefixer.
func (s *state) setStructuredPrefixer(structuredPrefixer StructuredPrefixer) {
s.loggerMutex.Lock()
defer s.loggerMutex.Unlock()

s.structuredPrefixer = structuredPrefixer
}

// getStructuredPrefixer gets s's structuredPrefixer.
func (s *state) getStructuredPrefixer() StructuredPrefixer {
s.loggerMutex.RLock()
defer s.loggerMutex.RUnlock()

return s.structuredPrefixer
}

// isFileLoggingEnabled returns true if files *state is enabled.
func (s *state) isFileLoggingEnabled() bool {
return s.getLogWriter() != nil
}

// setLogFile sets s's files *state. This method sets s.logger.Filename to the specified value and then sets s.logWriter
// to a copy of s.logger. If filename is the empty string, logWriter is set to nil.
func (s *state) setLogFile(filename string) {
s.loggerMutex.Lock()
defer s.loggerMutex.Unlock()

if filename == "" {
s.logger.Filename = ""
s.logWriter = nil
return
}
s.logger.Filename = filename
cp := *(s.logger)
s.logWriter = &cp
}

// setLogOpt8ions sets s's files *state options.
func (s *state) setLogOptions(options *LogOptions) {
s.loggerMutex.Lock()
defer s.loggerMutex.Unlock()

// give some default value
s.logger.MaxSize = 100
s.logger.MaxAge = 5
s.logger.MaxBackups = 5
s.logger.Compress = true
if options != nil {
if options.MaxAge != nil {
s.logger.MaxAge = *options.MaxAge
}
if options.MaxSize != nil {
s.logger.MaxSize = *options.MaxSize
}
if options.MaxBackups != nil {
s.logger.MaxBackups = *options.MaxBackups
}
if options.Compress != nil {
s.logger.Compress = *options.Compress
}
}
}
Loading

0 comments on commit 069d634

Please sign in to comment.