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 }