github.com/letsencrypt/boulder@v0.20251208.0/log/log.go (about) 1 package log 2 3 import ( 4 "encoding/base64" 5 "encoding/binary" 6 "encoding/json" 7 "errors" 8 "fmt" 9 "hash/crc32" 10 "io" 11 "log/syslog" 12 "os" 13 "strings" 14 "sync" 15 16 "github.com/jmhodges/clock" 17 "golang.org/x/term" 18 19 "github.com/letsencrypt/boulder/core" 20 ) 21 22 // A Logger logs messages with explicit priority levels. It is 23 // implemented by a logging back-end as provided by New() or 24 // NewMock(). Any additions to this interface with format strings should be 25 // added to the govet configuration in .golangci.yml 26 type Logger interface { 27 Err(msg string) 28 Errf(format string, a ...any) 29 Warning(msg string) 30 Warningf(format string, a ...any) 31 Info(msg string) 32 Infof(format string, a ...any) 33 InfoObject(string, any) 34 Debug(msg string) 35 Debugf(format string, a ...any) 36 AuditInfo(msg string) 37 AuditInfof(format string, a ...any) 38 AuditObject(string, any) 39 AuditErr(string) 40 AuditErrf(format string, a ...any) 41 } 42 43 // impl implements Logger. 44 type impl struct { 45 w writer 46 } 47 48 // singleton defines the object of a Singleton pattern 49 type singleton struct { 50 once sync.Once 51 log Logger 52 } 53 54 // _Singleton is the single impl entity in memory 55 var _Singleton singleton 56 57 // The constant used to identify audit-specific messages 58 const auditTag = "[AUDIT]" 59 60 // New returns a new Logger that uses the given syslog.Writer as a backend 61 // and also writes to stdout/stderr. It is safe for concurrent use. 62 func New(log *syslog.Writer, stdoutLogLevel int, syslogLogLevel int) (Logger, error) { 63 if log == nil { 64 return nil, errors.New("Attempted to use a nil System Logger") 65 } 66 return &impl{ 67 &bothWriter{ 68 sync.Mutex{}, 69 log, 70 newStdoutWriter(stdoutLogLevel), 71 syslogLogLevel, 72 }, 73 }, nil 74 } 75 76 // StdoutLogger returns a Logger that writes solely to stdout and stderr. 77 // It is safe for concurrent use. 78 func StdoutLogger(level int) Logger { 79 return &impl{newStdoutWriter(level)} 80 } 81 82 func newStdoutWriter(level int) *stdoutWriter { 83 prefix, clkFormat := getPrefix() 84 return &stdoutWriter{ 85 prefix: prefix, 86 level: level, 87 clkFormat: clkFormat, 88 clk: clock.New(), 89 stdout: os.Stdout, 90 stderr: os.Stderr, 91 isatty: term.IsTerminal(int(os.Stdout.Fd())), 92 } 93 } 94 95 // initialize is used in unit tests and called by `Get` before the logger 96 // is fully set up. 97 func initialize() { 98 const defaultPriority = syslog.LOG_INFO | syslog.LOG_LOCAL0 99 syslogger, err := syslog.Dial("", "", defaultPriority, "test") 100 if err != nil { 101 panic(err) 102 } 103 logger, err := New(syslogger, int(syslog.LOG_DEBUG), int(syslog.LOG_DEBUG)) 104 if err != nil { 105 panic(err) 106 } 107 108 _ = Set(logger) 109 } 110 111 // Set configures the singleton Logger. This method 112 // must only be called once, and before calling Get the 113 // first time. 114 func Set(logger Logger) (err error) { 115 if _Singleton.log != nil { 116 err = errors.New("You may not call Set after it has already been implicitly or explicitly set") 117 _Singleton.log.Warning(err.Error()) 118 } else { 119 _Singleton.log = logger 120 } 121 return 122 } 123 124 // Get obtains the singleton Logger. If Set has not been called first, this 125 // method initializes with basic defaults. The basic defaults cannot error, and 126 // subsequent access to an already-set Logger also cannot error, so this method is 127 // error-safe. 128 func Get() Logger { 129 _Singleton.once.Do(func() { 130 if _Singleton.log == nil { 131 initialize() 132 } 133 }) 134 135 return _Singleton.log 136 } 137 138 type writer interface { 139 logAtLevel(syslog.Priority, string, ...any) 140 } 141 142 // bothWriter implements writer and writes to both syslog and stdout. 143 type bothWriter struct { 144 sync.Mutex 145 *syslog.Writer 146 *stdoutWriter 147 syslogLevel int 148 } 149 150 // stdoutWriter implements writer and writes just to stdout. 151 type stdoutWriter struct { 152 // prefix is a set of information that is the same for every log line, 153 // imitating what syslog emits for us when we use the syslog writer. 154 prefix string 155 level int 156 clkFormat string 157 clk clock.Clock 158 stdout io.Writer 159 stderr io.Writer 160 isatty bool 161 } 162 163 // LogLineChecksum computes a CRC32 over the log line, which can be checked by 164 // log-validator to ensure no unexpected log corruption has occurred. 165 func LogLineChecksum(line string) string { 166 crc := crc32.ChecksumIEEE([]byte(line)) 167 buf := make([]byte, crc32.Size) 168 // Error is unreachable because we provide a supported type and buffer size 169 _, _ = binary.Encode(buf, binary.LittleEndian, crc) 170 return base64.RawURLEncoding.EncodeToString(buf) 171 } 172 173 func checkSummed(msg string) string { 174 return fmt.Sprintf("%s %s", LogLineChecksum(msg), msg) 175 } 176 177 // logAtLevel logs the provided message at the appropriate level, writing to 178 // both stdout and the Logger 179 func (w *bothWriter) logAtLevel(level syslog.Priority, msg string, a ...any) { 180 var err error 181 182 // Apply conditional formatting for f functions 183 if a != nil { 184 msg = fmt.Sprintf(msg, a...) 185 } 186 187 // Since messages are delimited by newlines, we have to escape any internal or 188 // trailing newlines before generating the checksum or outputting the message. 189 msg = strings.Replace(msg, "\n", "\\n", -1) 190 191 w.Lock() 192 defer w.Unlock() 193 194 switch syslogAllowed := int(level) <= w.syslogLevel; level { 195 case syslog.LOG_ERR: 196 if syslogAllowed { 197 err = w.Err(checkSummed(msg)) 198 } 199 case syslog.LOG_WARNING: 200 if syslogAllowed { 201 err = w.Warning(checkSummed(msg)) 202 } 203 case syslog.LOG_INFO: 204 if syslogAllowed { 205 err = w.Info(checkSummed(msg)) 206 } 207 case syslog.LOG_DEBUG: 208 if syslogAllowed { 209 err = w.Debug(checkSummed(msg)) 210 } 211 default: 212 err = w.Err(fmt.Sprintf("%s (unknown logging level: %d)", checkSummed(msg), int(level))) 213 } 214 215 if err != nil { 216 fmt.Fprintf(os.Stderr, "Failed to write to syslog: %d %s (%s)\n", int(level), checkSummed(msg), err) 217 } 218 219 w.stdoutWriter.logAtLevel(level, msg) 220 } 221 222 // logAtLevel logs the provided message to stdout, or stderr if it is at Warning or Error level. 223 func (w *stdoutWriter) logAtLevel(level syslog.Priority, msg string, a ...any) { 224 if int(level) <= w.level { 225 output := w.stdout 226 if int(level) <= int(syslog.LOG_WARNING) { 227 output = w.stderr 228 } 229 230 // Apply conditional formatting for f functions 231 if a != nil { 232 msg = fmt.Sprintf(msg, a...) 233 } 234 235 msg = strings.Replace(msg, "\n", "\\n", -1) 236 237 var color string 238 var reset string 239 240 const red = "\033[31m\033[1m" 241 const yellow = "\033[33m" 242 const gray = "\033[37m\033[2m" 243 244 if w.isatty { 245 if int(level) == int(syslog.LOG_DEBUG) { 246 color = gray 247 reset = "\033[0m" 248 } else if int(level) == int(syslog.LOG_WARNING) { 249 color = yellow 250 reset = "\033[0m" 251 } else if int(level) <= int(syslog.LOG_ERR) { 252 color = red 253 reset = "\033[0m" 254 } 255 } 256 257 if _, err := fmt.Fprintf(output, "%s%s %s%d %s %s%s\n", 258 color, 259 w.clk.Now().UTC().Format(w.clkFormat), 260 w.prefix, 261 int(level), 262 core.Command(), 263 checkSummed(msg), 264 reset); err != nil { 265 panic(fmt.Sprintf("failed to write to stdout: %v\n", err)) 266 } 267 } 268 } 269 270 func (log *impl) auditAtLevel(level syslog.Priority, msg string, a ...any) { 271 msg = fmt.Sprintf("%s %s", auditTag, msg) 272 log.w.logAtLevel(level, msg, a...) 273 } 274 275 // Err level messages are always marked with the audit tag, for special handling 276 // at the upstream system logger. 277 func (log *impl) Err(msg string) { 278 log.Errf(msg) 279 } 280 281 // Errf level messages are always marked with the audit tag, for special handling 282 // at the upstream system logger. 283 func (log *impl) Errf(format string, a ...any) { 284 log.auditAtLevel(syslog.LOG_ERR, format, a...) 285 } 286 287 // Warning level messages pass through normally. 288 func (log *impl) Warning(msg string) { 289 log.Warningf(msg) 290 } 291 292 // Warningf level messages pass through normally. 293 func (log *impl) Warningf(format string, a ...any) { 294 log.w.logAtLevel(syslog.LOG_WARNING, format, a...) 295 } 296 297 // Info level messages pass through normally. 298 func (log *impl) Info(msg string) { 299 log.Infof(msg) 300 } 301 302 // Infof level messages pass through normally. 303 func (log *impl) Infof(format string, a ...any) { 304 log.w.logAtLevel(syslog.LOG_INFO, format, a...) 305 } 306 307 // InfoObject logs an INFO level JSON-serialized object message. 308 func (log *impl) InfoObject(msg string, obj any) { 309 jsonObj, err := json.Marshal(obj) 310 if err != nil { 311 log.auditAtLevel(syslog.LOG_ERR, fmt.Sprintf("Object for msg %q could not be serialized to JSON. Raw: %+v", msg, obj)) 312 return 313 } 314 315 log.Infof("%s JSON=%s", msg, jsonObj) 316 } 317 318 // Debug level messages pass through normally. 319 func (log *impl) Debug(msg string) { 320 log.Debugf(msg) 321 322 } 323 324 // Debugf level messages pass through normally. 325 func (log *impl) Debugf(format string, a ...any) { 326 log.w.logAtLevel(syslog.LOG_DEBUG, format, a...) 327 } 328 329 // AuditInfo sends an INFO-severity message that is prefixed with the 330 // audit tag, for special handling at the upstream system logger. 331 func (log *impl) AuditInfo(msg string) { 332 log.AuditInfof(msg) 333 } 334 335 // AuditInfof sends an INFO-severity message that is prefixed with the 336 // audit tag, for special handling at the upstream system logger. 337 func (log *impl) AuditInfof(format string, a ...any) { 338 log.auditAtLevel(syslog.LOG_INFO, format, a...) 339 } 340 341 // AuditObject sends an INFO-severity JSON-serialized object message that is prefixed 342 // with the audit tag, for special handling at the upstream system logger. 343 func (log *impl) AuditObject(msg string, obj any) { 344 jsonObj, err := json.Marshal(obj) 345 if err != nil { 346 log.auditAtLevel(syslog.LOG_ERR, fmt.Sprintf("Object for msg %q could not be serialized to JSON. Raw: %+v", msg, obj)) 347 return 348 } 349 350 log.auditAtLevel(syslog.LOG_INFO, fmt.Sprintf("%s JSON=%s", msg, jsonObj)) 351 } 352 353 // AuditErr can format an error for auditing; it does so at ERR level. 354 func (log *impl) AuditErr(msg string) { 355 log.AuditErrf(msg) 356 } 357 358 // AuditErrf can format an error for auditing; it does so at ERR level. 359 func (log *impl) AuditErrf(format string, a ...any) { 360 log.auditAtLevel(syslog.LOG_ERR, format, a...) 361 }