1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2025-10-31 00:07:40 +02:00

Unify trace and metric stdout exporters (#956)

* Consolidate stdout exporter

* Move config to own file and match project standard

* Abstract Exporter into unified struct

* Rename trace part of the exporter

* Update import paths and configuration

* Update tests

* Update InstallNewPipeline to not return traceProvider

It is a registered global, access it that way.

* Update example_test

* Update docs

* Update example to be for whole package

* Update metric output

Closer match the span output.

* Clean up span output

Print as a batch and cleanup marshaling.

* Correct spelling error in doc

* Add Exporters README

* Update Changelog

* Propagate changes to rest of project

* Lint fixes

* Fix example test in metric SDK

* Add disable config options for trace and metric

Co-authored-by: Liz Fong-Jones <lizf@honeycomb.io>
This commit is contained in:
Tyler Yahn
2020-07-22 12:34:44 -07:00
committed by GitHub
parent f31d8ec1d0
commit 452256cbf4
22 changed files with 750 additions and 597 deletions

View File

@@ -12,6 +12,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Jaeger exporter helpers: added InstallNewPipeline and removed RegisterGlobal option instead. (#944)
- Zipkin exporter helpers: pipeline methods introduced, new exporter method adjusted. (#944)
- The trace (`go.opentelemetry.io/otel/exporters/trace/stdout`) and metric (`go.opentelemetry.io/otel/exporters/metric/stdout`) `stdout` exporters are now merged into a single exporter at `go.opentelemetry.io/otel/exporters/stdout`. (#956)
## [0.9.0] - 2020-07-20

View File

@@ -38,25 +38,17 @@ import (
"log"
"go.opentelemetry.io/otel/api/global"
"go.opentelemetry.io/otel/exporters/trace/stdout"
"go.opentelemetry.io/otel/exporters/stdout"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
func initTracer() {
exporter, err := stdout.NewExporter(stdout.Options{PrettyPrint: true})
if err != nil {
log.Fatal(err)
}
tp, err := sdktrace.NewProvider(sdktrace.WithConfig(sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}),
sdktrace.WithSyncer(exporter))
if err != nil {
log.Fatal(err)
}
global.SetTraceProvider(tp)
}
func main() {
initTracer()
pusher, err := stdout.InstallNewPipeline(nil, nil)
if err != nil {
log.Fatal(err)
}
defer pusher.Stop()
tracer := global.Tracer("ex.com/basic")
tracer.WithSpan(context.Background(), "foo",

View File

@@ -30,7 +30,7 @@ import (
"go.opentelemetry.io/otel/api/global/internal"
"go.opentelemetry.io/otel/api/kv"
"go.opentelemetry.io/otel/api/metric"
"go.opentelemetry.io/otel/exporters/metric/stdout"
"go.opentelemetry.io/otel/exporters/stdout"
metrictest "go.opentelemetry.io/otel/internal/metric"
)
@@ -243,10 +243,10 @@ func TestDefaultSDK(t *testing.T) {
counter.Add(ctx, 1, labels1...)
in, out := io.Pipe()
pusher, err := stdout.InstallNewPipeline(stdout.Config{
Writer: out,
DoNotPrintTime: true,
})
pusher, err := stdout.InstallNewPipeline([]stdout.Option{
stdout.WithWriter(out),
stdout.WithoutTimestamps(),
}, nil)
if err != nil {
panic(err)
}
@@ -262,7 +262,7 @@ func TestDefaultSDK(t *testing.T) {
pusher.Stop()
out.Close()
require.Equal(t, `{"updates":[{"name":"test.builtin{instrumentation.name=builtin,A=B}","sum":1}]}
require.Equal(t, `[{"Name":"test.builtin{instrumentation.name=builtin,A=B}","Sum":1}]
`, <-ch)
}
@@ -408,10 +408,10 @@ func TestRecordBatchRealSDK(t *testing.T) {
var buf bytes.Buffer
pusher, err := stdout.InstallNewPipeline(stdout.Config{
Writer: &buf,
DoNotPrintTime: true,
})
pusher, err := stdout.InstallNewPipeline([]stdout.Option{
stdout.WithWriter(&buf),
stdout.WithoutTimestamps(),
}, nil)
if err != nil {
t.Fatal(err)
}
@@ -420,6 +420,6 @@ func TestRecordBatchRealSDK(t *testing.T) {
meter.RecordBatch(context.Background(), nil, counter.Measurement(1))
pusher.Stop()
require.Equal(t, `{"updates":[{"name":"test.counter{instrumentation.name=builtin}","sum":1}]}
require.Equal(t, `[{"Name":"test.counter{instrumentation.name=builtin}","Sum":1}]
`, buf.String())
}

View File

@@ -23,11 +23,7 @@ import (
"go.opentelemetry.io/otel/api/kv"
"go.opentelemetry.io/otel/api/metric"
"go.opentelemetry.io/otel/api/trace"
metricstdout "go.opentelemetry.io/otel/exporters/metric/stdout"
tracestdout "go.opentelemetry.io/otel/exporters/trace/stdout"
"go.opentelemetry.io/otel/sdk/metric/controller/push"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/exporters/stdout"
)
var (
@@ -37,37 +33,15 @@ var (
anotherKey = kv.Key("ex.com/another")
)
// initTracer creates and registers trace provider instance.
func initTracer() {
var err error
exp, err := tracestdout.NewExporter(tracestdout.Options{PrettyPrint: false})
if err != nil {
log.Panicf("failed to initialize trace stdout exporter %v", err)
return
}
tp, err := sdktrace.NewProvider(sdktrace.WithSyncer(exp),
sdktrace.WithConfig(sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}),
sdktrace.WithResource(resource.New(kv.String("rk1", "rv11"), kv.Int64("rk2", 5))))
if err != nil {
log.Panicf("failed to initialize trace provider %v", err)
}
global.SetTraceProvider(tp)
}
func initMeter() *push.Controller {
pusher, err := metricstdout.InstallNewPipeline(metricstdout.Config{
Quantiles: []float64{0.5, 0.9, 0.99},
PrettyPrint: false,
})
if err != nil {
log.Panicf("failed to initialize metric stdout exporter %v", err)
}
return pusher
}
func main() {
defer initMeter().Stop()
initTracer()
pusher, err := stdout.InstallNewPipeline([]stdout.Option{
stdout.WithQuantiles([]float64{0.5, 0.9, 0.99}),
stdout.WithPrettyPrint(),
}, nil)
if err != nil {
log.Fatalf("failed to initialize stdout export pipeline: %v", err)
}
defer pusher.Stop()
tracer := global.Tracer("ex.com/basic")
meter := global.Meter("ex.com/basic")
@@ -93,7 +67,7 @@ func main() {
valuerecorder := valuerecorderTwo.Bind(commonLabels...)
defer valuerecorder.Unbind()
err := tracer.WithSpan(ctx, "operation", func(ctx context.Context) error {
err = tracer.WithSpan(ctx, "operation", func(ctx context.Context) error {
trace.SpanFromContext(ctx).AddEvent(ctx, "Nice operation!", kv.Key("bogons").Int(100))

View File

@@ -18,13 +18,13 @@ import (
"log"
"go.opentelemetry.io/otel/api/global"
"go.opentelemetry.io/otel/exporters/trace/stdout"
"go.opentelemetry.io/otel/exporters/stdout"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
// Init configures an OpenTelemetry exporter and trace provider
func Init() {
exporter, err := stdout.NewExporter(stdout.Options{PrettyPrint: true})
exporter, err := stdout.NewExporter(stdout.WithPrettyPrint())
if err != nil {
log.Fatal(err)
}

View File

@@ -30,7 +30,7 @@ import (
"go.opentelemetry.io/otel/api/correlation"
"go.opentelemetry.io/otel/api/global"
"go.opentelemetry.io/otel/exporters/trace/stdout"
"go.opentelemetry.io/otel/exporters/stdout"
"go.opentelemetry.io/otel/instrumentation/httptrace"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
@@ -38,7 +38,7 @@ import (
func initTracer() {
// Create stdout exporter to be able to retrieve
// the collected spans.
exporter, err := stdout.NewExporter(stdout.Options{PrettyPrint: true})
exporter, err := stdout.NewExporter(stdout.WithPrettyPrint())
if err != nil {
log.Fatal(err)
}

View File

@@ -23,7 +23,7 @@ import (
"go.opentelemetry.io/otel/api/global"
"go.opentelemetry.io/otel/api/standard"
"go.opentelemetry.io/otel/api/trace"
"go.opentelemetry.io/otel/exporters/trace/stdout"
"go.opentelemetry.io/otel/exporters/stdout"
"go.opentelemetry.io/otel/instrumentation/httptrace"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
@@ -32,7 +32,7 @@ import (
func initTracer() {
// Create stdout exporter to be able to retrieve
// the collected spans.
exporter, err := stdout.NewExporter(stdout.Options{PrettyPrint: true})
exporter, err := stdout.NewExporter(stdout.WithPrettyPrint())
if err != nil {
log.Fatal(err)
}

View File

@@ -24,7 +24,7 @@ import (
"go.opentelemetry.io/otel/api/global"
"go.opentelemetry.io/otel/api/trace"
"go.opentelemetry.io/otel/example/namedtracer/foo"
"go.opentelemetry.io/otel/exporters/trace/stdout"
"go.opentelemetry.io/otel/exporters/stdout"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
@@ -39,7 +39,7 @@ var tp *sdktrace.Provider
// initTracer creates and registers trace provider instance.
func initTracer() {
var err error
exp, err := stdout.NewExporter(stdout.Options{})
exp, err := stdout.NewExporter(stdout.WithPrettyPrint())
if err != nil {
log.Panicf("failed to initialize stdout exporter %v\n", err)
return

18
exporters/README.md Normal file
View File

@@ -0,0 +1,18 @@
# Exporters
Included in this directory are exporters that export both metric and trace telemetry.
- [stdout](./stdout): Writes telemetry to a specified local output as structured JSON.
- [otlp](./otlp): Sends telemetry to an OpenTelemetry collector as OTLP.
Additionally, there are [metric](./metric) and [trace](./trace) only exporters.
## Metric Telemetry Only
- [prometheus](./metric/prometheus): Exposes metric telemetry as Prometheus metrics.
- [test](./metric/test): A development tool when testing the telemetry pipeline.
## Trace Telemetry Only
- [jaeger](./trace/jaeger): Sends properly transformed trace telemetry to a Jaeger endpoint.
- [zipkin](./trace/zipkin): Sends properly transformed trace telemetry to a Zipkin endpoint.

View File

@@ -1,60 +0,0 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package stdout_test
import (
"context"
"log"
"go.opentelemetry.io/otel/api/kv"
"go.opentelemetry.io/otel/api/metric"
"go.opentelemetry.io/otel/exporters/metric/stdout"
)
func ExampleNewExportPipeline() {
// Create a meter
pusher, err := stdout.NewExportPipeline(stdout.Config{
PrettyPrint: true,
DoNotPrintTime: true,
})
if err != nil {
log.Fatal("Could not initialize stdout exporter:", err)
}
defer pusher.Stop()
ctx := context.Background()
key := kv.Key("key")
meter := pusher.Provider().Meter(
"github.com/instrumentron",
metric.WithInstrumentationVersion("v0.1.0"),
)
// Create and update a single counter:
counter := metric.Must(meter).NewInt64Counter("a.counter")
labels := []kv.KeyValue{key.String("value")}
counter.Add(ctx, 100, labels...)
// Output:
// {
// "updates": [
// {
// "name": "a.counter{instrumentation.name=github.com/instrumentron,instrumentation.version=v0.1.0,key=value}",
// "sum": 100
// }
// ]
// }
}

View File

@@ -1,280 +0,0 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package stdout // import "go.opentelemetry.io/otel/exporters/metric/stdout"
import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"strings"
"time"
"go.opentelemetry.io/otel/api/global"
"go.opentelemetry.io/otel/api/kv"
"go.opentelemetry.io/otel/api/label"
"go.opentelemetry.io/otel/api/metric"
export "go.opentelemetry.io/otel/sdk/export/metric"
"go.opentelemetry.io/otel/sdk/export/metric/aggregation"
"go.opentelemetry.io/otel/sdk/metric/controller/push"
"go.opentelemetry.io/otel/sdk/metric/selector/simple"
)
type Exporter struct {
config Config
}
var _ export.Exporter = &Exporter{}
// Config is the configuration to be used when initializing a stdout export.
type Config struct {
// Writer is the destination. If not set, os.Stdout is used.
Writer io.Writer
// PrettyPrint will pretty the json representation of the span,
// making it print "pretty". Default is false.
PrettyPrint bool
// DoNotPrintTime suppresses timestamp printing. This is
// useful to create deterministic test conditions.
DoNotPrintTime bool
// Quantiles are the desired aggregation quantiles for distribution
// summaries, used when the configured aggregator supports
// quantiles.
//
// Note: this exporter is meant as a demonstration; a real
// exporter may wish to configure quantiles on a per-metric
// basis.
Quantiles []float64
// LabelEncoder encodes the labels
LabelEncoder label.Encoder
}
type expoBatch struct {
Timestamp *time.Time `json:"time,omitempty"`
Updates []expoLine `json:"updates"`
}
type expoLine struct {
Name string `json:"name"`
Min interface{} `json:"min,omitempty"`
Max interface{} `json:"max,omitempty"`
Sum interface{} `json:"sum,omitempty"`
Count interface{} `json:"count,omitempty"`
LastValue interface{} `json:"last,omitempty"`
Quantiles interface{} `json:"quantiles,omitempty"`
// Note: this is a pointer because omitempty doesn't work when time.IsZero()
Timestamp *time.Time `json:"time,omitempty"`
}
type expoQuantile struct {
Q interface{} `json:"q"`
V interface{} `json:"v"`
}
// NewRawExporter creates a stdout Exporter for use in a pipeline.
func NewRawExporter(config Config) (*Exporter, error) {
if config.Writer == nil {
config.Writer = os.Stdout
}
if config.Quantiles == nil {
config.Quantiles = []float64{0.5, 0.9, 0.99}
} else {
for _, q := range config.Quantiles {
if q < 0 || q > 1 {
return nil, aggregation.ErrInvalidQuantile
}
}
}
if config.LabelEncoder == nil {
config.LabelEncoder = label.DefaultEncoder()
}
return &Exporter{
config: config,
}, nil
}
// InstallNewPipeline instantiates a NewExportPipeline and registers it globally.
// Typically called as:
//
// pipeline, err := stdout.InstallNewPipeline(stdout.Config{...})
// if err != nil {
// ...
// }
// defer pipeline.Stop()
// ... Done
func InstallNewPipeline(config Config, options ...push.Option) (*push.Controller, error) {
controller, err := NewExportPipeline(config, options...)
if err != nil {
return controller, err
}
global.SetMeterProvider(controller.Provider())
return controller, err
}
// NewExportPipeline sets up a complete export pipeline with the
// recommended setup, chaining a NewRawExporter into the recommended
// selectors and processors.
func NewExportPipeline(config Config, options ...push.Option) (*push.Controller, error) {
exporter, err := NewRawExporter(config)
if err != nil {
return nil, err
}
pusher := push.New(
simple.NewWithExactDistribution(),
exporter,
options...,
)
pusher.Start()
return pusher, nil
}
func (e *Exporter) ExportKindFor(*metric.Descriptor, aggregation.Kind) export.ExportKind {
return export.PassThroughExporter
}
func (e *Exporter) Export(_ context.Context, checkpointSet export.CheckpointSet) error {
var aggError error
var batch expoBatch
if !e.config.DoNotPrintTime {
ts := time.Now()
batch.Timestamp = &ts
}
aggError = checkpointSet.ForEach(e, func(record export.Record) error {
desc := record.Descriptor()
agg := record.Aggregation()
kind := desc.NumberKind()
encodedResource := record.Resource().Encoded(e.config.LabelEncoder)
var instLabels []kv.KeyValue
if name := desc.InstrumentationName(); name != "" {
instLabels = append(instLabels, kv.String("instrumentation.name", name))
if version := desc.InstrumentationVersion(); version != "" {
instLabels = append(instLabels, kv.String("instrumentation.version", version))
}
}
instSet := label.NewSet(instLabels...)
encodedInstLabels := instSet.Encoded(e.config.LabelEncoder)
var expose expoLine
if sum, ok := agg.(aggregation.Sum); ok {
value, err := sum.Sum()
if err != nil {
return err
}
expose.Sum = value.AsInterface(kind)
}
if mmsc, ok := agg.(aggregation.MinMaxSumCount); ok {
count, err := mmsc.Count()
if err != nil {
return err
}
expose.Count = count
max, err := mmsc.Max()
if err != nil {
return err
}
expose.Max = max.AsInterface(kind)
min, err := mmsc.Min()
if err != nil {
return err
}
expose.Min = min.AsInterface(kind)
if dist, ok := agg.(aggregation.Distribution); ok && len(e.config.Quantiles) != 0 {
summary := make([]expoQuantile, len(e.config.Quantiles))
expose.Quantiles = summary
for i, q := range e.config.Quantiles {
var vstr interface{}
value, err := dist.Quantile(q)
if err != nil {
return err
}
vstr = value.AsInterface(kind)
summary[i] = expoQuantile{
Q: q,
V: vstr,
}
}
}
} else if lv, ok := agg.(aggregation.LastValue); ok {
value, timestamp, err := lv.LastValue()
if err != nil {
return err
}
expose.LastValue = value.AsInterface(kind)
if !e.config.DoNotPrintTime {
expose.Timestamp = &timestamp
}
}
var encodedLabels string
iter := record.Labels().Iter()
if iter.Len() > 0 {
encodedLabels = record.Labels().Encoded(e.config.LabelEncoder)
}
var sb strings.Builder
sb.WriteString(desc.Name())
if len(encodedLabels) > 0 || len(encodedResource) > 0 || len(encodedInstLabels) > 0 {
sb.WriteRune('{')
sb.WriteString(encodedResource)
if len(encodedInstLabels) > 0 && len(encodedResource) > 0 {
sb.WriteRune(',')
}
sb.WriteString(encodedInstLabels)
if len(encodedLabels) > 0 && (len(encodedInstLabels) > 0 || len(encodedResource) > 0) {
sb.WriteRune(',')
}
sb.WriteString(encodedLabels)
sb.WriteRune('}')
}
expose.Name = sb.String()
batch.Updates = append(batch.Updates, expose)
return nil
})
var data []byte
var err error
if e.config.PrettyPrint {
data, err = json.MarshalIndent(batch, "", "\t")
} else {
data, err = json.Marshal(batch)
}
if err == nil {
fmt.Fprintln(e.config.Writer, string(data))
} else {
return err
}
return aggError
}

175
exporters/stdout/config.go Normal file
View File

@@ -0,0 +1,175 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package stdout
import (
"io"
"os"
"go.opentelemetry.io/otel/api/label"
"go.opentelemetry.io/otel/sdk/export/metric/aggregation"
)
var (
defaultWriter = os.Stdout
defaultPrettyPrint = false
defaultTimestamps = true
defaultQuantiles = []float64{0.5, 0.9, 0.99}
defaultLabelEncoder = label.DefaultEncoder()
defaultDisableTraceExport = false
defaultDisableMetricExport = false
)
// Config contains options for the STDOUT exporter.
type Config struct {
// Writer is the destination. If not set, os.Stdout is used.
Writer io.Writer
// PrettyPrint will encode the output into readable JSON. Default is
// false.
PrettyPrint bool
// Timestamps specifies if timestamps should be pritted. Default is
// true.
Timestamps bool
// Quantiles are the desired aggregation quantiles for distribution
// summaries, used when the configured aggregator supports
// quantiles.
//
// Note: this exporter is meant as a demonstration; a real
// exporter may wish to configure quantiles on a per-metric
// basis.
Quantiles []float64
// LabelEncoder encodes the labels.
LabelEncoder label.Encoder
// DisableTraceExport prevents any export of trace telemetry.
DisableTraceExport bool
// DisableMetricExport prevents any export of metric telemetry.
DisableMetricExport bool
}
// Configure creates a validated Config configured with options.
func Configure(options ...Option) (Config, error) {
config := Config{
Writer: defaultWriter,
PrettyPrint: defaultPrettyPrint,
Timestamps: defaultTimestamps,
Quantiles: defaultQuantiles,
LabelEncoder: defaultLabelEncoder,
DisableTraceExport: defaultDisableTraceExport,
DisableMetricExport: defaultDisableMetricExport,
}
for _, opt := range options {
opt.Apply(&config)
}
for _, q := range config.Quantiles {
if q < 0 || q > 1 {
return config, aggregation.ErrInvalidQuantile
}
}
return config, nil
}
// Option sets the value of an option for a Config.
type Option interface {
// Apply option value to Config.
Apply(*Config)
}
// WithWriter sets the export stream destination.
func WithWriter(w io.Writer) Option {
return writerOption{w}
}
type writerOption struct {
W io.Writer
}
func (o writerOption) Apply(config *Config) {
config.Writer = o.W
}
// WithPrettyPrint sets the export stream format to use JSON.
func WithPrettyPrint() Option {
return prettyPrintOption(true)
}
type prettyPrintOption bool
func (o prettyPrintOption) Apply(config *Config) {
config.PrettyPrint = bool(o)
}
// WithoutTimestamps sets the export stream to not include timestamps.
func WithoutTimestamps() Option {
return timestampsOption(false)
}
type timestampsOption bool
func (o timestampsOption) Apply(config *Config) {
config.Timestamps = bool(o)
}
// WithQuantiles sets the quantile values to export.
func WithQuantiles(quantiles []float64) Option {
return quantilesOption(quantiles)
}
type quantilesOption []float64
func (o quantilesOption) Apply(config *Config) {
config.Quantiles = []float64(o)
}
// WithLabelEncoder sets the label encoder used in export.
func WithLabelEncoder(enc label.Encoder) Option {
return labelEncoderOption{enc}
}
type labelEncoderOption struct {
LabelEncoder label.Encoder
}
func (o labelEncoderOption) Apply(config *Config) {
config.LabelEncoder = o.LabelEncoder
}
// WithoutTraceExport disables all trace exporting.
func WithoutTraceExport() Option {
return disableTraceExportOption(true)
}
type disableTraceExportOption bool
func (o disableTraceExportOption) Apply(config *Config) {
config.DisableTraceExport = bool(o)
}
// WithoutMetricExport disables all metric exporting.
func WithoutMetricExport() Option {
return disableMetricExportOption(true)
}
type disableMetricExportOption bool
func (o disableMetricExportOption) Apply(config *Config) {
config.DisableMetricExport = bool(o)
}

View File

@@ -12,5 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// Package stdout contains an OpenTelemetry tracing exporter for writing to stdout.
package stdout // import "go.opentelemetry.io/otel/exporters/trace/stdout"
// Package stdout contains an OpenTelemetry exporter for both tracing and
// metric telemetry to be written to an output destination as JSON.
package stdout // import "go.opentelemetry.io/otel/exporters/stdout"

View File

@@ -0,0 +1,91 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package stdout_test
import (
"context"
"log"
"go.opentelemetry.io/otel/api/global"
"go.opentelemetry.io/otel/api/kv"
"go.opentelemetry.io/otel/api/metric"
"go.opentelemetry.io/otel/api/trace"
"go.opentelemetry.io/otel/exporters/stdout"
)
const (
instrumentationName = "github.com/instrumentron"
instrumentationVersion = "v0.1.0"
)
var (
tracer = global.TraceProvider().Tracer(
instrumentationName,
trace.WithInstrumentationVersion(instrumentationVersion),
)
meter = global.MeterProvider().Meter(
instrumentationName,
metric.WithInstrumentationVersion(instrumentationVersion),
)
loopCounter = metric.Must(meter).NewInt64Counter("function.loops")
paramValue = metric.Must(meter).NewFloat64ValueRecorder("function.param")
nameKey = kv.Key("function.name")
)
func myFunction(ctx context.Context, values ...float64) error {
nameKV := nameKey.String("myFunction")
boundCount := loopCounter.Bind(nameKV)
boundValue := paramValue.Bind(nameKV)
for _, value := range values {
boundCount.Add(ctx, 1)
boundValue.Record(ctx, value)
}
return nil
}
func Example() {
exportOpts := []stdout.Option{
stdout.WithQuantiles([]float64{0.5}),
stdout.WithPrettyPrint(),
}
// Registers both a trace and meter Provider globally.
pusher, err := stdout.InstallNewPipeline(exportOpts, nil)
if err != nil {
log.Fatal("Could not initialize stdout exporter:", err)
}
defer pusher.Stop()
err = tracer.WithSpan(
context.Background(),
"myFunction/call",
func(ctx context.Context) error {
err := tracer.WithSpan(
ctx,
"internal/call",
func(ctx context.Context) error { return myFunction(ctx, 200, 100, 5000, 600) },
)
if err != nil {
return err
}
return myFunction(ctx, 100, 200, 500, 800)
},
)
if err != nil {
log.Fatal("Failed to call myFunction", err)
}
}

View File

@@ -0,0 +1,94 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package stdout
import (
"go.opentelemetry.io/otel/api/global"
apitrace "go.opentelemetry.io/otel/api/trace"
"go.opentelemetry.io/otel/sdk/export/metric"
"go.opentelemetry.io/otel/sdk/export/trace"
"go.opentelemetry.io/otel/sdk/metric/controller/push"
"go.opentelemetry.io/otel/sdk/metric/selector/simple"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
type Exporter struct {
traceExporter
metricExporter
}
var (
_ metric.Exporter = &Exporter{}
_ trace.SpanSyncer = &Exporter{}
_ trace.SpanBatcher = &Exporter{}
)
// NewExporter creates an Exporter with the passed options.
func NewExporter(options ...Option) (*Exporter, error) {
config, err := Configure(options...)
if err != nil {
return nil, err
}
return &Exporter{
traceExporter: traceExporter{config},
metricExporter: metricExporter{config},
}, nil
}
// NewExportPipeline creates a complete export pipeline with the default
// selectors, processors, and trace registration. It is the responsibility
// of the caller to stop the returned push Controller.
func NewExportPipeline(exportOpts []Option, pushOpts []push.Option) (apitrace.Provider, *push.Controller, error) {
exporter, err := NewExporter(exportOpts...)
if err != nil {
return nil, nil, err
}
tp, err := sdktrace.NewProvider(sdktrace.WithSyncer(exporter))
if err != nil {
return nil, nil, err
}
pusher := push.New(
simple.NewWithExactDistribution(),
exporter,
pushOpts...,
)
pusher.Start()
return tp, pusher, nil
}
// InstallNewPipeline creates a complete export pipelines with defaults and
// registers it globally. It is the responsibility of the caller to stop the
// returned push Controller.
//
// Typically this is called as:
//
// pipeline, err := stdout.InstallNewPipeline(stdout.Config{...})
// if err != nil {
// ...
// }
// defer pipeline.Stop()
// ... Done
func InstallNewPipeline(exportOpts []Option, pushOpts []push.Option) (*push.Controller, error) {
traceProvider, controller, err := NewExportPipeline(exportOpts, pushOpts)
if err != nil {
return controller, err
}
global.SetTraceProvider(traceProvider)
global.SetMeterProvider(controller.Provider())
return controller, err
}

186
exporters/stdout/metric.go Normal file
View File

@@ -0,0 +1,186 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package stdout
import (
"context"
"encoding/json"
"fmt"
"strings"
"time"
"go.opentelemetry.io/otel/api/kv"
"go.opentelemetry.io/otel/api/label"
apimetric "go.opentelemetry.io/otel/api/metric"
"go.opentelemetry.io/otel/sdk/export/metric"
"go.opentelemetry.io/otel/sdk/export/metric/aggregation"
)
type metricExporter struct {
config Config
}
var _ metric.Exporter = &metricExporter{}
type line struct {
Name string `json:"Name"`
Min interface{} `json:"Min,omitempty"`
Max interface{} `json:"Max,omitempty"`
Sum interface{} `json:"Sum,omitempty"`
Count interface{} `json:"Count,omitempty"`
LastValue interface{} `json:"Last,omitempty"`
Quantiles []quantile `json:"Quantiles,omitempty"`
// Note: this is a pointer because omitempty doesn't work when time.IsZero()
Timestamp *time.Time `json:"Timestamp,omitempty"`
}
type quantile struct {
Quantile interface{} `json:"Quantile"`
Value interface{} `json:"Value"`
}
func (e *metricExporter) ExportKindFor(*apimetric.Descriptor, aggregation.Kind) metric.ExportKind {
return metric.PassThroughExporter
}
func (e *metricExporter) Export(_ context.Context, checkpointSet metric.CheckpointSet) error {
if e.config.DisableMetricExport {
return nil
}
var aggError error
var batch []line
aggError = checkpointSet.ForEach(e, func(record metric.Record) error {
desc := record.Descriptor()
agg := record.Aggregation()
kind := desc.NumberKind()
encodedResource := record.Resource().Encoded(e.config.LabelEncoder)
var instLabels []kv.KeyValue
if name := desc.InstrumentationName(); name != "" {
instLabels = append(instLabels, kv.String("instrumentation.name", name))
if version := desc.InstrumentationVersion(); version != "" {
instLabels = append(instLabels, kv.String("instrumentation.version", version))
}
}
instSet := label.NewSet(instLabels...)
encodedInstLabels := instSet.Encoded(e.config.LabelEncoder)
var expose line
if sum, ok := agg.(aggregation.Sum); ok {
value, err := sum.Sum()
if err != nil {
return err
}
expose.Sum = value.AsInterface(kind)
}
if mmsc, ok := agg.(aggregation.MinMaxSumCount); ok {
count, err := mmsc.Count()
if err != nil {
return err
}
expose.Count = count
max, err := mmsc.Max()
if err != nil {
return err
}
expose.Max = max.AsInterface(kind)
min, err := mmsc.Min()
if err != nil {
return err
}
expose.Min = min.AsInterface(kind)
if dist, ok := agg.(aggregation.Distribution); ok && len(e.config.Quantiles) != 0 {
summary := make([]quantile, len(e.config.Quantiles))
expose.Quantiles = summary
for i, q := range e.config.Quantiles {
value, err := dist.Quantile(q)
if err != nil {
return err
}
summary[i] = quantile{
Quantile: q,
Value: value.AsInterface(kind),
}
}
}
} else if lv, ok := agg.(aggregation.LastValue); ok {
value, timestamp, err := lv.LastValue()
if err != nil {
return err
}
expose.LastValue = value.AsInterface(kind)
if e.config.Timestamps {
expose.Timestamp = &timestamp
}
}
var encodedLabels string
iter := record.Labels().Iter()
if iter.Len() > 0 {
encodedLabels = record.Labels().Encoded(e.config.LabelEncoder)
}
var sb strings.Builder
sb.WriteString(desc.Name())
if len(encodedLabels) > 0 || len(encodedResource) > 0 || len(encodedInstLabels) > 0 {
sb.WriteRune('{')
sb.WriteString(encodedResource)
if len(encodedInstLabels) > 0 && len(encodedResource) > 0 {
sb.WriteRune(',')
}
sb.WriteString(encodedInstLabels)
if len(encodedLabels) > 0 && (len(encodedInstLabels) > 0 || len(encodedResource) > 0) {
sb.WriteRune(',')
}
sb.WriteString(encodedLabels)
sb.WriteRune('}')
}
expose.Name = sb.String()
batch = append(batch, expose)
return nil
})
if len(batch) == 0 {
return aggError
}
data, err := e.marshal(batch)
if err != nil {
return err
}
fmt.Fprintln(e.config.Writer, string(data))
return aggError
}
// marshal v with approriate indentation.
func (e *metricExporter) marshal(v interface{}) ([]byte, error) {
if e.config.PrettyPrint {
return json.MarshalIndent(v, "", "\t")
}
return json.Marshal(v)
}

View File

@@ -23,12 +23,13 @@ import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/api/kv"
"go.opentelemetry.io/otel/api/metric"
"go.opentelemetry.io/otel/exporters/metric/stdout"
"go.opentelemetry.io/otel/exporters/metric/test"
"go.opentelemetry.io/otel/exporters/stdout"
export "go.opentelemetry.io/otel/sdk/export/metric"
"go.opentelemetry.io/otel/sdk/export/metric/aggregation"
"go.opentelemetry.io/otel/sdk/metric/aggregator/array"
@@ -49,11 +50,11 @@ type testFixture struct {
var testResource = resource.New(kv.String("R", "V"))
func newFixture(t *testing.T, config stdout.Config) testFixture {
func newFixture(t *testing.T, opts ...stdout.Option) testFixture {
buf := &bytes.Buffer{}
config.Writer = buf
config.DoNotPrintTime = true
exp, err := stdout.NewRawExporter(config)
opts = append(opts, stdout.WithWriter(buf))
opts = append(opts, stdout.WithoutTimestamps())
exp, err := stdout.NewExporter(opts...)
if err != nil {
t.Fatal("Error building fixture: ", err)
}
@@ -77,19 +78,18 @@ func (fix testFixture) Export(checkpointSet export.CheckpointSet) {
}
func TestStdoutInvalidQuantile(t *testing.T) {
_, err := stdout.NewRawExporter(stdout.Config{
Quantiles: []float64{1.1, 0.9},
})
_, err := stdout.NewExporter(
stdout.WithQuantiles([]float64{1.1, 0.9}),
)
require.Error(t, err, "Invalid quantile error expected")
require.Equal(t, aggregation.ErrInvalidQuantile, err)
}
func TestStdoutTimestamp(t *testing.T) {
var buf bytes.Buffer
exporter, err := stdout.NewRawExporter(stdout.Config{
Writer: &buf,
DoNotPrintTime: false,
})
exporter, err := stdout.NewExporter(
stdout.WithWriter(&buf),
)
if err != nil {
t.Fatal("Invalid config: ", err)
}
@@ -114,35 +114,27 @@ func TestStdoutTimestamp(t *testing.T) {
after := time.Now()
var printed map[string]interface{}
var printed []interface{}
if err := json.Unmarshal(buf.Bytes(), &printed); err != nil {
t.Fatal("JSON parse error: ", err)
}
updateTS := printed["time"].(string)
updateTimestamp, err := time.Parse(time.RFC3339Nano, updateTS)
if err != nil {
t.Fatal("JSON parse error: ", updateTS, ": ", err)
}
lastValueTS := printed["updates"].([]interface{})[0].(map[string]interface{})["time"].(string)
require.Len(t, printed, 1)
lastValue, ok := printed[0].(map[string]interface{})
require.True(t, ok, "last value format")
require.Contains(t, lastValue, "Timestamp")
lastValueTS := lastValue["Timestamp"].(string)
lastValueTimestamp, err := time.Parse(time.RFC3339Nano, lastValueTS)
if err != nil {
t.Fatal("JSON parse error: ", lastValueTS, ": ", err)
}
require.True(t, updateTimestamp.After(before))
require.True(t, updateTimestamp.Before(after))
require.True(t, lastValueTimestamp.After(before))
require.True(t, lastValueTimestamp.Before(after))
require.True(t, lastValueTimestamp.Before(updateTimestamp))
assert.True(t, lastValueTimestamp.After(before))
assert.True(t, lastValueTimestamp.Before(after))
}
func TestStdoutCounterFormat(t *testing.T) {
fix := newFixture(t, stdout.Config{})
fix := newFixture(t)
checkpointSet := test.NewCheckpointSet(testResource)
@@ -157,11 +149,11 @@ func TestStdoutCounterFormat(t *testing.T) {
fix.Export(checkpointSet)
require.Equal(t, `{"updates":[{"name":"test.name{R=V,A=B,C=D}","sum":123}]}`, fix.Output())
require.Equal(t, `[{"Name":"test.name{R=V,A=B,C=D}","Sum":123}]`, fix.Output())
}
func TestStdoutLastValueFormat(t *testing.T) {
fix := newFixture(t, stdout.Config{})
fix := newFixture(t)
checkpointSet := test.NewCheckpointSet(testResource)
@@ -175,11 +167,11 @@ func TestStdoutLastValueFormat(t *testing.T) {
fix.Export(checkpointSet)
require.Equal(t, `{"updates":[{"name":"test.name{R=V,A=B,C=D}","last":123.456}]}`, fix.Output())
require.Equal(t, `[{"Name":"test.name{R=V,A=B,C=D}","Last":123.456}]`, fix.Output())
}
func TestStdoutMinMaxSumCount(t *testing.T) {
fix := newFixture(t, stdout.Config{})
fix := newFixture(t)
checkpointSet := test.NewCheckpointSet(testResource)
@@ -195,13 +187,11 @@ func TestStdoutMinMaxSumCount(t *testing.T) {
fix.Export(checkpointSet)
require.Equal(t, `{"updates":[{"name":"test.name{R=V,A=B,C=D}","min":123.456,"max":876.543,"sum":999.999,"count":2}]}`, fix.Output())
require.Equal(t, `[{"Name":"test.name{R=V,A=B,C=D}","Min":123.456,"Max":876.543,"Sum":999.999,"Count":2}]`, fix.Output())
}
func TestStdoutValueRecorderFormat(t *testing.T) {
fix := newFixture(t, stdout.Config{
PrettyPrint: true,
})
fix := newFixture(t, stdout.WithPrettyPrint())
checkpointSet := test.NewCheckpointSet(testResource)
@@ -218,31 +208,29 @@ func TestStdoutValueRecorderFormat(t *testing.T) {
fix.Export(checkpointSet)
require.Equal(t, `{
"updates": [
{
"name": "test.name{R=V,A=B,C=D}",
"min": 0.5,
"max": 999.5,
"sum": 500000,
"count": 1000,
"quantiles": [
{
"q": 0.5,
"v": 500.5
},
{
"q": 0.9,
"v": 900.5
},
{
"q": 0.99,
"v": 990.5
}
]
}
]
}`, fix.Output())
require.Equal(t, `[
{
"Name": "test.name{R=V,A=B,C=D}",
"Min": 0.5,
"Max": 999.5,
"Sum": 500000,
"Count": 1000,
"Quantiles": [
{
"Quantile": 0.5,
"Value": 500.5
},
{
"Quantile": 0.9,
"Value": 900.5
},
{
"Quantile": 0.99,
"Value": 990.5
}
]
}
]`, fix.Output())
}
func TestStdoutNoData(t *testing.T) {
@@ -252,7 +240,7 @@ func TestStdoutNoData(t *testing.T) {
t.Run(fmt.Sprintf("%T", agg), func(t *testing.T) {
t.Parallel()
fix := newFixture(t, stdout.Config{})
fix := newFixture(t)
checkpointSet := test.NewCheckpointSet(testResource)
@@ -262,7 +250,7 @@ func TestStdoutNoData(t *testing.T) {
fix.Export(checkpointSet)
require.Equal(t, `{"updates":null}`, fix.Output())
require.Equal(t, "", fix.Output())
})
}
@@ -271,7 +259,7 @@ func TestStdoutNoData(t *testing.T) {
}
func TestStdoutLastValueNotSet(t *testing.T) {
fix := newFixture(t, stdout.Config{})
fix := newFixture(t)
checkpointSet := test.NewCheckpointSet(testResource)
@@ -284,7 +272,7 @@ func TestStdoutLastValueNotSet(t *testing.T) {
fix.Export(checkpointSet)
require.Equal(t, `{"updates":null}`, fix.Output())
require.Equal(t, "", fix.Output())
}
func TestStdoutResource(t *testing.T) {
@@ -322,7 +310,7 @@ func TestStdoutResource(t *testing.T) {
}
for _, tc := range testCases {
fix := newFixture(t, stdout.Config{})
fix := newFixture(t)
checkpointSet := test.NewCheckpointSet(tc.res)
@@ -336,6 +324,6 @@ func TestStdoutResource(t *testing.T) {
fix.Export(checkpointSet)
require.Equal(t, `{"updates":[{"name":"test.name{`+tc.expect+`}","last":123.456}]}`, fix.Output())
require.Equal(t, `[{"Name":"test.name{`+tc.expect+`}","Last":123.456}]`, fix.Output())
}
}

58
exporters/stdout/trace.go Normal file
View File

@@ -0,0 +1,58 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package stdout
import (
"context"
"encoding/json"
"fmt"
"go.opentelemetry.io/otel/sdk/export/trace"
)
// Exporter is an implementation of trace.SpanSyncer that writes spans to stdout.
type traceExporter struct {
config Config
}
// ExportSpan writes a SpanData in json format to stdout.
func (e *traceExporter) ExportSpan(ctx context.Context, data *trace.SpanData) {
if e.config.DisableTraceExport {
return
}
e.ExportSpans(ctx, []*trace.SpanData{data})
}
// ExportSpans writes SpanData in json format to stdout.
func (e *traceExporter) ExportSpans(ctx context.Context, data []*trace.SpanData) {
if e.config.DisableTraceExport || len(data) == 0 {
return
}
out, err := e.marshal(data)
if err != nil {
fmt.Fprintf(e.config.Writer, "error converting spanData to json: %v", err)
return
}
fmt.Fprintln(e.config.Writer, string(out))
}
// marshal v with approriate indentation.
func (e *traceExporter) marshal(v interface{}) ([]byte, error) {
if e.config.PrettyPrint {
return json.MarshalIndent(v, "", "\t")
}
return json.Marshal(v)
}

View File

@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package stdout
package stdout_test
import (
"bytes"
@@ -25,6 +25,7 @@ import (
"go.opentelemetry.io/otel/api/kv"
"go.opentelemetry.io/otel/api/trace"
"go.opentelemetry.io/otel/exporters/stdout"
export "go.opentelemetry.io/otel/sdk/export/trace"
"go.opentelemetry.io/otel/sdk/resource"
)
@@ -32,7 +33,7 @@ import (
func TestExporter_ExportSpan(t *testing.T) {
// write to buffer for testing
var b bytes.Buffer
ex, err := NewExporter(Options{Writer: &b})
ex, err := stdout.NewExporter(stdout.WithWriter(&b))
if err != nil {
t.Errorf("Error constructing stdout exporter %s", err)
}
@@ -71,7 +72,7 @@ func TestExporter_ExportSpan(t *testing.T) {
expectedSerializedNow, _ := json.Marshal(now)
got := b.String()
expectedOutput := `{"SpanContext":{` +
expectedOutput := `[{"SpanContext":{` +
`"TraceID":"0102030405060708090a0b0c0d0e0f10",` +
`"SpanID":"0102030405060708","TraceFlags":0},` +
`"ParentSpanID":"0000000000000000",` +
@@ -126,7 +127,7 @@ func TestExporter_ExportSpan(t *testing.T) {
`"InstrumentationLibrary":{` +
`"Name":"",` +
`"Version":""` +
`}}` + "\n"
`}}]` + "\n"
if got != expectedOutput {
t.Errorf("Want: %v but got: %v", expectedOutput, got)

View File

@@ -1,68 +0,0 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package stdout
import (
"context"
"encoding/json"
"io"
"os"
export "go.opentelemetry.io/otel/sdk/export/trace"
)
// Options are the options to be used when initializing a stdout export.
type Options struct {
// Writer is the destination. If not set, os.Stdout is used.
Writer io.Writer
// PrettyPrint will pretty the json representation of the span,
// making it print "pretty". Default is false.
PrettyPrint bool
}
// Exporter is an implementation of trace.SpanSyncer that writes spans to stdout.
type Exporter struct {
pretty bool
outputWriter io.Writer
}
func NewExporter(o Options) (*Exporter, error) {
if o.Writer == nil {
o.Writer = os.Stdout
}
return &Exporter{
pretty: o.PrettyPrint,
outputWriter: o.Writer,
}, nil
}
// ExportSpan writes a SpanData in json format to stdout.
func (e *Exporter) ExportSpan(ctx context.Context, data *export.SpanData) {
var jsonSpan []byte
var err error
if e.pretty {
jsonSpan, err = json.MarshalIndent(data, "", "\t")
} else {
jsonSpan, err = json.Marshal(data)
}
if err != nil {
// ignore writer failures for now
_, _ = e.outputWriter.Write([]byte("Error converting spanData to json: " + err.Error()))
return
}
// ignore writer failures for now
_, _ = e.outputWriter.Write(append(jsonSpan, byte('\n')))
}

View File

@@ -24,12 +24,9 @@ import (
"go.opentelemetry.io/otel/api/kv"
"go.opentelemetry.io/otel/api/global"
"go.opentelemetry.io/otel/api/trace"
mstdout "go.opentelemetry.io/otel/exporters/metric/stdout"
"go.opentelemetry.io/otel/exporters/trace/stdout"
"go.opentelemetry.io/otel/exporters/stdout"
"go.opentelemetry.io/otel/instrumentation/othttp"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
func ExampleNewHandler() {
@@ -48,29 +45,14 @@ func ExampleNewHandler() {
*/
// Write spans to stdout
exporter, err := stdout.NewExporter(stdout.Options{PrettyPrint: true})
pusher, err := stdout.InstallNewPipeline([]stdout.Option{
stdout.WithPrettyPrint(),
stdout.WithoutTimestamps(), // This makes the output deterministic
}, nil)
if err != nil {
log.Fatal(err)
}
tp, err := sdktrace.NewProvider(sdktrace.WithConfig(sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}),
sdktrace.WithSyncer(exporter))
if err != nil {
log.Fatal(err)
}
pusher, err := mstdout.NewExportPipeline(mstdout.Config{
PrettyPrint: true,
DoNotPrintTime: true, // This makes the output deterministic
})
if err != nil {
log.Fatal(err)
}
meterProvider := pusher.Provider()
global.SetTraceProvider(tp)
global.SetMeterProvider(meterProvider)
defer pusher.Stop()
figureOutName := func(ctx context.Context, s string) (string, error) {
pp := strings.SplitN(s, "/", 2)

View File

@@ -15,41 +15,41 @@
package metric_test
import (
"bytes"
"context"
"fmt"
"go.opentelemetry.io/otel/api/kv"
"go.opentelemetry.io/otel/api/metric"
"go.opentelemetry.io/otel/exporters/metric/stdout"
"go.opentelemetry.io/otel/exporters/stdout"
)
func ExampleNew() {
pusher, err := stdout.NewExportPipeline(stdout.Config{
PrettyPrint: true,
DoNotPrintTime: true, // This makes the output deterministic
})
buf := bytes.Buffer{}
_, pusher, err := stdout.NewExportPipeline([]stdout.Option{
// Defaults to STDOUT.
stdout.WithWriter(&buf),
stdout.WithPrettyPrint(),
stdout.WithoutTimestamps(), // This makes the output deterministic
}, nil)
if err != nil {
panic(fmt.Sprintln("Could not initialize stdout exporter:", err))
}
defer pusher.Stop()
ctx := context.Background()
meter := metric.Must(pusher.Provider().Meter("example"))
counter := meter.NewInt64Counter("a.counter")
counter.Add(context.Background(), 100, kv.String("key", "value"))
key := kv.Key("key")
meter := pusher.Provider().Meter("example")
counter := metric.Must(meter).NewInt64Counter("a.counter")
counter.Add(ctx, 100, key.String("value"))
// Flush everything
pusher.Stop()
fmt.Println(buf.String())
// Output:
// {
// "updates": [
// {
// "name": "a.counter{instrumentation.name=example,key=value}",
// "sum": 100
// }
// ]
// }
// [
// {
// "Name": "a.counter{instrumentation.name=example,key=value}",
// "Sum": 100
// }
// ]
}