// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package telemetry import ( "bytes" "encoding/json" "errors" "fmt" "io" ) // Traces represents the traces data that can be stored in a persistent storage, // OR can be embedded by other protocols that transfer OTLP traces data but do // not implement the OTLP protocol. // // The main difference between this message and collector protocol is that // in this message there will not be any "control" or "metadata" specific to // OTLP protocol. // // When new fields are added into this message, the OTLP request MUST be updated // as well. type Traces struct { // An array of ResourceSpans. // For data coming from a single resource this array will typically contain // one element. Intermediary nodes that receive data from multiple origins // typically batch the data before forwarding further and in that case this // array will contain multiple elements. ResourceSpans []*ResourceSpans `json:"resourceSpans,omitempty"` } // UnmarshalJSON decodes the OTLP formatted JSON contained in data into td. func (td *Traces) UnmarshalJSON(data []byte) error { decoder := json.NewDecoder(bytes.NewReader(data)) t, err := decoder.Token() if err != nil { return err } if t != json.Delim('{') { return errors.New("invalid TracesData type") } for decoder.More() { keyIface, err := decoder.Token() if err != nil { if errors.Is(err, io.EOF) { // Empty. return nil } return err } key, ok := keyIface.(string) if !ok { return fmt.Errorf("invalid TracesData field: %#v", keyIface) } switch key { case "resourceSpans", "resource_spans": err = decoder.Decode(&td.ResourceSpans) default: // Skip unknown. } if err != nil { return err } } return nil } // A collection of ScopeSpans from a Resource. type ResourceSpans struct { // The resource for the spans in this message. // If this field is not set then no resource info is known. Resource Resource `json:"resource"` // A list of ScopeSpans that originate from a resource. ScopeSpans []*ScopeSpans `json:"scopeSpans,omitempty"` // This schema_url applies to the data in the "resource" field. It does not apply // to the data in the "scope_spans" field which have their own schema_url field. SchemaURL string `json:"schemaUrl,omitempty"` } // UnmarshalJSON decodes the OTLP formatted JSON contained in data into rs. func (rs *ResourceSpans) UnmarshalJSON(data []byte) error { decoder := json.NewDecoder(bytes.NewReader(data)) t, err := decoder.Token() if err != nil { return err } if t != json.Delim('{') { return errors.New("invalid ResourceSpans type") } for decoder.More() { keyIface, err := decoder.Token() if err != nil { if errors.Is(err, io.EOF) { // Empty. return nil } return err } key, ok := keyIface.(string) if !ok { return fmt.Errorf("invalid ResourceSpans field: %#v", keyIface) } switch key { case "resource": err = decoder.Decode(&rs.Resource) case "scopeSpans", "scope_spans": err = decoder.Decode(&rs.ScopeSpans) case "schemaUrl", "schema_url": err = decoder.Decode(&rs.SchemaURL) default: // Skip unknown. } if err != nil { return err } } return nil } // A collection of Spans produced by an InstrumentationScope. type ScopeSpans struct { // The instrumentation scope information for the spans in this message. // Semantically when InstrumentationScope isn't set, it is equivalent with // an empty instrumentation scope name (unknown). Scope *Scope `json:"scope"` // A list of Spans that originate from an instrumentation scope. Spans []*Span `json:"spans,omitempty"` // The Schema URL, if known. This is the identifier of the Schema that the span data // is recorded in. To learn more about Schema URL see // https://opentelemetry.io/docs/specs/otel/schemas/#schema-url // This schema_url applies to all spans and span events in the "spans" field. SchemaURL string `json:"schemaUrl,omitempty"` } // UnmarshalJSON decodes the OTLP formatted JSON contained in data into ss. func (ss *ScopeSpans) UnmarshalJSON(data []byte) error { decoder := json.NewDecoder(bytes.NewReader(data)) t, err := decoder.Token() if err != nil { return err } if t != json.Delim('{') { return errors.New("invalid ScopeSpans type") } for decoder.More() { keyIface, err := decoder.Token() if err != nil { if errors.Is(err, io.EOF) { // Empty. return nil } return err } key, ok := keyIface.(string) if !ok { return fmt.Errorf("invalid ScopeSpans field: %#v", keyIface) } switch key { case "scope": err = decoder.Decode(&ss.Scope) case "spans": err = decoder.Decode(&ss.Spans) case "schemaUrl", "schema_url": err = decoder.Decode(&ss.SchemaURL) default: // Skip unknown. } if err != nil { return err } } return nil }