2020-07-22 12:34:44 -07:00
|
|
|
// Copyright The OpenTelemetry Authors
|
2024-02-29 07:05:28 +01:00
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
2020-07-22 12:34:44 -07:00
|
|
|
|
2021-06-16 11:32:42 -04:00
|
|
|
package stdouttrace // import "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
|
2020-07-22 12:34:44 -07:00
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"encoding/json"
|
2025-08-11 22:05:02 +08:00
|
|
|
"fmt"
|
2020-09-09 10:19:03 -07:00
|
|
|
"sync"
|
2025-08-11 22:05:02 +08:00
|
|
|
"sync/atomic"
|
2021-08-24 00:29:51 +03:00
|
|
|
"time"
|
2020-07-22 12:34:44 -07:00
|
|
|
|
2025-08-11 22:05:02 +08:00
|
|
|
"go.opentelemetry.io/otel"
|
|
|
|
|
"go.opentelemetry.io/otel/attribute"
|
|
|
|
|
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/x"
|
|
|
|
|
"go.opentelemetry.io/otel/metric"
|
|
|
|
|
"go.opentelemetry.io/otel/sdk"
|
2021-04-07 15:03:43 +00:00
|
|
|
"go.opentelemetry.io/otel/sdk/trace"
|
2021-05-04 23:45:13 +00:00
|
|
|
"go.opentelemetry.io/otel/sdk/trace/tracetest"
|
2025-08-11 22:05:02 +08:00
|
|
|
semconv "go.opentelemetry.io/otel/semconv/v1.36.0"
|
|
|
|
|
"go.opentelemetry.io/otel/semconv/v1.36.0/otelconv"
|
2020-07-22 12:34:44 -07:00
|
|
|
)
|
|
|
|
|
|
2025-08-11 22:05:02 +08:00
|
|
|
// otelComponentType is a name identifying the type of the OpenTelemetry component.
|
|
|
|
|
const otelComponentType = "stdout_trace_exporter"
|
|
|
|
|
|
2021-08-24 00:29:51 +03:00
|
|
|
var zeroTime time.Time
|
|
|
|
|
|
2021-08-25 18:48:48 +03:00
|
|
|
var _ trace.SpanExporter = &Exporter{}
|
|
|
|
|
|
|
|
|
|
// New creates an Exporter with the passed options.
|
|
|
|
|
func New(options ...Option) (*Exporter, error) {
|
2024-06-22 01:02:07 +02:00
|
|
|
cfg := newConfig(options...)
|
2021-08-25 18:48:48 +03:00
|
|
|
|
|
|
|
|
enc := json.NewEncoder(cfg.Writer)
|
|
|
|
|
if cfg.PrettyPrint {
|
|
|
|
|
enc.SetIndent("", "\t")
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-11 22:05:02 +08:00
|
|
|
exporter := &Exporter{
|
2021-08-25 18:48:48 +03:00
|
|
|
encoder: enc,
|
|
|
|
|
timestamps: cfg.Timestamps,
|
2025-08-11 22:05:02 +08:00
|
|
|
}
|
|
|
|
|
exporter.initSelfObservability()
|
|
|
|
|
|
|
|
|
|
return exporter, nil
|
2021-08-25 18:48:48 +03:00
|
|
|
}
|
|
|
|
|
|
2020-07-22 12:34:44 -07:00
|
|
|
// Exporter is an implementation of trace.SpanSyncer that writes spans to stdout.
|
2021-08-25 18:48:48 +03:00
|
|
|
type Exporter struct {
|
|
|
|
|
encoder *json.Encoder
|
2021-09-30 12:58:26 -07:00
|
|
|
encoderMu sync.Mutex
|
2021-08-25 18:48:48 +03:00
|
|
|
timestamps bool
|
2020-07-22 12:34:44 -07:00
|
|
|
|
2020-09-09 10:19:03 -07:00
|
|
|
stoppedMu sync.RWMutex
|
|
|
|
|
stopped bool
|
2025-08-11 22:05:02 +08:00
|
|
|
|
|
|
|
|
selfObservabilityEnabled bool
|
|
|
|
|
selfObservabilityAttrs []attribute.KeyValue // selfObservability common attributes
|
|
|
|
|
spanInflightMetric otelconv.SDKExporterSpanInflight
|
|
|
|
|
spanExportedMetric otelconv.SDKExporterSpanExported
|
|
|
|
|
operationDurationMetric otelconv.SDKExporterOperationDuration
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// initSelfObservability initializes self-observability for the exporter if enabled.
|
|
|
|
|
func (e *Exporter) initSelfObservability() {
|
|
|
|
|
if !x.SelfObservability.Enabled() {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
e.selfObservabilityEnabled = true
|
|
|
|
|
e.selfObservabilityAttrs = []attribute.KeyValue{
|
|
|
|
|
semconv.OTelComponentName(fmt.Sprintf("%s/%d", otelComponentType, nextExporterID())),
|
|
|
|
|
semconv.OTelComponentTypeKey.String(otelComponentType),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mp := otel.GetMeterProvider()
|
|
|
|
|
m := mp.Meter("go.opentelemetry.io/otel/exporters/stdout/stdouttrace",
|
|
|
|
|
metric.WithInstrumentationVersion(sdk.Version()),
|
|
|
|
|
metric.WithSchemaURL(semconv.SchemaURL),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var err error
|
|
|
|
|
if e.spanInflightMetric, err = otelconv.NewSDKExporterSpanInflight(m); err != nil {
|
|
|
|
|
otel.Handle(err)
|
|
|
|
|
}
|
|
|
|
|
if e.spanExportedMetric, err = otelconv.NewSDKExporterSpanExported(m); err != nil {
|
|
|
|
|
otel.Handle(err)
|
|
|
|
|
}
|
|
|
|
|
if e.operationDurationMetric, err = otelconv.NewSDKExporterOperationDuration(m); err != nil {
|
|
|
|
|
otel.Handle(err)
|
|
|
|
|
}
|
2020-07-22 12:34:44 -07:00
|
|
|
}
|
|
|
|
|
|
2021-05-04 23:45:13 +00:00
|
|
|
// ExportSpans writes spans in json format to stdout.
|
2025-08-11 22:05:02 +08:00
|
|
|
func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan) (err error) {
|
|
|
|
|
if e.selfObservabilityEnabled {
|
|
|
|
|
count := int64(len(spans))
|
|
|
|
|
|
|
|
|
|
e.spanInflightMetric.Add(context.Background(), count, e.selfObservabilityAttrs...)
|
|
|
|
|
defer func(starting time.Time) {
|
|
|
|
|
// additional attributes for self-observability,
|
|
|
|
|
// only spanExportedMetric and operationDurationMetric are supported
|
|
|
|
|
addAttrs := make([]attribute.KeyValue, len(e.selfObservabilityAttrs), len(e.selfObservabilityAttrs)+1)
|
|
|
|
|
copy(addAttrs, e.selfObservabilityAttrs)
|
|
|
|
|
if err != nil {
|
|
|
|
|
addAttrs = append(addAttrs, semconv.ErrorType(err))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
e.spanInflightMetric.Add(context.Background(), -count, e.selfObservabilityAttrs...)
|
|
|
|
|
e.spanExportedMetric.Add(context.Background(), count, addAttrs...)
|
|
|
|
|
e.operationDurationMetric.Record(context.Background(), time.Since(starting).Seconds(), addAttrs...)
|
|
|
|
|
}(time.Now())
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-25 08:11:45 -07:00
|
|
|
if err := ctx.Err(); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2020-09-09 10:19:03 -07:00
|
|
|
e.stoppedMu.RLock()
|
|
|
|
|
stopped := e.stopped
|
|
|
|
|
e.stoppedMu.RUnlock()
|
|
|
|
|
if stopped {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-16 11:32:42 -04:00
|
|
|
if len(spans) == 0 {
|
2020-09-09 10:19:03 -07:00
|
|
|
return nil
|
2020-07-22 12:34:44 -07:00
|
|
|
}
|
2021-08-25 18:48:48 +03:00
|
|
|
|
2021-08-24 00:29:51 +03:00
|
|
|
stubs := tracetest.SpanStubsFromReadOnlySpans(spans)
|
2021-08-25 18:48:48 +03:00
|
|
|
|
2021-09-30 12:58:26 -07:00
|
|
|
e.encoderMu.Lock()
|
|
|
|
|
defer e.encoderMu.Unlock()
|
2021-08-25 18:48:48 +03:00
|
|
|
for i := range stubs {
|
|
|
|
|
stub := &stubs[i]
|
|
|
|
|
// Remove timestamps
|
|
|
|
|
if !e.timestamps {
|
2021-08-24 00:29:51 +03:00
|
|
|
stub.StartTime = zeroTime
|
|
|
|
|
stub.EndTime = zeroTime
|
|
|
|
|
for j := range stub.Events {
|
|
|
|
|
ev := &stub.Events[j]
|
|
|
|
|
ev.Time = zeroTime
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-25 18:48:48 +03:00
|
|
|
// Encode span stubs, one by one
|
|
|
|
|
if err := e.encoder.Encode(stub); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2020-09-09 10:19:03 -07:00
|
|
|
}
|
2021-08-25 18:48:48 +03:00
|
|
|
return nil
|
2020-09-09 10:19:03 -07:00
|
|
|
}
|
|
|
|
|
|
2023-04-11 17:28:13 -07:00
|
|
|
// Shutdown is called to stop the exporter, it performs no action.
|
2025-08-04 21:48:04 +02:00
|
|
|
func (e *Exporter) Shutdown(context.Context) error {
|
2020-09-09 10:19:03 -07:00
|
|
|
e.stoppedMu.Lock()
|
|
|
|
|
e.stopped = true
|
|
|
|
|
e.stoppedMu.Unlock()
|
2020-07-22 12:34:44 -07:00
|
|
|
|
2020-09-09 10:19:03 -07:00
|
|
|
return nil
|
2020-07-22 12:34:44 -07:00
|
|
|
}
|
2022-01-10 18:58:01 -06:00
|
|
|
|
2024-01-19 06:18:16 -08:00
|
|
|
// MarshalLog is the marshaling function used by the logging system to represent this Exporter.
|
2025-07-29 18:19:11 +10:00
|
|
|
func (e *Exporter) MarshalLog() any {
|
2022-01-10 18:58:01 -06:00
|
|
|
return struct {
|
|
|
|
|
Type string
|
|
|
|
|
WithTimestamps bool
|
|
|
|
|
}{
|
|
|
|
|
Type: "stdout",
|
|
|
|
|
WithTimestamps: e.timestamps,
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-11 22:05:02 +08:00
|
|
|
|
|
|
|
|
var exporterIDCounter atomic.Int64
|
|
|
|
|
|
|
|
|
|
// nextExporterID returns a new unique ID for an exporter.
|
|
|
|
|
// the starting value is 0, and it increments by 1 for each call.
|
|
|
|
|
func nextExporterID() int64 {
|
|
|
|
|
return exporterIDCounter.Add(1) - 1
|
|
|
|
|
}
|