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  }