resource.go (7810B)
1 // Copyright The OpenTelemetry Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package resource // import "go.opentelemetry.io/otel/sdk/resource" 16 17 import ( 18 "context" 19 "errors" 20 "sync" 21 22 "go.opentelemetry.io/otel" 23 "go.opentelemetry.io/otel/attribute" 24 ) 25 26 // Resource describes an entity about which identifying information 27 // and metadata is exposed. Resource is an immutable object, 28 // equivalent to a map from key to unique value. 29 // 30 // Resources should be passed and stored as pointers 31 // (`*resource.Resource`). The `nil` value is equivalent to an empty 32 // Resource. 33 type Resource struct { 34 attrs attribute.Set 35 schemaURL string 36 } 37 38 var ( 39 emptyResource Resource 40 defaultResource *Resource 41 defaultResourceOnce sync.Once 42 ) 43 44 var errMergeConflictSchemaURL = errors.New("cannot merge resource due to conflicting Schema URL") 45 46 // New returns a Resource combined from the user-provided detectors. 47 func New(ctx context.Context, opts ...Option) (*Resource, error) { 48 cfg := config{} 49 for _, opt := range opts { 50 cfg = opt.apply(cfg) 51 } 52 53 r := &Resource{schemaURL: cfg.schemaURL} 54 return r, detect(ctx, r, cfg.detectors) 55 } 56 57 // NewWithAttributes creates a resource from attrs and associates the resource with a 58 // schema URL. If attrs contains duplicate keys, the last value will be used. If attrs 59 // contains any invalid items those items will be dropped. The attrs are assumed to be 60 // in a schema identified by schemaURL. 61 func NewWithAttributes(schemaURL string, attrs ...attribute.KeyValue) *Resource { 62 resource := NewSchemaless(attrs...) 63 resource.schemaURL = schemaURL 64 return resource 65 } 66 67 // NewSchemaless creates a resource from attrs. If attrs contains duplicate keys, 68 // the last value will be used. If attrs contains any invalid items those items will 69 // be dropped. The resource will not be associated with a schema URL. If the schema 70 // of the attrs is known use NewWithAttributes instead. 71 func NewSchemaless(attrs ...attribute.KeyValue) *Resource { 72 if len(attrs) == 0 { 73 return &emptyResource 74 } 75 76 // Ensure attributes comply with the specification: 77 // https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/common/README.md#attribute 78 s, _ := attribute.NewSetWithFiltered(attrs, func(kv attribute.KeyValue) bool { 79 return kv.Valid() 80 }) 81 82 // If attrs only contains invalid entries do not allocate a new resource. 83 if s.Len() == 0 { 84 return &emptyResource 85 } 86 87 return &Resource{attrs: s} //nolint 88 } 89 90 // String implements the Stringer interface and provides a 91 // human-readable form of the resource. 92 // 93 // Avoid using this representation as the key in a map of resources, 94 // use Equivalent() as the key instead. 95 func (r *Resource) String() string { 96 if r == nil { 97 return "" 98 } 99 return r.attrs.Encoded(attribute.DefaultEncoder()) 100 } 101 102 // MarshalLog is the marshaling function used by the logging system to represent this exporter. 103 func (r *Resource) MarshalLog() interface{} { 104 return struct { 105 Attributes attribute.Set 106 SchemaURL string 107 }{ 108 Attributes: r.attrs, 109 SchemaURL: r.schemaURL, 110 } 111 } 112 113 // Attributes returns a copy of attributes from the resource in a sorted order. 114 // To avoid allocating a new slice, use an iterator. 115 func (r *Resource) Attributes() []attribute.KeyValue { 116 if r == nil { 117 r = Empty() 118 } 119 return r.attrs.ToSlice() 120 } 121 122 // SchemaURL returns the schema URL associated with Resource r. 123 func (r *Resource) SchemaURL() string { 124 if r == nil { 125 return "" 126 } 127 return r.schemaURL 128 } 129 130 // Iter returns an iterator of the Resource attributes. 131 // This is ideal to use if you do not want a copy of the attributes. 132 func (r *Resource) Iter() attribute.Iterator { 133 if r == nil { 134 r = Empty() 135 } 136 return r.attrs.Iter() 137 } 138 139 // Equal returns true when a Resource is equivalent to this Resource. 140 func (r *Resource) Equal(eq *Resource) bool { 141 if r == nil { 142 r = Empty() 143 } 144 if eq == nil { 145 eq = Empty() 146 } 147 return r.Equivalent() == eq.Equivalent() 148 } 149 150 // Merge creates a new resource by combining resource a and b. 151 // 152 // If there are common keys between resource a and b, then the value 153 // from resource b will overwrite the value from resource a, even 154 // if resource b's value is empty. 155 // 156 // The SchemaURL of the resources will be merged according to the spec rules: 157 // https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/resource/sdk.md#merge 158 // If the resources have different non-empty schemaURL an empty resource and an error 159 // will be returned. 160 func Merge(a, b *Resource) (*Resource, error) { 161 if a == nil && b == nil { 162 return Empty(), nil 163 } 164 if a == nil { 165 return b, nil 166 } 167 if b == nil { 168 return a, nil 169 } 170 171 // Merge the schema URL. 172 var schemaURL string 173 switch true { 174 case a.schemaURL == "": 175 schemaURL = b.schemaURL 176 case b.schemaURL == "": 177 schemaURL = a.schemaURL 178 case a.schemaURL == b.schemaURL: 179 schemaURL = a.schemaURL 180 default: 181 return Empty(), errMergeConflictSchemaURL 182 } 183 184 // Note: 'b' attributes will overwrite 'a' with last-value-wins in attribute.Key() 185 // Meaning this is equivalent to: append(a.Attributes(), b.Attributes()...) 186 mi := attribute.NewMergeIterator(b.Set(), a.Set()) 187 combine := make([]attribute.KeyValue, 0, a.Len()+b.Len()) 188 for mi.Next() { 189 combine = append(combine, mi.Attribute()) 190 } 191 merged := NewWithAttributes(schemaURL, combine...) 192 return merged, nil 193 } 194 195 // Empty returns an instance of Resource with no attributes. It is 196 // equivalent to a `nil` Resource. 197 func Empty() *Resource { 198 return &emptyResource 199 } 200 201 // Default returns an instance of Resource with a default 202 // "service.name" and OpenTelemetrySDK attributes. 203 func Default() *Resource { 204 defaultResourceOnce.Do(func() { 205 var err error 206 defaultResource, err = Detect( 207 context.Background(), 208 defaultServiceNameDetector{}, 209 fromEnv{}, 210 telemetrySDK{}, 211 ) 212 if err != nil { 213 otel.Handle(err) 214 } 215 // If Detect did not return a valid resource, fall back to emptyResource. 216 if defaultResource == nil { 217 defaultResource = &emptyResource 218 } 219 }) 220 return defaultResource 221 } 222 223 // Environment returns an instance of Resource with attributes 224 // extracted from the OTEL_RESOURCE_ATTRIBUTES environment variable. 225 func Environment() *Resource { 226 detector := &fromEnv{} 227 resource, err := detector.Detect(context.Background()) 228 if err != nil { 229 otel.Handle(err) 230 } 231 return resource 232 } 233 234 // Equivalent returns an object that can be compared for equality 235 // between two resources. This value is suitable for use as a key in 236 // a map. 237 func (r *Resource) Equivalent() attribute.Distinct { 238 return r.Set().Equivalent() 239 } 240 241 // Set returns the equivalent *attribute.Set of this resource's attributes. 242 func (r *Resource) Set() *attribute.Set { 243 if r == nil { 244 r = Empty() 245 } 246 return &r.attrs 247 } 248 249 // MarshalJSON encodes the resource attributes as a JSON list of { "Key": 250 // "...", "Value": ... } pairs in order sorted by key. 251 func (r *Resource) MarshalJSON() ([]byte, error) { 252 if r == nil { 253 r = Empty() 254 } 255 return r.attrs.MarshalJSON() 256 } 257 258 // Len returns the number of unique key-values in this Resource. 259 func (r *Resource) Len() int { 260 if r == nil { 261 return 0 262 } 263 return r.attrs.Len() 264 } 265 266 // Encoded returns an encoded representation of the resource. 267 func (r *Resource) Encoded(enc attribute.Encoder) string { 268 if r == nil { 269 return "" 270 } 271 return r.attrs.Encoded(enc) 272 }