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-17 08:37:42 -07:00
|
|
|
"errors"
|
2025-08-11 22:05:02 +08:00
|
|
|
"fmt"
|
2020-09-09 10:19:03 -07:00
|
|
|
"sync"
|
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"
|
2025-08-17 09:05:09 -07:00
|
|
|
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/counter"
|
2025-08-11 22:05:02 +08:00
|
|
|
"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-28 02:29:52 -07:00
|
|
|
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
|
|
|
|
|
"go.opentelemetry.io/otel/semconv/v1.37.0/otelconv"
|
2020-07-22 12:34:44 -07:00
|
|
|
)
|
|
|
|
|
|
2025-08-17 08:46:13 -07:00
|
|
|
// otelComponentType is a name identifying the type of the OpenTelemetry
|
|
|
|
|
// component. It is not a standardized OTel component type, so it uses the
|
|
|
|
|
// Go package prefixed type name to ensure uniqueness and identity.
|
|
|
|
|
const otelComponentType = "go.opentelemetry.io/otel/exporters/stdout/stdouttrace.Exporter"
|
2025-08-11 22:05:02 +08:00
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2025-08-17 08:37:42 -07:00
|
|
|
if !x.SelfObservability.Enabled() {
|
|
|
|
|
return exporter, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
exporter.selfObservabilityEnabled = true
|
|
|
|
|
exporter.selfObservabilityAttrs = []attribute.KeyValue{
|
2025-08-17 09:05:09 -07:00
|
|
|
semconv.OTelComponentName(fmt.Sprintf("%s/%d", otelComponentType, counter.NextExporterID())),
|
2025-08-17 08:37:42 -07:00
|
|
|
semconv.OTelComponentTypeKey.String(otelComponentType),
|
|
|
|
|
}
|
2025-08-26 09:10:29 -07:00
|
|
|
s := attribute.NewSet(exporter.selfObservabilityAttrs...)
|
|
|
|
|
exporter.selfObservabilitySetOpt = metric.WithAttributeSet(s)
|
2025-08-17 08:37:42 -07:00
|
|
|
|
|
|
|
|
mp := otel.GetMeterProvider()
|
|
|
|
|
m := mp.Meter(
|
|
|
|
|
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace",
|
|
|
|
|
metric.WithInstrumentationVersion(sdk.Version()),
|
|
|
|
|
metric.WithSchemaURL(semconv.SchemaURL),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var err, e error
|
|
|
|
|
if exporter.spanInflightMetric, e = otelconv.NewSDKExporterSpanInflight(m); e != nil {
|
|
|
|
|
e = fmt.Errorf("failed to create span inflight metric: %w", e)
|
|
|
|
|
err = errors.Join(err, e)
|
|
|
|
|
}
|
|
|
|
|
if exporter.spanExportedMetric, e = otelconv.NewSDKExporterSpanExported(m); e != nil {
|
|
|
|
|
e = fmt.Errorf("failed to create span exported metric: %w", e)
|
|
|
|
|
err = errors.Join(err, e)
|
|
|
|
|
}
|
|
|
|
|
if exporter.operationDurationMetric, e = otelconv.NewSDKExporterOperationDuration(m); e != nil {
|
|
|
|
|
e = fmt.Errorf("failed to create operation duration metric: %w", e)
|
|
|
|
|
err = errors.Join(err, e)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return exporter, err
|
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
|
2025-08-26 09:10:29 -07:00
|
|
|
selfObservabilitySetOpt metric.MeasurementOption
|
2025-08-11 22:05:02 +08:00
|
|
|
spanInflightMetric otelconv.SDKExporterSpanInflight
|
|
|
|
|
spanExportedMetric otelconv.SDKExporterSpanExported
|
|
|
|
|
operationDurationMetric otelconv.SDKExporterOperationDuration
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-26 09:10:29 -07:00
|
|
|
var (
|
|
|
|
|
measureAttrsPool = sync.Pool{
|
|
|
|
|
New: func() any {
|
|
|
|
|
// "component.name" + "component.type" + "error.type"
|
|
|
|
|
const n = 1 + 1 + 1
|
|
|
|
|
s := make([]attribute.KeyValue, 0, n)
|
|
|
|
|
// Return a pointer to a slice instead of a slice itself
|
|
|
|
|
// to avoid allocations on every call.
|
|
|
|
|
return &s
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addOptPool = &sync.Pool{
|
|
|
|
|
New: func() any {
|
|
|
|
|
const n = 1 // WithAttributeSet
|
|
|
|
|
o := make([]metric.AddOption, 0, n)
|
|
|
|
|
return &o
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
recordOptPool = &sync.Pool{
|
|
|
|
|
New: func() any {
|
|
|
|
|
const n = 1 // WithAttributeSet
|
|
|
|
|
o := make([]metric.RecordOption, 0, n)
|
|
|
|
|
return &o
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
)
|
2025-08-18 10:27:17 -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) {
|
2025-08-18 09:37:33 -07:00
|
|
|
var success int64
|
2025-08-11 22:05:02 +08:00
|
|
|
if e.selfObservabilityEnabled {
|
|
|
|
|
count := int64(len(spans))
|
|
|
|
|
|
2025-08-26 09:10:29 -07:00
|
|
|
addOpt := addOptPool.Get().(*[]metric.AddOption)
|
|
|
|
|
defer func() {
|
|
|
|
|
*addOpt = (*addOpt)[:0]
|
|
|
|
|
addOptPool.Put(addOpt)
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
*addOpt = append(*addOpt, e.selfObservabilitySetOpt)
|
|
|
|
|
|
|
|
|
|
e.spanInflightMetric.Inst().Add(ctx, count, *addOpt...)
|
2025-08-11 22:05:02 +08:00
|
|
|
defer func(starting time.Time) {
|
2025-08-26 09:10:29 -07:00
|
|
|
e.spanInflightMetric.Inst().Add(ctx, -count, *addOpt...)
|
2025-08-18 09:37:33 -07:00
|
|
|
|
|
|
|
|
// Record the success and duration of the operation.
|
|
|
|
|
//
|
|
|
|
|
// Do not exclude 0 values, as they are valid and indicate no spans
|
|
|
|
|
// were exported which is meaningful for certain aggregations.
|
2025-08-26 09:10:29 -07:00
|
|
|
e.spanExportedMetric.Inst().Add(ctx, success, *addOpt...)
|
2025-08-18 09:37:33 -07:00
|
|
|
|
2025-08-26 09:10:29 -07:00
|
|
|
mOpt := e.selfObservabilitySetOpt
|
2025-08-11 22:05:02 +08:00
|
|
|
if err != nil {
|
2025-08-18 09:37:33 -07:00
|
|
|
// additional attributes for self-observability,
|
|
|
|
|
// only spanExportedMetric and operationDurationMetric are supported.
|
2025-08-18 10:27:17 -07:00
|
|
|
attrs := measureAttrsPool.Get().(*[]attribute.KeyValue)
|
|
|
|
|
defer func() {
|
|
|
|
|
*attrs = (*attrs)[:0] // reset the slice for reuse
|
|
|
|
|
measureAttrsPool.Put(attrs)
|
|
|
|
|
}()
|
|
|
|
|
*attrs = append(*attrs, e.selfObservabilityAttrs...)
|
|
|
|
|
*attrs = append(*attrs, semconv.ErrorType(err))
|
2025-08-18 09:37:33 -07:00
|
|
|
|
2025-08-26 09:10:29 -07:00
|
|
|
// Do not inefficiently make a copy of attrs by using
|
|
|
|
|
// WithAttributes instead of WithAttributeSet.
|
|
|
|
|
set := attribute.NewSet(*attrs...)
|
|
|
|
|
mOpt = metric.WithAttributeSet(set)
|
|
|
|
|
|
|
|
|
|
// Reset addOpt with new attribute set.
|
|
|
|
|
*addOpt = append((*addOpt)[:0], mOpt)
|
|
|
|
|
|
|
|
|
|
e.spanExportedMetric.Inst().Add(
|
|
|
|
|
ctx,
|
|
|
|
|
count-success,
|
|
|
|
|
*addOpt...,
|
|
|
|
|
)
|
2025-08-11 22:05:02 +08:00
|
|
|
}
|
|
|
|
|
|
2025-08-26 09:10:29 -07:00
|
|
|
recordOpt := recordOptPool.Get().(*[]metric.RecordOption)
|
|
|
|
|
defer func() {
|
|
|
|
|
*recordOpt = (*recordOpt)[:0]
|
|
|
|
|
recordOptPool.Put(recordOpt)
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
*recordOpt = append(*recordOpt, mOpt)
|
|
|
|
|
e.operationDurationMetric.Inst().Record(
|
|
|
|
|
ctx,
|
|
|
|
|
time.Since(starting).Seconds(),
|
|
|
|
|
*recordOpt...,
|
|
|
|
|
)
|
2025-08-11 22:05:02 +08:00
|
|
|
}(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
|
2025-08-18 09:37:33 -07:00
|
|
|
if e := e.encoder.Encode(stub); e != nil {
|
|
|
|
|
err = errors.Join(err, fmt.Errorf("failed to encode span %d: %w", i, e))
|
|
|
|
|
continue
|
2021-08-25 18:48:48 +03:00
|
|
|
}
|
2025-08-18 09:37:33 -07:00
|
|
|
success++
|
2020-09-09 10:19:03 -07:00
|
|
|
}
|
2025-08-18 09:37:33 -07:00
|
|
|
return err
|
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,
|
|
|
|
|
}
|
|
|
|
|
}
|