github.com/decred/politeia@v1.4.0/politeiawww/logger/logger.go (about) 1 // Copyright (c) 2017-2021 The Decred developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 5 package logger 6 7 import ( 8 "fmt" 9 "os" 10 "path/filepath" 11 "sort" 12 13 "github.com/decred/slog" 14 "github.com/jrick/logrotate/rotator" 15 ) 16 17 // logWriter implements an io.Writer that outputs to both standard output and 18 // the write-end pipe of an initialized log rotator. 19 type logWriter struct{} 20 21 func (logWriter) Write(p []byte) (n int, err error) { 22 os.Stdout.Write(p) 23 if logRotator == nil { 24 return 0, nil 25 } 26 return logRotator.Write(p) 27 } 28 29 // Loggers per subsystem. A single backend logger is created and all subsytem 30 // loggers created from it will write to the backend. When adding new 31 // subsystems, add the subsystem logger variable here and to the 32 // subsystemLoggers map. 33 // 34 // Loggers can not be used before the log rotator has been initialized with a 35 // log file. This must be performed early during application startup by calling 36 // initLogRotator. 37 var ( 38 // backendLog is the logging backend used to create all subsystem loggers. 39 // The backend must not be used before the log rotator has been initialized, 40 // or data races and/or nil pointer dereferences will occur. 41 backendLog = slog.NewBackend(logWriter{}) 42 43 // logRotator is one of the logging outputs. It should be closed on 44 // application shutdown. 45 logRotator *rotator.Rotator 46 47 // subsystemsLoggers contains all of the subsystem loggers. A new subsystem 48 // logger is registered using Register(). 49 subsystemLoggers = map[string]slog.Logger{} 50 ) 51 52 // InitLogRotator initializes the logging rotater to write logs to logFile and 53 // create roll files in the same directory. It must be called before the 54 // package-global log rotater variables are used. 55 func InitLogRotator(logFile string) { 56 logDir, _ := filepath.Split(logFile) 57 err := os.MkdirAll(logDir, 0700) 58 if err != nil { 59 fmt.Fprintf(os.Stderr, "failed to create log directory: %v\n", err) 60 os.Exit(1) 61 } 62 r, err := rotator.New(logFile, 10*1024, false, 3) 63 if err != nil { 64 fmt.Fprintf(os.Stderr, "failed to create file rotator: %v\n", err) 65 os.Exit(1) 66 } 67 68 logRotator = r 69 } 70 71 // CloseLogRotator closes the log rotator. 72 func CloseLogRotator() { 73 if logRotator != nil { 74 logRotator.Close() 75 } 76 } 77 78 // NewSubsystem registers and returns a new subsystem logger. 79 func NewSubsystem(subsystemTag string) slog.Logger { 80 l := backendLog.Logger(subsystemTag) 81 subsystemLoggers[subsystemTag] = l 82 return l 83 } 84 85 // SupportedSubsystems returns a sorted slice of the supported subsystems for 86 // logging purposes. 87 func SupportedSubsystems() []string { 88 // Convert the subsystemLoggers map keys to a slice. 89 subsystems := make([]string, 0, len(subsystemLoggers)) 90 for subsysID := range subsystemLoggers { 91 subsystems = append(subsystems, subsysID) 92 } 93 94 // Sort the subsytems for stable display. 95 sort.Strings(subsystems) 96 return subsystems 97 } 98 99 // SetLogLevel sets the logging level for provided subsystem. Invalid 100 // subsystems are ignored. Uninitialized subsystems are dynamically created as 101 // needed. 102 func SetLogLevel(subsystemID string, logLevel string) { 103 // Ignore invalid subsystems. 104 logger, ok := subsystemLoggers[subsystemID] 105 if !ok { 106 return 107 } 108 109 // Defaults to info if the log level is invalid. 110 level, _ := slog.LevelFromString(logLevel) 111 logger.SetLevel(level) 112 } 113 114 // SetLogLevels sets the log level for all subsystem loggers to the passed 115 // level. It also dynamically creates the subsystem loggers as needed, so it 116 // can be used to initialize the logging system. 117 func SetLogLevels(logLevel string) { 118 // Configure all sub-systems with the new logging level. Dynamically 119 // create loggers as needed. 120 for subsystemID := range subsystemLoggers { 121 SetLogLevel(subsystemID, logLevel) 122 } 123 } 124 125 // LogClosure is a closure that can be printed with %v to be used to 126 // generate expensive-to-create data for a detailed log level and avoid doing 127 // the work if the data isn't printed. 128 type LogClosure func() string 129 130 func (c LogClosure) String() string { 131 return c() 132 } 133 134 // NewLogClosure returns a new LogClosure. 135 func NewLogClosure(c func() string) LogClosure { 136 return LogClosure(c) 137 }