github.com/blend/go-sdk@v1.20220411.3/logger/logger.go (about) 1 /* 2 3 Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file. 5 6 */ 7 8 package logger 9 10 import ( 11 "context" 12 "io" 13 "os" 14 "sync" 15 ) 16 17 // New returns a new logger with a given set of enabled flags. 18 // By default it uses a text output formatter writing to stdout. 19 func New(options ...Option) (*Logger, error) { 20 l := &Logger{ 21 Formatter: NewTextOutputFormatter(), 22 Output: NopCloserWriter{NewInterlockedWriter(os.Stdout)}, 23 RecoverPanics: DefaultRecoverPanics, 24 Flags: NewFlags(DefaultFlags...), 25 Writable: FlagsAll(), 26 Scopes: ScopesAll(), 27 WritableScopes: ScopesAll(), 28 } 29 30 l.Scope = NewScope(l) 31 var err error 32 for _, option := range options { 33 if err = option(l); err != nil { 34 return nil, err 35 } 36 } 37 return l, nil 38 } 39 40 // MustNew creates a new logger with a given list of options and panics on error. 41 func MustNew(options ...Option) *Logger { 42 log, err := New(options...) 43 if err != nil { 44 panic(err) 45 } 46 return log 47 } 48 49 // All returns a new logger with all flags enabled. 50 func All(options ...Option) *Logger { 51 return MustNew( 52 append([]Option{ 53 OptConfigFromEnv(), 54 OptAll(), 55 }, options...)...) 56 } 57 58 // None returns a new logger with all flags enabled. 59 func None(options ...Option) *Logger { 60 return MustNew( 61 append([]Option{ 62 OptNone(), 63 OptOutput(nil), 64 OptFormatter(nil), 65 }, options...)...) 66 } 67 68 // Prod returns a new logger tuned for production use. 69 // It writes to os.Stderr with text output colorization disabled. 70 func Prod(options ...Option) *Logger { 71 return MustNew( 72 append([]Option{ 73 OptAll(), 74 OptOutput(os.Stderr), 75 OptFormatter(NewTextOutputFormatter(OptTextNoColor())), 76 }, options...)...) 77 } 78 79 // Memory creates a logger that logs to the in-memory writer passed in. 80 // 81 // It is useful for writing tests that collect log output. 82 func Memory(buffer io.Writer, options ...Option) *Logger { 83 return MustNew( 84 append([]Option{ 85 OptAll(), 86 OptOutput(buffer), 87 OptFormatter(NewTextOutputFormatter( 88 OptTextNoColor(), 89 OptTextHideTimestamp(), 90 )), 91 }, options...)...) 92 } 93 94 // Logger is a handler for various logging events with descendent handlers. 95 type Logger struct { 96 sync.Mutex 97 *Flags 98 Scope 99 100 Writable *Flags 101 Scopes *Scopes 102 WritableScopes *Scopes 103 RecoverPanics bool 104 105 Output io.Writer 106 Formatter WriteFormatter 107 Errors chan error 108 109 // Filters hold filters organized by flag, and then by filter name. 110 // The intent is to modify event data before it is written or given to listeners. 111 Filters map[string]map[string]Filter 112 // Listeners hold event listeners organized by flag, and then by listener name. 113 Listeners map[string]map[string]*Worker 114 } 115 116 // GetFlags returns the flags. 117 func (l *Logger) GetFlags() *Flags { 118 return l.Flags 119 } 120 121 // GetWritable returns the writable flags. 122 func (l *Logger) GetWritable() *Flags { 123 return l.Writable 124 } 125 126 // HasListeners returns if there are registered listener for an event. 127 func (l *Logger) HasListeners(flag string) bool { 128 l.Lock() 129 defer l.Unlock() 130 131 if l.Listeners == nil { 132 return false 133 } 134 listeners, ok := l.Listeners[flag] 135 if !ok { 136 return false 137 } 138 return len(listeners) > 0 139 } 140 141 // HasListener returns if a specific listener is registered for a flag. 142 func (l *Logger) HasListener(flag, listenerName string) bool { 143 l.Lock() 144 defer l.Unlock() 145 146 if l.Listeners == nil { 147 return false 148 } 149 workers, ok := l.Listeners[flag] 150 if !ok { 151 return false 152 } 153 _, ok = workers[listenerName] 154 return ok 155 } 156 157 // Listen adds a listener for a given flag. 158 func (l *Logger) Listen(flag, listenerName string, listener Listener) { 159 l.Lock() 160 defer l.Unlock() 161 162 if l.Listeners == nil { 163 l.Listeners = make(map[string]map[string]*Worker) 164 } 165 if l.Listeners[flag] == nil { 166 l.Listeners[flag] = make(map[string]*Worker) 167 } 168 169 eventListener := NewWorker(listener) 170 l.Listeners[flag][listenerName] = eventListener 171 go func() { _ = eventListener.Start() }() 172 <-eventListener.NotifyStarted() 173 } 174 175 // RemoveListeners clears *all* listeners for a Flag. 176 func (l *Logger) RemoveListeners(flag string) error { 177 l.Lock() 178 defer l.Unlock() 179 180 if l.Listeners == nil { 181 return nil 182 } 183 184 listeners, ok := l.Listeners[flag] 185 if !ok { 186 return nil 187 } 188 var err error 189 for _, l := range listeners { 190 if err = l.Stop(); err != nil { 191 return err 192 } 193 } 194 delete(l.Listeners, flag) 195 return nil 196 } 197 198 // RemoveListener clears a specific listener for a Flag. 199 func (l *Logger) RemoveListener(flag, listenerName string) error { 200 l.Lock() 201 defer l.Unlock() 202 203 if l.Listeners == nil { 204 return nil 205 } 206 207 listeners, ok := l.Listeners[flag] 208 if !ok { 209 return nil 210 } 211 212 worker, ok := listeners[listenerName] 213 if !ok { 214 return nil 215 } 216 if err := worker.Stop(); err != nil { 217 return err 218 } 219 delete(listeners, listenerName) 220 if len(listeners) == 0 { 221 delete(l.Listeners, flag) 222 } 223 return nil 224 } 225 226 // HasFilters returns if a logger has filters for a given flag. 227 func (l *Logger) HasFilters(flag string) bool { 228 l.Lock() 229 defer l.Unlock() 230 231 if l.Filters == nil { 232 return false 233 } 234 filters, ok := l.Filters[flag] 235 if !ok { 236 return false 237 } 238 return len(filters) > 0 239 } 240 241 // HasFilter returns if a logger has a given filter by name. 242 func (l *Logger) HasFilter(flag, filterName string) bool { 243 l.Lock() 244 defer l.Unlock() 245 246 if l.Filters == nil { 247 return false 248 } 249 filters, ok := l.Filters[flag] 250 if !ok { 251 return false 252 } 253 _, ok = filters[filterName] 254 return ok 255 } 256 257 // Filter adds a given filter for a given flag. 258 func (l *Logger) Filter(flag, filterName string, filter Filter) { 259 l.Lock() 260 defer l.Unlock() 261 262 if l.Filters == nil { 263 l.Filters = make(map[string]map[string]Filter) 264 } 265 if l.Filters[flag] == nil { 266 l.Filters[flag] = make(map[string]Filter) 267 } 268 l.Filters[flag][filterName] = filter 269 } 270 271 // RemoveFilters clears *all* filters for a Flag. 272 func (l *Logger) RemoveFilters(flag string) { 273 l.Lock() 274 defer l.Unlock() 275 delete(l.Filters, flag) 276 } 277 278 // RemoveFilter clears a specific filter for a Flag. 279 func (l *Logger) RemoveFilter(flag, filterName string) { 280 l.Lock() 281 defer l.Unlock() 282 283 if l.Filters == nil { 284 return 285 } 286 filters, ok := l.Filters[flag] 287 if !ok { 288 return 289 } 290 delete(filters, filterName) 291 } 292 293 // Dispatch fires the listeners for a given event asynchronously, and writes the event to the output. 294 // The invocations will be queued in a work queue per listener. 295 // There are no order guarantees on when these events will be processed across listeners. 296 // This call will not block on the event listeners, but will block on the write. 297 func (l *Logger) Dispatch(ctx context.Context, e Event) { 298 if e == nil { 299 return 300 } 301 flag := e.GetFlag() 302 if !l.IsEnabled(flag) { 303 return 304 } 305 if !l.Scopes.IsEnabled(GetPath(ctx)...) { 306 return 307 } 308 309 if !IsSkipTrigger(ctx) { 310 var filters map[string]Filter 311 var listeners map[string]*Worker 312 l.Lock() 313 if l.Filters != nil { 314 if flagFilters, ok := l.Filters[flag]; ok { 315 filters = flagFilters 316 } 317 } 318 if l.Listeners != nil { 319 if flagListeners, ok := l.Listeners[flag]; ok { 320 listeners = flagListeners 321 } 322 } 323 l.Unlock() 324 325 var shouldFilter bool 326 for _, filter := range filters { 327 e, shouldFilter = filter(ctx, e) 328 if shouldFilter { 329 return 330 } 331 } 332 for _, listener := range listeners { 333 listener.Work <- EventWithContext{ctx, e} 334 } 335 } 336 337 l.Write(ctx, e) 338 } 339 340 // Write writes an event synchronously to the writer either as a normal even or as an error. 341 func (l *Logger) Write(ctx context.Context, e Event) { 342 // if a formater or the output are unset, bail. 343 if l.Formatter == nil || l.Output == nil { 344 return 345 } 346 347 if IsSkipWrite(ctx) { 348 return 349 } 350 if !l.Writable.IsEnabled(e.GetFlag()) { 351 return 352 } 353 if !l.WritableScopes.IsEnabled(GetPath(ctx)...) { 354 return 355 } 356 357 err := l.Formatter.WriteFormat(ctx, l.Output, e) 358 if err != nil && l.Errors != nil { 359 l.Errors <- err 360 } 361 } 362 363 // -------------------------------------------------------------------------------- 364 // finalizers 365 // -------------------------------------------------------------------------------- 366 367 // Close releases shared resources for the agent. 368 // It will stop listeners and wait for them to complete work 369 // and then zero out any other resources. 370 func (l *Logger) Close() { 371 l.Lock() 372 defer l.Unlock() 373 374 if l.Flags != nil { 375 l.Flags.SetNone() 376 } 377 378 for _, listeners := range l.Listeners { 379 for _, listener := range listeners { 380 _ = listener.Stop() 381 } 382 } 383 if closer, ok := l.Output.(io.Closer); ok { 384 _ = closer.Close() 385 } 386 l.Listeners = nil 387 l.Filters = nil 388 } 389 390 // Drain stops the event listeners, letting them complete their work 391 // and then restarts the listeners. 392 func (l *Logger) Drain() { 393 l.DrainContext(context.Background()) 394 } 395 396 // DrainContext waits for the logger to finish its queue of events with a given context. 397 func (l *Logger) DrainContext(ctx context.Context) { 398 for _, workers := range l.Listeners { 399 for _, worker := range workers { 400 _ = worker.StopContext(ctx) 401 worker.Reset() 402 403 notifyStarted := worker.NotifyStarted() 404 go func(w *Worker) { 405 _ = w.Start() 406 }(worker) 407 408 // Wait for worker to start 409 select { 410 case <-notifyStarted: 411 case <-ctx.Done(): 412 } 413 } 414 } 415 }