github.com/mdaxf/iac@v0.0.0-20240519030858-58a061660378/vendor_skip/go.mongodb.org/mongo-driver/internal/logger/logger.go (about) 1 // Copyright (C) MongoDB, Inc. 2023-present. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); you may 4 // not use this file except in compliance with the License. You may obtain 5 // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 7 package logger 8 9 import ( 10 "fmt" 11 "os" 12 "strconv" 13 "strings" 14 ) 15 16 // DefaultMaxDocumentLength is the default maximum number of bytes that can be 17 // logged for a stringified BSON document. 18 const DefaultMaxDocumentLength = 1000 19 20 // TruncationSuffix are trailling ellipsis "..." appended to a message to 21 // indicate to the user that truncation occurred. This constant does not count 22 // toward the max document length. 23 const TruncationSuffix = "..." 24 25 const logSinkPathEnvVar = "MONGODB_LOG_PATH" 26 const maxDocumentLengthEnvVar = "MONGODB_LOG_MAX_DOCUMENT_LENGTH" 27 28 // LogSink represents a logging implementation, this interface should be 1-1 29 // with the exported "LogSink" interface in the mongo/options package. 30 type LogSink interface { 31 // Info logs a non-error message with the given key/value pairs. The 32 // level argument is provided for optional logging. 33 Info(level int, msg string, keysAndValues ...interface{}) 34 35 // Error logs an error, with the given message and key/value pairs. 36 Error(err error, msg string, keysAndValues ...interface{}) 37 } 38 39 // Logger represents the configuration for the internal logger. 40 type Logger struct { 41 ComponentLevels map[Component]Level // Log levels for each component. 42 Sink LogSink // LogSink for log printing. 43 MaxDocumentLength uint // Command truncation width. 44 logFile *os.File // File to write logs to. 45 } 46 47 // New will construct a new logger. If any of the given options are the 48 // zero-value of the argument type, then the constructor will attempt to 49 // source the data from the environment. If the environment has not been set, 50 // then the constructor will the respective default values. 51 func New(sink LogSink, maxDocLen uint, compLevels map[Component]Level) (*Logger, error) { 52 logger := &Logger{ 53 ComponentLevels: selectComponentLevels(compLevels), 54 MaxDocumentLength: selectMaxDocumentLength(maxDocLen), 55 } 56 57 sink, logFile, err := selectLogSink(sink) 58 if err != nil { 59 return nil, err 60 } 61 62 logger.Sink = sink 63 logger.logFile = logFile 64 65 return logger, nil 66 } 67 68 // Close will close the logger's log file, if it exists. 69 func (logger *Logger) Close() error { 70 if logger.logFile != nil { 71 return logger.logFile.Close() 72 } 73 74 return nil 75 } 76 77 // LevelComponentEnabled will return true if the given LogLevel is enabled for 78 // the given LogComponent. If the ComponentLevels on the logger are enabled for 79 // "ComponentAll", then this function will return true for any level bound by 80 // the level assigned to "ComponentAll". 81 // 82 // If the level is not enabled (i.e. LevelOff), then false is returned. This is 83 // to avoid false positives, such as returning "true" for a component that is 84 // not enabled. For example, without this condition, an empty LevelComponent 85 // would be considered "enabled" for "LevelOff". 86 func (logger *Logger) LevelComponentEnabled(level Level, component Component) bool { 87 if level == LevelOff { 88 return false 89 } 90 91 if logger.ComponentLevels == nil { 92 return false 93 } 94 95 return logger.ComponentLevels[component] >= level || 96 logger.ComponentLevels[ComponentAll] >= level 97 } 98 99 // Print will synchronously print the given message to the configured LogSink. 100 // If the LogSink is nil, then this method will do nothing. Future work could be done to make 101 // this method asynchronous, see buffer management in libraries such as log4j. 102 func (logger *Logger) Print(level Level, component Component, msg string, keysAndValues ...interface{}) { 103 // If the level is not enabled for the component, then 104 // skip the message. 105 if !logger.LevelComponentEnabled(level, component) { 106 return 107 } 108 109 // If the sink is nil, then skip the message. 110 if logger.Sink == nil { 111 return 112 } 113 114 logger.Sink.Info(int(level)-DiffToInfo, msg, keysAndValues...) 115 } 116 117 // Error logs an error, with the given message and key/value pairs. 118 // It functions similarly to Print, but may have unique behavior, and should be 119 // preferred for logging errors. 120 func (logger *Logger) Error(err error, msg string, keysAndValues ...interface{}) { 121 if logger.Sink == nil { 122 return 123 } 124 125 logger.Sink.Error(err, msg, keysAndValues...) 126 } 127 128 // selectMaxDocumentLength will return the integer value of the first non-zero 129 // function, with the user-defined function taking priority over the environment 130 // variables. For the environment, the function will attempt to get the value of 131 // "MONGODB_LOG_MAX_DOCUMENT_LENGTH" and parse it as an unsigned integer. If the 132 // environment variable is not set or is not an unsigned integer, then this 133 // function will return the default max document length. 134 func selectMaxDocumentLength(maxDocLen uint) uint { 135 if maxDocLen != 0 { 136 return maxDocLen 137 } 138 139 maxDocLenEnv := os.Getenv(maxDocumentLengthEnvVar) 140 if maxDocLenEnv != "" { 141 maxDocLenEnvInt, err := strconv.ParseUint(maxDocLenEnv, 10, 32) 142 if err == nil { 143 return uint(maxDocLenEnvInt) 144 } 145 } 146 147 return DefaultMaxDocumentLength 148 } 149 150 const ( 151 logSinkPathStdout = "stdout" 152 logSinkPathStderr = "stderr" 153 ) 154 155 // selectLogSink will return the first non-nil LogSink, with the user-defined 156 // LogSink taking precedence over the environment-defined LogSink. If no LogSink 157 // is defined, then this function will return a LogSink that writes to stderr. 158 func selectLogSink(sink LogSink) (LogSink, *os.File, error) { 159 if sink != nil { 160 return sink, nil, nil 161 } 162 163 path := os.Getenv(logSinkPathEnvVar) 164 lowerPath := strings.ToLower(path) 165 166 if lowerPath == string(logSinkPathStderr) { 167 return NewIOSink(os.Stderr), nil, nil 168 } 169 170 if lowerPath == string(logSinkPathStdout) { 171 return NewIOSink(os.Stdout), nil, nil 172 } 173 174 if path != "" { 175 logFile, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666) 176 if err != nil { 177 return nil, nil, fmt.Errorf("unable to open log file: %v", err) 178 } 179 180 return NewIOSink(logFile), logFile, nil 181 } 182 183 return NewIOSink(os.Stderr), nil, nil 184 } 185 186 // selectComponentLevels returns a new map of LogComponents to LogLevels that is 187 // the result of merging the user-defined data with the environment, with the 188 // user-defined data taking priority. 189 func selectComponentLevels(componentLevels map[Component]Level) map[Component]Level { 190 selected := make(map[Component]Level) 191 192 // Determine if the "MONGODB_LOG_ALL" environment variable is set. 193 var globalEnvLevel *Level 194 if all := os.Getenv(mongoDBLogAllEnvVar); all != "" { 195 level := ParseLevel(all) 196 globalEnvLevel = &level 197 } 198 199 for envVar, component := range componentEnvVarMap { 200 // If the component already has a level, then skip it. 201 if _, ok := componentLevels[component]; ok { 202 selected[component] = componentLevels[component] 203 204 continue 205 } 206 207 // If the "MONGODB_LOG_ALL" environment variable is set, then 208 // set the level for the component to the value of the 209 // environment variable. 210 if globalEnvLevel != nil { 211 selected[component] = *globalEnvLevel 212 213 continue 214 } 215 216 // Otherwise, set the level for the component to the value of 217 // the environment variable. 218 selected[component] = ParseLevel(os.Getenv(envVar)) 219 } 220 221 return selected 222 } 223 224 // truncate will truncate a string to the given width, appending "..." to the 225 // end of the string if it is truncated. This routine is safe for multi-byte 226 // characters. 227 func truncate(str string, width uint) string { 228 if width == 0 { 229 return "" 230 } 231 232 if len(str) <= int(width) { 233 return str 234 } 235 236 // Truncate the byte slice of the string to the given width. 237 newStr := str[:width] 238 239 // Check if the last byte is at the beginning of a multi-byte character. 240 // If it is, then remove the last byte. 241 if newStr[len(newStr)-1]&0xC0 == 0xC0 { 242 return newStr[:len(newStr)-1] + TruncationSuffix 243 } 244 245 // Check if the last byte is in the middle of a multi-byte character. If 246 // it is, then step back until we find the beginning of the character. 247 if newStr[len(newStr)-1]&0xC0 == 0x80 { 248 for i := len(newStr) - 1; i >= 0; i-- { 249 if newStr[i]&0xC0 == 0xC0 { 250 return newStr[:i] + TruncationSuffix 251 } 252 } 253 } 254 255 return newStr + TruncationSuffix 256 } 257 258 // FormatMessage formats a BSON document for logging. The document is truncated 259 // to the given width. 260 func FormatMessage(msg string, width uint) string { 261 if len(msg) == 0 { 262 return "{}" 263 } 264 265 return truncate(msg, width) 266 }