github.com/facebookincubator/go-belt@v0.0.0-20230703220935-39cd348f1a38/belt.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 belt
    14  
    15  import (
    16  	"sync"
    17  
    18  	"github.com/facebookincubator/go-belt/pkg/field"
    19  )
    20  
    21  // Belt (tool belt for observability) is the handler which orchestrates
    22  // all the observability tooling.
    23  //
    24  // Things like Logger, Tracer, Metrics, ErrorMonitoring are handled together,
    25  // and are available at any moment, given an Belt.
    26  //
    27  // For example one may call `logger.FromBelt(belt)` to get a `Logger`, if `belt` is an
    28  // `Belt`. It is different from just storing this tooling as values within a context,
    29  // for example it is:
    30  // * More performance efficient.
    31  // * Safer (e.g. you won't get an untyped nil because a context was accidentally changed to a new one)
    32  // * More convenient: no need to manage each tool separately. And all the possible sugar is already in place.
    33  // * Reusable. It is a generic standardized way to handle observability tooling.
    34  // * Quality. Here we try to accumulate best practices, instead of having a quick solution for a single project.
    35  type Belt struct {
    36  	haveTools        bool
    37  	tools            Tools
    38  	toolsLocker      sync.Mutex
    39  	artifacts        Artifacts
    40  	contextFields    *field.FieldsChain
    41  	newFieldsCount   int
    42  	traceIDs         TraceIDs
    43  	newTraceIDsCount int
    44  }
    45  
    46  // New returns a new instance of an Belt
    47  func New() *Belt {
    48  	return &Belt{}
    49  }
    50  
    51  func (belt *Belt) clone() *Belt {
    52  	return &Belt{
    53  		haveTools:        belt.haveTools,
    54  		tools:            belt.tools,
    55  		artifacts:        belt.artifacts,
    56  		traceIDs:         belt.traceIDs,
    57  		contextFields:    belt.contextFields,
    58  		newFieldsCount:   belt.newFieldsCount,
    59  		newTraceIDsCount: belt.newTraceIDsCount,
    60  	}
    61  }
    62  
    63  // WithField returns a clone/derivative of the Belt which includes the passed value.
    64  //
    65  // The value is used by observability tooling. For example a Logger derived from the resulting
    66  // Belt may add this value to the structured fields of each log entry.
    67  func (belt *Belt) WithField(key string, value field.Value, props ...field.Property) *Belt {
    68  	clone := belt.clone()
    69  	clone.contextFields = clone.contextFields.WithField(key, value, props...)
    70  	clone.newFieldsCount++
    71  	return clone
    72  }
    73  
    74  // WithFields is the same as WithField, but adds multiple Fields at the same time.
    75  //
    76  // It is more performance efficient than adding fields by one.
    77  func (belt *Belt) WithFields(fields field.AbstractFields) *Belt {
    78  	clone := belt.clone()
    79  	clone.contextFields = clone.contextFields.WithFields(fields)
    80  	clone.newFieldsCount += fields.Len()
    81  	return clone
    82  }
    83  
    84  // WithMap is just a sugar method, which provides logrus like way of adding fields.
    85  // Effectively the same as WithFields, just the argument are in another format.
    86  func (belt *Belt) WithMap(m map[string]interface{}, props ...field.Property) *Belt {
    87  	clone := belt.clone()
    88  	clone.contextFields = clone.contextFields.WithMap(m, props...)
    89  	clone.newFieldsCount += len(m)
    90  	return clone
    91  }
    92  
    93  // Fields returns returns the set of fields set in the scope of this Belt.
    94  //
    95  // Do not modify the output of this function! It is for reading only.
    96  func (belt *Belt) Fields() field.AbstractFields {
    97  	return belt.contextFields
    98  }
    99  
   100  // WithTool returns an Belt clone/derivative, but the provided tool
   101  // added to the collection of tools.
   102  //
   103  // Special case: to remove a specific tool, just passed an untyped nil as `tool`.
   104  func (belt *Belt) WithTool(toolID ToolID, tool Tool) *Belt {
   105  	clone := belt.clone()
   106  
   107  	tools := make(Tools, len(clone.tools)+1)
   108  	belt.toolsLocker.Lock()
   109  	for k, v := range clone.tools {
   110  		tools[k] = v
   111  	}
   112  	belt.toolsLocker.Unlock()
   113  	if tool == nil {
   114  		delete(tools, toolID)
   115  	} else {
   116  		tools[toolID] = tool
   117  	}
   118  	clone.tools = tools
   119  	clone.haveTools = len(tools) > 0
   120  
   121  	return clone
   122  }
   123  
   124  // Tools returns the current collection of Tools.
   125  //
   126  // Do not modify the output of this function! It is for reading only.
   127  func (belt *Belt) Tools() Tools {
   128  	if !belt.haveTools {
   129  		return nil
   130  	}
   131  	belt.toolsLocker.Lock()
   132  	defer belt.toolsLocker.Unlock()
   133  	belt.updateTools()
   134  	return belt.tools
   135  }
   136  
   137  func (belt *Belt) updateTools() {
   138  	if belt.newFieldsCount == 0 && belt.newTraceIDsCount == 0 {
   139  		return
   140  	}
   141  
   142  	newTools := make(Tools, len(belt.tools))
   143  	for toolID, tool := range belt.tools {
   144  		if belt.newFieldsCount != 0 {
   145  			tool = tool.WithContextFields(belt.contextFields, belt.newFieldsCount)
   146  		}
   147  		if belt.newTraceIDsCount != 0 {
   148  			tool = tool.WithTraceIDs(belt.traceIDs, belt.newTraceIDsCount)
   149  		}
   150  		newTools[toolID] = tool
   151  	}
   152  	belt.tools = newTools
   153  
   154  	belt.newFieldsCount = 0
   155  	belt.newTraceIDsCount = 0
   156  }
   157  
   158  // WithArtifact returns a clone of the Belt, but with the Artifact set.
   159  func (belt *Belt) WithArtifact(artifactID ArtifactID, artifact Artifact) *Belt {
   160  	clone := belt.clone()
   161  
   162  	artifacts := make(Artifacts, len(clone.artifacts))
   163  	for k, v := range clone.artifacts {
   164  		artifacts[k] = v
   165  	}
   166  	if artifact == nil {
   167  		delete(artifacts, artifactID)
   168  	} else {
   169  		artifacts[artifactID] = artifact
   170  	}
   171  	clone.artifacts = artifacts
   172  
   173  	return clone
   174  }
   175  
   176  // Artifacts returns the collection of Artifacts in the scope of the Belt.
   177  //
   178  // Do not modify the output of this function! It is for reading only.
   179  func (belt *Belt) Artifacts() Artifacts {
   180  	return belt.artifacts
   181  }
   182  
   183  // WithTraceID returns an Belt clone/derivative with the passed traceIDs added to the set of TraceIDs.
   184  func (belt *Belt) WithTraceID(traceIDs ...TraceID) *Belt {
   185  	clone := belt.clone()
   186  	clone.traceIDs = append(belt.traceIDs, traceIDs...)
   187  	clone.newTraceIDsCount += len(traceIDs)
   188  	return clone
   189  }
   190  
   191  // TraceIDs returns the current set of TraceID-s.
   192  //
   193  // Do not modify the output of this function! It is for reading only.
   194  func (belt *Belt) TraceIDs() TraceIDs {
   195  	return belt.traceIDs
   196  }
   197  
   198  // Flush forces to flush all buffers of all the tools.
   199  func (belt *Belt) Flush() {
   200  	for _, tool := range belt.Tools() {
   201  		tool.Flush()
   202  	}
   203  }