github.com/facebookincubator/go-belt@v0.0.0-20230703220935-39cd348f1a38/tool/logger/adapter/generic_logger.go (about) 1 // Copyright 2022 Meta Platforms, Inc. and affiliates. 2 // 3 // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 // 5 // 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 // 7 // 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 // 9 // 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 // 11 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 13 package adapter 14 15 import ( 16 "fmt" 17 "os" 18 "strings" 19 "time" 20 21 "github.com/facebookincubator/go-belt" 22 "github.com/facebookincubator/go-belt/pkg/field" 23 "github.com/facebookincubator/go-belt/pkg/valuesparser" 24 "github.com/facebookincubator/go-belt/tool/logger/experimental" 25 "github.com/facebookincubator/go-belt/tool/logger/types" 26 ) 27 28 // GenericLogger is a generic implementation of types.CompactLogger given 29 // Emitters. 30 type GenericLogger struct { 31 Emitters types.Emitters 32 CurrentLevel types.Level 33 Fields *field.FieldsChain 34 TraceIDs belt.TraceIDs 35 CurrentPreHooks types.PreHooks 36 CurrentHooks types.Hooks 37 MessagePrefix string 38 GetCallerFunc types.GetCallerPC 39 EntryProperties types.EntryProperties 40 } 41 42 var _ CompactLogger = (*GenericLogger)(nil) 43 44 // Level implements types.CompactLogger. 45 func (l *GenericLogger) Level() types.Level { 46 return l.CurrentLevel 47 } 48 49 // WithTraceIDs implements types.CompactLogger. 50 func (l *GenericLogger) WithTraceIDs(allTraceIDs belt.TraceIDs, newTraceIDs int) belt.Tool { 51 newLogger := *l 52 newLogger.TraceIDs = allTraceIDs 53 return &newLogger 54 } 55 56 // WithLevel implements types.CompactLogger. 57 func (l *GenericLogger) WithLevel(newLevel types.Level) CompactLogger { 58 newLogger := *l 59 newLogger.CurrentLevel = newLevel 60 return &newLogger 61 } 62 63 // WithPreHooks implements types.CompactLogger. 64 func (l *GenericLogger) WithPreHooks(preHooks ...types.PreHook) CompactLogger { 65 newLogger := *l 66 if preHooks == nil { 67 newLogger.CurrentPreHooks = nil 68 } else { 69 newLogger.CurrentPreHooks = types.PreHooks{newLogger.CurrentPreHooks, types.PreHooks(preHooks)} 70 } 71 return &newLogger 72 } 73 74 // WithHooks implements types.CompactLogger. 75 func (l *GenericLogger) WithHooks(hooks ...types.Hook) CompactLogger { 76 newLogger := *l 77 if hooks == nil { 78 newLogger.CurrentHooks = nil 79 } else { 80 newLogger.CurrentHooks = types.Hooks{newLogger.CurrentHooks, types.Hooks(hooks)} 81 } 82 return &newLogger 83 } 84 85 // WithField implements types.CompactLogger. 86 func (l *GenericLogger) WithField( 87 key field.Key, 88 value field.Value, 89 props ...field.Property, 90 ) CompactLogger { 91 newLogger := *l 92 newLogger.Fields = newLogger.Fields.WithField(key, value, props...) 93 return &newLogger 94 } 95 96 // WithFields implements types.CompactLogger. 97 func (l *GenericLogger) WithFields(fields field.AbstractFields) CompactLogger { 98 newLogger := *l 99 if fields == nil { 100 return &newLogger 101 } 102 newLogger.Fields = newLogger.Fields.WithFields(fields) 103 return &newLogger 104 } 105 106 // WithContextFields implements types.CompactLogger. 107 func (l *GenericLogger) WithContextFields(allFields *field.FieldsChain, newFields int) belt.Tool { 108 newLogger := *l 109 newLogger.Fields = allFields 110 return &newLogger 111 } 112 113 // Emitter implements types.CompactLogger. 114 func (l *GenericLogger) Emitter() types.Emitter { 115 return l.Emitters 116 } 117 118 // Log implements types.CompactLogger. 119 func (l *GenericLogger) Log(level types.Level, values ...any) { 120 preHooksResult := LogPreprocess(l.CurrentPreHooks, l.TraceIDs, level, l.CurrentLevel >= level, values...) 121 if preHooksResult.Skip { 122 return 123 } 124 125 if len(values) == 1 { 126 if entry, ok := values[0].(*types.Entry); ok { 127 entry.Fields = field.Add(entry.Fields, preHooksResult.ExtraFields) 128 l.emit(entry) 129 return 130 } 131 } 132 133 // TODO: optimize this 134 valuesParser := valuesparser.AnySlice(values) 135 fields := make(field.Fields, 0, valuesParser.Len()) 136 valuesParser.ForEachField(func(f *field.Field) bool { 137 fields = append(fields, *f) 138 return true 139 }) 140 141 // TODO: optimize this 142 var buf strings.Builder 143 valuesParser.WriteUnparsed(&buf) 144 145 var finalFields field.AbstractFields 146 if preHooksResult.ExtraFields != nil { 147 finalFields = field.Slice[field.AbstractFields]{fields, preHooksResult.ExtraFields} 148 } else { 149 finalFields = fields 150 } 151 152 entry := l.acquireEntry(level, buf.String(), finalFields, preHooksResult.ExtraEntryProperties) 153 defer releaseEntry(entry) 154 155 l.emit(entry) 156 } 157 158 // LogFields implements types.CompactLogger. 159 func (l *GenericLogger) LogFields(level types.Level, message string, fields field.AbstractFields) { 160 preHooksResult := LogFieldsPreprocess(l.CurrentPreHooks, l.TraceIDs, level, l.CurrentLevel >= level, message, fields) 161 if preHooksResult.Skip { 162 return 163 } 164 165 var finalFields field.AbstractFields 166 if preHooksResult.ExtraFields != nil { 167 finalFields = field.Slice[field.AbstractFields]{fields, preHooksResult.ExtraFields} 168 } else { 169 finalFields = fields 170 } 171 172 entry := l.acquireEntry(level, message, finalFields, preHooksResult.ExtraEntryProperties) 173 defer releaseEntry(entry) 174 175 l.emit(entry) 176 } 177 178 // LogPreprocess checks logging level and calls PreHooks. Returns the total 179 // outcome of these two steps. 180 // 181 // It will never return Skip=true for message logging levels Panic and Fatal, instead 182 // it will ask to skip the logging through Entry property "experimental.EntryPropertySkipAllEmitters". 183 // 184 // It is a helper which is supposed to be used in the beginning of a Log implementation. 185 func LogPreprocess( 186 preHooks types.PreHooks, 187 traceIDs belt.TraceIDs, 188 level types.Level, 189 logLevelSatisfied bool, 190 values ...any, 191 ) types.PreHookResult { 192 if level == types.LevelNone { 193 return types.PreHookResult{Skip: true} 194 } 195 shouldSkip := false 196 if !logLevelSatisfied { 197 if level != types.LevelPanic && level != types.LevelFatal { 198 return types.PreHookResult{Skip: true} 199 } 200 shouldSkip = true 201 } 202 203 result := preHooks.ProcessInput(traceIDs, level, values...) 204 if !result.Skip { 205 if shouldSkip { 206 result.ExtraEntryProperties = append(result.ExtraEntryProperties, experimental.EntryPropertySkipAllEmitters) 207 } 208 return result 209 } 210 if level != types.LevelPanic && level != types.LevelFatal { 211 return result 212 } 213 214 result.Skip = false 215 result.ExtraEntryProperties = append(result.ExtraEntryProperties, experimental.EntryPropertySkipAllEmitters) 216 return result 217 } 218 219 // LogFieldsPreprocess checks logging level and calls PreHooks. Returns the total 220 // outcome of these two steps. 221 // 222 // It will never return Skip=true for message logging levels Panic and Fatal, instead 223 // it will ask to skip the logging through Entry property "experimental.EntryPropertySkipAllEmitters". 224 // 225 // It is a helper which is supposed to be used in the beginning of a LogFields implementation. 226 func LogFieldsPreprocess( 227 preHooks types.PreHooks, 228 traceIDs belt.TraceIDs, 229 level types.Level, 230 logLevelSatisfied bool, 231 message string, 232 fields field.AbstractFields, 233 ) types.PreHookResult { 234 if level == types.LevelNone { 235 return types.PreHookResult{Skip: true} 236 } 237 shouldSkip := false 238 if !logLevelSatisfied { 239 if level != types.LevelPanic && level != types.LevelFatal { 240 return types.PreHookResult{Skip: true} 241 } 242 shouldSkip = true 243 } 244 245 result := preHooks.ProcessInputFields(traceIDs, level, message, fields) 246 if !result.Skip { 247 if shouldSkip { 248 result.ExtraEntryProperties = append(result.ExtraEntryProperties, experimental.EntryPropertySkipAllEmitters) 249 } 250 return result 251 } 252 if level != types.LevelPanic && level != types.LevelFatal { 253 return result 254 } 255 256 result.Skip = false 257 result.ExtraEntryProperties = append(result.ExtraEntryProperties, experimental.EntryPropertySkipAllEmitters) 258 return result 259 } 260 261 // LogfPreprocess checks logging level and calls PreHooks. Returns the total 262 // outcome of these two steps. 263 // 264 // It will never return Skip=true for message with logging levels Panic and Fatal, instead 265 // it will ask to skip the logging through Entry property "experimental.EntryPropertySkipAllEmitters". 266 // 267 // It is a helper which is supposed to be used in the beginning of a Logf implementation. 268 func LogfPreprocess( 269 preHooks types.PreHooks, 270 traceIDs belt.TraceIDs, 271 level types.Level, 272 logLevelSatisfied bool, 273 format string, 274 args ...any, 275 ) types.PreHookResult { 276 if level == types.LevelNone { 277 return types.PreHookResult{Skip: true} 278 } 279 shouldSkip := false 280 if !logLevelSatisfied { 281 if level != types.LevelPanic && level != types.LevelFatal { 282 return types.PreHookResult{Skip: true} 283 } 284 shouldSkip = true 285 } 286 287 result := preHooks.ProcessInputf(traceIDs, level, format, args...) 288 if !result.Skip { 289 if shouldSkip { 290 result.ExtraEntryProperties = append(result.ExtraEntryProperties, experimental.EntryPropertySkipAllEmitters) 291 } 292 return result 293 } 294 if level != types.LevelPanic && level != types.LevelFatal { 295 return result 296 } 297 298 result.Skip = false 299 result.ExtraEntryProperties = append(result.ExtraEntryProperties, experimental.EntryPropertySkipAllEmitters) 300 return result 301 } 302 303 // Logf implements types.CompactLogger. 304 func (l *GenericLogger) Logf(level types.Level, format string, args ...any) { 305 preHooksResult := LogfPreprocess(l.CurrentPreHooks, l.TraceIDs, level, l.CurrentLevel >= level, format, args...) 306 if preHooksResult.Skip { 307 return 308 } 309 310 entry := l.acquireEntry(level, fmt.Sprintf(format, args...), preHooksResult.ExtraFields, preHooksResult.ExtraEntryProperties) 311 defer releaseEntry(entry) 312 313 l.emit(entry) 314 } 315 316 // WithMessagePrefix implements types.CompactLogger. 317 func (l *GenericLogger) WithMessagePrefix(prefix string) CompactLogger { 318 newLogger := *l 319 newLogger.MessagePrefix = newLogger.MessagePrefix + prefix 320 return &newLogger 321 } 322 323 // WithMessagePrefix implements types.CompactLogger. 324 func (l *GenericLogger) WithEntryProperties(props ...types.EntryProperty) CompactLogger { 325 if len(props) == 0 { 326 return l 327 } 328 newLogger := *l 329 newLogger.EntryProperties = newLogger.EntryProperties.Add(props) 330 return &newLogger 331 } 332 333 // ProcessHooks executes hooks and never returns false for Panic and Fatal logging 334 // levels. In case of a "false" result from hooks for Panic or Fatal it adds 335 // EntryProperty "experimental.EntryPropertySkipAllEmitters" and returns true. 336 // 337 // It is a helper which is supposed to be used in Logf, LogFields and Log implementations. 338 func ProcessHooks(hooks types.Hooks, entry *types.Entry) bool { 339 if hooks.ProcessLogEntry(entry) { 340 return true 341 } 342 343 if entry.Level != types.LevelPanic && entry.Level != types.LevelFatal { 344 return false 345 } 346 347 entry.Properties = append(entry.Properties, experimental.EntryPropertySkipAllEmitters) 348 return true 349 } 350 351 func (l *GenericLogger) emit(entry *types.Entry) { 352 if !ProcessHooks(l.CurrentHooks, entry) { 353 return 354 } 355 356 if !entry.Properties.Has(experimental.EntryPropertySkipAllEmitters) { 357 l.Emitters.Emit(entry) 358 } 359 360 switch entry.Level { 361 case types.LevelPanic, types.LevelFatal: 362 l.Flush() 363 switch entry.Level { 364 case types.LevelPanic: 365 panic(fmt.Sprintf("panic was requested with the log entry: %#v", entry)) 366 case types.LevelFatal: 367 os.Exit(2) 368 } 369 } 370 } 371 372 // Flush implements types.CompactLogger. 373 func (l *GenericLogger) Flush() { 374 l.Emitters.Flush() 375 } 376 377 func (l *GenericLogger) acquireEntry(level types.Level, message string, fields field.AbstractFields, props types.EntryProperties) *types.Entry { 378 entry := acquireEntry() 379 entry.Level = level 380 entry.Timestamp = time.Now() 381 entry.TraceIDs = l.TraceIDs 382 entry.Message = l.MessagePrefix + message 383 entry.Fields = l.Fields.WithFields(fields) 384 entry.Properties = l.EntryProperties.Add(props...) 385 entry.Caller = l.GetCallerFunc() 386 return entry 387 }