github.com/blend/go-sdk@v1.20220411.3/logger/scope.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  	"fmt"
    13  	"time"
    14  )
    15  
    16  var (
    17  	_ Log = (*Scope)(nil)
    18  )
    19  
    20  // NewScope returns a new scope for a logger with a given set of optional options.
    21  func NewScope(log *Logger, options ...ScopeOption) Scope {
    22  	s := Scope{
    23  		Logger:      log,
    24  		Labels:      make(Labels),
    25  		Annotations: make(Annotations),
    26  	}
    27  	for _, option := range options {
    28  		option(&s)
    29  	}
    30  	return s
    31  }
    32  
    33  // Scope is a set of re-usable parameters for triggering events.
    34  /*
    35  The key fields:
    36  - "Path" is a set of names that denote a hierarchy or tree of calls.
    37  - "Labels" are string pairs that will appear with written log messages for easier searching later.
    38  - "Annoations" are string pairs that will not appear with written log messages, but can be used to add extra data to events.
    39  */
    40  type Scope struct {
    41  	// Path is a series of descriptive labels that shows the origin of the scope.
    42  	Path []string
    43  	// Labels are descriptive string fields for the scope.
    44  	Labels
    45  	// Annotations are extra fields for the scope.
    46  	Annotations
    47  
    48  	// Logger is a parent reference to the root logger; this holds
    49  	// information around what flags are enabled and listeners for events.
    50  	Logger *Logger
    51  }
    52  
    53  // ScopeOption is a mutator for a scope.
    54  type ScopeOption func(*Scope)
    55  
    56  // OptScopePath sets the path on a scope.
    57  func OptScopePath(path ...string) ScopeOption {
    58  	return func(s *Scope) {
    59  		s.Path = path
    60  	}
    61  }
    62  
    63  // OptScopeLabels sets the labels on a scope.
    64  func OptScopeLabels(labels ...Labels) ScopeOption {
    65  	return func(s *Scope) {
    66  		s.Labels = CombineLabels(labels...)
    67  	}
    68  }
    69  
    70  // OptScopeAnnotations sets the annotations on a scope.
    71  func OptScopeAnnotations(annotations ...Annotations) ScopeOption {
    72  	return func(s *Scope) {
    73  		s.Annotations = CombineAnnotations(annotations...)
    74  	}
    75  }
    76  
    77  // WithPath returns a new scope with a given additional path segment.
    78  func (sc Scope) WithPath(paths ...string) Scope {
    79  	return NewScope(sc.Logger,
    80  		OptScopePath(append(sc.Path, paths...)...),
    81  		OptScopeLabels(sc.Labels),
    82  		OptScopeAnnotations(sc.Annotations),
    83  	)
    84  }
    85  
    86  // WithLabels returns a new scope with a given additional set of labels.
    87  func (sc Scope) WithLabels(labels Labels) Scope {
    88  	return NewScope(sc.Logger,
    89  		OptScopePath(sc.Path...),
    90  		OptScopeLabels(sc.Labels, labels),
    91  		OptScopeAnnotations(sc.Annotations),
    92  	)
    93  }
    94  
    95  // WithAnnotations returns a new scope with a given additional set of annotations.
    96  func (sc Scope) WithAnnotations(annotations Annotations) Scope {
    97  	return NewScope(sc.Logger,
    98  		OptScopePath(sc.Path...),
    99  		OptScopeLabels(sc.Labels),
   100  		OptScopeAnnotations(sc.Annotations, annotations),
   101  	)
   102  }
   103  
   104  // --------------------------------------------------------------------------------
   105  // Trigger event handler
   106  // --------------------------------------------------------------------------------
   107  
   108  // Trigger triggers an event in the subcontext.
   109  // The provided context is amended with fields from the scope.
   110  // The provided context is also amended with a TriggerTimestamp, which can be retrieved with `GetTriggerTimestamp(ctx)` in listeners.
   111  func (sc Scope) Trigger(event Event) {
   112  	sc.TriggerContext(context.Background(), event)
   113  }
   114  
   115  // TriggerContext triggers an event with a given context..
   116  func (sc Scope) TriggerContext(ctx context.Context, event Event) {
   117  	ctx = WithTriggerTimestamp(ctx, time.Now().UTC())
   118  	sc.Logger.Dispatch(sc.ApplyContext(ctx), event)
   119  }
   120  
   121  // --------------------------------------------------------------------------------
   122  // Builtin Flag Handlers (infof, debugf etc.)
   123  // --------------------------------------------------------------------------------
   124  
   125  // Info logs an informational message to the output stream.
   126  func (sc Scope) Info(args ...interface{}) {
   127  	sc.Trigger(NewMessageEvent(Info, fmt.Sprint(args...)))
   128  }
   129  
   130  // InfoContext logs an informational message to the output stream in a given context.
   131  func (sc Scope) InfoContext(ctx context.Context, args ...interface{}) {
   132  	sc.TriggerContext(ctx, NewMessageEvent(Info, fmt.Sprint(args...)))
   133  }
   134  
   135  // Infof logs an informational message to the output stream.
   136  func (sc Scope) Infof(format string, args ...interface{}) {
   137  	sc.Trigger(NewMessageEvent(Info, fmt.Sprintf(format, args...)))
   138  }
   139  
   140  // InfofContext logs an informational message to the output stream in a given context.
   141  func (sc Scope) InfofContext(ctx context.Context, format string, args ...interface{}) {
   142  	sc.TriggerContext(ctx, NewMessageEvent(Info, fmt.Sprintf(format, args...)))
   143  }
   144  
   145  // Debug logs a debug message to the output stream.
   146  func (sc Scope) Debug(args ...interface{}) {
   147  	sc.Trigger(NewMessageEvent(Debug, fmt.Sprint(args...)))
   148  }
   149  
   150  // DebugContext logs a debug message to the output stream in a given context.
   151  func (sc Scope) DebugContext(ctx context.Context, args ...interface{}) {
   152  	sc.TriggerContext(ctx, NewMessageEvent(Debug, fmt.Sprint(args...)))
   153  }
   154  
   155  // Debugf logs a debug message to the output stream.
   156  func (sc Scope) Debugf(format string, args ...interface{}) {
   157  	sc.Trigger(NewMessageEvent(Debug, fmt.Sprintf(format, args...)))
   158  }
   159  
   160  // DebugfContext logs a debug message to the output stream.
   161  func (sc Scope) DebugfContext(ctx context.Context, format string, args ...interface{}) {
   162  	sc.TriggerContext(ctx, NewMessageEvent(Debug, fmt.Sprintf(format, args...)))
   163  }
   164  
   165  // Warningf logs a warning message to the output stream.
   166  func (sc Scope) Warningf(format string, args ...interface{}) {
   167  	sc.Trigger(NewErrorEvent(Warning, fmt.Errorf(format, args...)))
   168  }
   169  
   170  // WarningfContext logs a warning message to the output stream in a given context.
   171  func (sc Scope) WarningfContext(ctx context.Context, format string, args ...interface{}) {
   172  	sc.TriggerContext(ctx, NewErrorEvent(Warning, fmt.Errorf(format, args...)))
   173  }
   174  
   175  // Errorf writes an event to the log and triggers event listeners.
   176  func (sc Scope) Errorf(format string, args ...interface{}) {
   177  	sc.Trigger(NewErrorEvent(Error, fmt.Errorf(format, args...)))
   178  }
   179  
   180  // ErrorfContext writes an event to the log and triggers event listeners in a given context.
   181  func (sc Scope) ErrorfContext(ctx context.Context, format string, args ...interface{}) {
   182  	sc.TriggerContext(ctx, NewErrorEvent(Error, fmt.Errorf(format, args...)))
   183  }
   184  
   185  // Fatalf writes an event to the log and triggers event listeners.
   186  func (sc Scope) Fatalf(format string, args ...interface{}) {
   187  	sc.Trigger(NewErrorEvent(Fatal, fmt.Errorf(format, args...)))
   188  }
   189  
   190  // FatalfContext writes an event to the log and triggers event listeners in a given context.
   191  func (sc Scope) FatalfContext(ctx context.Context, format string, args ...interface{}) {
   192  	sc.TriggerContext(ctx, NewErrorEvent(Fatal, fmt.Errorf(format, args...)))
   193  }
   194  
   195  // Warning logs a warning error to std err.
   196  func (sc Scope) Warning(err error, opts ...ErrorEventOption) {
   197  	sc.Trigger(NewErrorEvent(Warning, err, opts...))
   198  }
   199  
   200  // WarningContext logs a warning error to std err in a given context.
   201  func (sc Scope) WarningContext(ctx context.Context, err error, opts ...ErrorEventOption) {
   202  	sc.TriggerContext(ctx, NewErrorEvent(Warning, err, opts...))
   203  }
   204  
   205  // Error logs an error to std err.
   206  func (sc Scope) Error(err error, opts ...ErrorEventOption) {
   207  	sc.Trigger(NewErrorEvent(Error, err, opts...))
   208  }
   209  
   210  // ErrorContext logs an error to std err.
   211  func (sc Scope) ErrorContext(ctx context.Context, err error, opts ...ErrorEventOption) {
   212  	sc.TriggerContext(ctx, NewErrorEvent(Error, err, opts...))
   213  }
   214  
   215  // Fatal logs an error as fatal.
   216  func (sc Scope) Fatal(err error, opts ...ErrorEventOption) {
   217  	sc.Trigger(NewErrorEvent(Fatal, err, opts...))
   218  }
   219  
   220  // FatalContext logs an error as fatal.
   221  func (sc Scope) FatalContext(ctx context.Context, err error, opts ...ErrorEventOption) {
   222  	sc.TriggerContext(ctx, NewErrorEvent(Fatal, err, opts...))
   223  }
   224  
   225  //
   226  // Context utilities
   227  //
   228  
   229  // FromContext returns a scope from a given context.
   230  // It will read any relevant fields off the context (Path, Labels, Annotations)
   231  // and append them to values already on the scope.
   232  func (sc Scope) FromContext(ctx context.Context) Scope {
   233  	return NewScope(sc.Logger,
   234  		OptScopePath(append(GetPath(ctx), sc.Path...)...),
   235  		OptScopeLabels(sc.Labels, GetLabels(ctx)),
   236  		OptScopeAnnotations(sc.Annotations, GetAnnotations(ctx)),
   237  	)
   238  }
   239  
   240  // ApplyContext applies the scope fields to a given context.
   241  func (sc Scope) ApplyContext(ctx context.Context) context.Context {
   242  	ctx = WithPath(ctx, append(GetPath(ctx), sc.Path...)...)
   243  	ctx = WithLabels(ctx, sc.Labels) // treated specially because maps are references
   244  	ctx = WithAnnotations(ctx, CombineAnnotations(sc.Annotations, GetAnnotations(ctx)))
   245  	return ctx
   246  }