From 86862818d339e366d60ed84e4ab85dcfab597abb Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 21 Jul 2020 11:23:01 -0700 Subject: [PATCH 01/19] Consolidate stdout exporter --- exporters/{trace => }/stdout/doc.go | 0 exporters/{metric => }/stdout/example_test.go | 0 exporters/{metric/stdout/stdout.go => stdout/metric.go} | 0 exporters/{metric/stdout/stdout_test.go => stdout/metric_test.go} | 0 exporters/{trace => }/stdout/stdout.go | 0 exporters/{trace => }/stdout/stdout_test.go | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename exporters/{trace => }/stdout/doc.go (100%) rename exporters/{metric => }/stdout/example_test.go (100%) rename exporters/{metric/stdout/stdout.go => stdout/metric.go} (100%) rename exporters/{metric/stdout/stdout_test.go => stdout/metric_test.go} (100%) rename exporters/{trace => }/stdout/stdout.go (100%) rename exporters/{trace => }/stdout/stdout_test.go (100%) diff --git a/exporters/trace/stdout/doc.go b/exporters/stdout/doc.go similarity index 100% rename from exporters/trace/stdout/doc.go rename to exporters/stdout/doc.go diff --git a/exporters/metric/stdout/example_test.go b/exporters/stdout/example_test.go similarity index 100% rename from exporters/metric/stdout/example_test.go rename to exporters/stdout/example_test.go diff --git a/exporters/metric/stdout/stdout.go b/exporters/stdout/metric.go similarity index 100% rename from exporters/metric/stdout/stdout.go rename to exporters/stdout/metric.go diff --git a/exporters/metric/stdout/stdout_test.go b/exporters/stdout/metric_test.go similarity index 100% rename from exporters/metric/stdout/stdout_test.go rename to exporters/stdout/metric_test.go diff --git a/exporters/trace/stdout/stdout.go b/exporters/stdout/stdout.go similarity index 100% rename from exporters/trace/stdout/stdout.go rename to exporters/stdout/stdout.go diff --git a/exporters/trace/stdout/stdout_test.go b/exporters/stdout/stdout_test.go similarity index 100% rename from exporters/trace/stdout/stdout_test.go rename to exporters/stdout/stdout_test.go From 8a052cbf877774ae4e0d390d9ee13019bf94820f Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 21 Jul 2020 11:59:39 -0700 Subject: [PATCH 02/19] Move config to own file and match project standard --- exporters/stdout/config.go | 136 +++++++++++++++++++++++++++++++++++++ exporters/stdout/metric.go | 31 +-------- 2 files changed, 138 insertions(+), 29 deletions(-) create mode 100644 exporters/stdout/config.go diff --git a/exporters/stdout/config.go b/exporters/stdout/config.go new file mode 100644 index 00000000000..ec909e5c1f7 --- /dev/null +++ b/exporters/stdout/config.go @@ -0,0 +1,136 @@ +// 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" +) + +var ( + defaultWriter = os.Stdout + defaultPrettyPrint = false + defaultTimestamps = true + defaultQuantiles = []float64{0.5, 0.9, 0.99} + defaultLabelEncoder = label.DefaultEncoder() +) + +// 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 +} + +func Configure(opts ...Option) Config { + config := Config{ + Writer: defaultWriter, + PrettyPrint: defaultPrettyPrint, + Timestamps: defaultTimestamps, + Quantiles: defaultQuantiles, + LabelEncoder: defaultLabelEncoder, + } + for _, opt := range opts { + opt.Apply(&config) + + } + return config +} + +// 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 +} diff --git a/exporters/stdout/metric.go b/exporters/stdout/metric.go index 4ffadb021c2..f04f4909891 100644 --- a/exporters/stdout/metric.go +++ b/exporters/stdout/metric.go @@ -18,7 +18,6 @@ import ( "context" "encoding/json" "fmt" - "io" "os" "strings" "time" @@ -39,32 +38,6 @@ type Exporter struct { 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"` @@ -154,7 +127,7 @@ func (e *Exporter) ExportKindFor(*metric.Descriptor, aggregation.Kind) export.Ex func (e *Exporter) Export(_ context.Context, checkpointSet export.CheckpointSet) error { var aggError error var batch expoBatch - if !e.config.DoNotPrintTime { + if e.config.Timestamps { ts := time.Now() batch.Timestamp = &ts } @@ -227,7 +200,7 @@ func (e *Exporter) Export(_ context.Context, checkpointSet export.CheckpointSet) } expose.LastValue = value.AsInterface(kind) - if !e.config.DoNotPrintTime { + if e.config.Timestamps { expose.Timestamp = ×tamp } } From 7b033695ceb95e7ffaf6be58a94b4f477ca254de Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 21 Jul 2020 12:51:23 -0700 Subject: [PATCH 03/19] Abstract Exporter into unified struct --- exporters/stdout/exporter.go | 91 ++++++++++++++++++++++++++++++++++++ exporters/stdout/metric.go | 70 ++------------------------- exporters/stdout/stdout.go | 31 ++++++------ 3 files changed, 108 insertions(+), 84 deletions(-) create mode 100644 exporters/stdout/exporter.go diff --git a/exporters/stdout/exporter.go b/exporters/stdout/exporter.go new file mode 100644 index 00000000000..e2be680234f --- /dev/null +++ b/exporters/stdout/exporter.go @@ -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 + +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 := Configure(options...) + 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) (apitrace.Provider, *push.Controller, error) { + traceProvider, controller, err := NewExportPipeline(exportOpts, pushOpts) + if err != nil { + return traceProvider, controller, err + } + global.SetTraceProvider(traceProvider) + global.SetMeterProvider(controller.Provider()) + return traceProvider, controller, err +} diff --git a/exporters/stdout/metric.go b/exporters/stdout/metric.go index f04f4909891..10b8dad65ba 100644 --- a/exporters/stdout/metric.go +++ b/exporters/stdout/metric.go @@ -18,25 +18,21 @@ import ( "context" "encoding/json" "fmt" - "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 { +type metricExporter struct { config Config } -var _ export.Exporter = &Exporter{} +var _ export.Exporter = &metricExporter{} type expoBatch struct { Timestamp *time.Time `json:"time,omitempty"` @@ -62,69 +58,11 @@ type expoQuantile struct { 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 { +func (e *metricExporter) ExportKindFor(*metric.Descriptor, aggregation.Kind) export.ExportKind { return export.PassThroughExporter } -func (e *Exporter) Export(_ context.Context, checkpointSet export.CheckpointSet) error { +func (e *metricExporter) Export(_ context.Context, checkpointSet export.CheckpointSet) error { var aggError error var batch expoBatch if e.config.Timestamps { diff --git a/exporters/stdout/stdout.go b/exporters/stdout/stdout.go index 29f3d0eff04..88b8ae8643f 100644 --- a/exporters/stdout/stdout.go +++ b/exporters/stdout/stdout.go @@ -18,7 +18,6 @@ import ( "context" "encoding/json" "io" - "os" export "go.opentelemetry.io/otel/sdk/export/trace" ) @@ -34,35 +33,31 @@ type Options struct { } // 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 +type traceExporter struct { + config Config } // ExportSpan writes a SpanData in json format to stdout. -func (e *Exporter) ExportSpan(ctx context.Context, data *export.SpanData) { +func (e *traceExporter) ExportSpan(ctx context.Context, data *export.SpanData) { var jsonSpan []byte var err error - if e.pretty { + if e.config.PrettyPrint { 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())) + _, _ = e.config.Writer.Write([]byte("Error converting spanData to json: " + err.Error())) return } // ignore writer failures for now - _, _ = e.outputWriter.Write(append(jsonSpan, byte('\n'))) + _, _ = e.config.Writer.Write(append(jsonSpan, byte('\n'))) +} + +// ExportSpans writes SpanData in json format to stdout. +func (e *traceExporter) ExportSpans(ctx context.Context, data []*export.SpanData) { + for _, sd := range data { + e.ExportSpan(ctx, sd) + } } From 6d442128d1090f0b69a1b9172aedb74e44a6ac2b Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 21 Jul 2020 12:52:44 -0700 Subject: [PATCH 04/19] Rename trace part of the exporter --- exporters/stdout/{stdout.go => trace.go} | 0 exporters/stdout/{stdout_test.go => trace_test.go} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename exporters/stdout/{stdout.go => trace.go} (100%) rename exporters/stdout/{stdout_test.go => trace_test.go} (100%) diff --git a/exporters/stdout/stdout.go b/exporters/stdout/trace.go similarity index 100% rename from exporters/stdout/stdout.go rename to exporters/stdout/trace.go diff --git a/exporters/stdout/stdout_test.go b/exporters/stdout/trace_test.go similarity index 100% rename from exporters/stdout/stdout_test.go rename to exporters/stdout/trace_test.go From 9476bd4e26e6c54d84cefd0fc6dbe4a087bf87a9 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 21 Jul 2020 16:03:17 -0700 Subject: [PATCH 05/19] Update import paths and configuration --- exporters/stdout/config.go | 13 ++++++++++--- exporters/stdout/exporter.go | 5 ++++- exporters/stdout/metric.go | 14 +++++++------- exporters/stdout/trace.go | 17 +++-------------- 4 files changed, 24 insertions(+), 25 deletions(-) diff --git a/exporters/stdout/config.go b/exporters/stdout/config.go index ec909e5c1f7..2dd2104ab67 100644 --- a/exporters/stdout/config.go +++ b/exporters/stdout/config.go @@ -19,6 +19,7 @@ import ( "os" "go.opentelemetry.io/otel/api/label" + "go.opentelemetry.io/otel/sdk/export/metric/aggregation" ) var ( @@ -55,7 +56,8 @@ type Config struct { LabelEncoder label.Encoder } -func Configure(opts ...Option) Config { +// Configure creates a validated Config configured with options. +func Configure(options ...Option) (Config, error) { config := Config{ Writer: defaultWriter, PrettyPrint: defaultPrettyPrint, @@ -63,11 +65,16 @@ func Configure(opts ...Option) Config { Quantiles: defaultQuantiles, LabelEncoder: defaultLabelEncoder, } - for _, opt := range opts { + for _, opt := range options { opt.Apply(&config) } - return 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. diff --git a/exporters/stdout/exporter.go b/exporters/stdout/exporter.go index e2be680234f..c08005a1ec8 100644 --- a/exporters/stdout/exporter.go +++ b/exporters/stdout/exporter.go @@ -37,7 +37,10 @@ var ( // NewExporter creates an Exporter with the passed options. func NewExporter(options ...Option) (*Exporter, error) { - config := Configure(options...) + config, err := Configure(options...) + if err != nil { + return nil, err + } return &Exporter{ traceExporter: traceExporter{config}, metricExporter: metricExporter{config}, diff --git a/exporters/stdout/metric.go b/exporters/stdout/metric.go index 10b8dad65ba..cba65368856 100644 --- a/exporters/stdout/metric.go +++ b/exporters/stdout/metric.go @@ -23,8 +23,8 @@ import ( "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" + apimetric "go.opentelemetry.io/otel/api/metric" + "go.opentelemetry.io/otel/sdk/export/metric" "go.opentelemetry.io/otel/sdk/export/metric/aggregation" ) @@ -32,7 +32,7 @@ type metricExporter struct { config Config } -var _ export.Exporter = &metricExporter{} +var _ metric.Exporter = &metricExporter{} type expoBatch struct { Timestamp *time.Time `json:"time,omitempty"` @@ -58,18 +58,18 @@ type expoQuantile struct { V interface{} `json:"v"` } -func (e *metricExporter) ExportKindFor(*metric.Descriptor, aggregation.Kind) export.ExportKind { - return export.PassThroughExporter +func (e *metricExporter) ExportKindFor(*apimetric.Descriptor, aggregation.Kind) metric.ExportKind { + return metric.PassThroughExporter } -func (e *metricExporter) Export(_ context.Context, checkpointSet export.CheckpointSet) error { +func (e *metricExporter) Export(_ context.Context, checkpointSet metric.CheckpointSet) error { var aggError error var batch expoBatch if e.config.Timestamps { ts := time.Now() batch.Timestamp = &ts } - aggError = checkpointSet.ForEach(e, func(record export.Record) error { + aggError = checkpointSet.ForEach(e, func(record metric.Record) error { desc := record.Descriptor() agg := record.Aggregation() kind := desc.NumberKind() diff --git a/exporters/stdout/trace.go b/exporters/stdout/trace.go index 88b8ae8643f..9bf2d6e2441 100644 --- a/exporters/stdout/trace.go +++ b/exporters/stdout/trace.go @@ -17,28 +17,17 @@ package stdout import ( "context" "encoding/json" - "io" - export "go.opentelemetry.io/otel/sdk/export/trace" + "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 traceExporter struct { config Config } // ExportSpan writes a SpanData in json format to stdout. -func (e *traceExporter) ExportSpan(ctx context.Context, data *export.SpanData) { +func (e *traceExporter) ExportSpan(ctx context.Context, data *trace.SpanData) { var jsonSpan []byte var err error if e.config.PrettyPrint { @@ -56,7 +45,7 @@ func (e *traceExporter) ExportSpan(ctx context.Context, data *export.SpanData) { } // ExportSpans writes SpanData in json format to stdout. -func (e *traceExporter) ExportSpans(ctx context.Context, data []*export.SpanData) { +func (e *traceExporter) ExportSpans(ctx context.Context, data []*trace.SpanData) { for _, sd := range data { e.ExportSpan(ctx, sd) } From e620307ff2224d5fec606a7923409d350a22fe8d Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 21 Jul 2020 16:05:04 -0700 Subject: [PATCH 06/19] Update tests --- ...example_test.go => example_metric_test.go} | 40 ++++++++++++------- exporters/stdout/metric_test.go | 39 +++++++++--------- exporters/stdout/trace_test.go | 5 ++- 3 files changed, 46 insertions(+), 38 deletions(-) rename exporters/stdout/{example_test.go => example_metric_test.go} (63%) diff --git a/exporters/stdout/example_test.go b/exporters/stdout/example_metric_test.go similarity index 63% rename from exporters/stdout/example_test.go rename to exporters/stdout/example_metric_test.go index 60861cdc27d..dc7de4c4cb2 100644 --- a/exporters/stdout/example_test.go +++ b/exporters/stdout/example_metric_test.go @@ -15,39 +15,49 @@ package stdout_test import ( + "bytes" "context" + "fmt" "log" "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 ExampleNewExportPipeline() { - // Create a meter - pusher, err := stdout.NewExportPipeline(stdout.Config{ - PrettyPrint: true, - DoNotPrintTime: true, - }) +func ExampleExport() { + // Default output is STDOUT. + buf := &bytes.Buffer{} + exportOpts := []stdout.Option{ + stdout.WithPrettyPrint(), + // Used in testing to make output predictable. + stdout.WithoutTimestamps(), + stdout.WithWriter(buf), + } + _, pusher, err := stdout.NewExportPipeline(exportOpts, nil) 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")} + // Create a counter. + counter, err := meter.NewInt64Counter("a.counter") + if err != nil { + log.Fatal("Could not initialize a.counter:", err) + } + + // Update the counter. + ctx := context.Background() + counter.Add(ctx, 100, kv.String("key", "value")) - counter.Add(ctx, 100, labels...) + // Flush everything. + pusher.Stop() + fmt.Println(buf.String()) // Output: // { // "updates": [ diff --git a/exporters/stdout/metric_test.go b/exporters/stdout/metric_test.go index 582b7dab0ba..49030ece5e3 100644 --- a/exporters/stdout/metric_test.go +++ b/exporters/stdout/metric_test.go @@ -27,8 +27,8 @@ import ( "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 +49,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 +77,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) } @@ -142,7 +141,7 @@ func TestStdoutTimestamp(t *testing.T) { } func TestStdoutCounterFormat(t *testing.T) { - fix := newFixture(t, stdout.Config{}) + fix := newFixture(t) checkpointSet := test.NewCheckpointSet(testResource) @@ -161,7 +160,7 @@ func TestStdoutCounterFormat(t *testing.T) { } func TestStdoutLastValueFormat(t *testing.T) { - fix := newFixture(t, stdout.Config{}) + fix := newFixture(t) checkpointSet := test.NewCheckpointSet(testResource) @@ -179,7 +178,7 @@ func TestStdoutLastValueFormat(t *testing.T) { } func TestStdoutMinMaxSumCount(t *testing.T) { - fix := newFixture(t, stdout.Config{}) + fix := newFixture(t) checkpointSet := test.NewCheckpointSet(testResource) @@ -199,9 +198,7 @@ func TestStdoutMinMaxSumCount(t *testing.T) { } func TestStdoutValueRecorderFormat(t *testing.T) { - fix := newFixture(t, stdout.Config{ - PrettyPrint: true, - }) + fix := newFixture(t, stdout.WithPrettyPrint()) checkpointSet := test.NewCheckpointSet(testResource) @@ -252,7 +249,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) @@ -271,7 +268,7 @@ func TestStdoutNoData(t *testing.T) { } func TestStdoutLastValueNotSet(t *testing.T) { - fix := newFixture(t, stdout.Config{}) + fix := newFixture(t) checkpointSet := test.NewCheckpointSet(testResource) @@ -322,7 +319,7 @@ func TestStdoutResource(t *testing.T) { } for _, tc := range testCases { - fix := newFixture(t, stdout.Config{}) + fix := newFixture(t) checkpointSet := test.NewCheckpointSet(tc.res) diff --git a/exporters/stdout/trace_test.go b/exporters/stdout/trace_test.go index 3531bc2cce4..e4fb3fa1925 100644 --- a/exporters/stdout/trace_test.go +++ b/exporters/stdout/trace_test.go @@ -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) } From e236935b408212687267ba13afcd1e27a0428864 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 21 Jul 2020 17:04:48 -0700 Subject: [PATCH 07/19] Update InstallNewPipeline to not return traceProvider It is a registered global, access it that way. --- exporters/stdout/exporter.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/exporters/stdout/exporter.go b/exporters/stdout/exporter.go index c08005a1ec8..f8555181370 100644 --- a/exporters/stdout/exporter.go +++ b/exporters/stdout/exporter.go @@ -83,12 +83,12 @@ func NewExportPipeline(exportOpts []Option, pushOpts []push.Option) (apitrace.Pr // } // defer pipeline.Stop() // ... Done -func InstallNewPipeline(exportOpts []Option, pushOpts []push.Option) (apitrace.Provider, *push.Controller, error) { +func InstallNewPipeline(exportOpts []Option, pushOpts []push.Option) (*push.Controller, error) { traceProvider, controller, err := NewExportPipeline(exportOpts, pushOpts) if err != nil { - return traceProvider, controller, err + return controller, err } global.SetTraceProvider(traceProvider) global.SetMeterProvider(controller.Provider()) - return traceProvider, controller, err + return controller, err } From f5ab6739cd0e02242a9dec0879599773c946947e Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 21 Jul 2020 17:05:38 -0700 Subject: [PATCH 08/19] Update example_test --- exporters/stdout/example_metric_test.go | 70 ------------------- exporters/stdout/example_test.go | 93 +++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 70 deletions(-) delete mode 100644 exporters/stdout/example_metric_test.go create mode 100644 exporters/stdout/example_test.go diff --git a/exporters/stdout/example_metric_test.go b/exporters/stdout/example_metric_test.go deleted file mode 100644 index dc7de4c4cb2..00000000000 --- a/exporters/stdout/example_metric_test.go +++ /dev/null @@ -1,70 +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 ( - "bytes" - "context" - "fmt" - "log" - - "go.opentelemetry.io/otel/api/kv" - "go.opentelemetry.io/otel/api/metric" - "go.opentelemetry.io/otel/exporters/stdout" -) - -func ExampleExport() { - // Default output is STDOUT. - buf := &bytes.Buffer{} - exportOpts := []stdout.Option{ - stdout.WithPrettyPrint(), - // Used in testing to make output predictable. - stdout.WithoutTimestamps(), - stdout.WithWriter(buf), - } - _, pusher, err := stdout.NewExportPipeline(exportOpts, nil) - if err != nil { - log.Fatal("Could not initialize stdout exporter:", err) - } - - meter := pusher.Provider().Meter( - "github.com/instrumentron", - metric.WithInstrumentationVersion("v0.1.0"), - ) - - // Create a counter. - counter, err := meter.NewInt64Counter("a.counter") - if err != nil { - log.Fatal("Could not initialize a.counter:", err) - } - - // Update the counter. - ctx := context.Background() - counter.Add(ctx, 100, kv.String("key", "value")) - - // Flush everything. - pusher.Stop() - - fmt.Println(buf.String()) - // Output: - // { - // "updates": [ - // { - // "name": "a.counter{instrumentation.name=github.com/instrumentron,instrumentation.version=v0.1.0,key=value}", - // "sum": 100 - // } - // ] - // } -} diff --git a/exporters/stdout/example_test.go b/exporters/stdout/example_test.go new file mode 100644 index 00000000000..7cd76b2511d --- /dev/null +++ b/exporters/stdout/example_test.go @@ -0,0 +1,93 @@ +// 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 ( + 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") + + tracer = global.TraceProvider().Tracer( + instrumentationName, + trace.WithInstrumentationVersion(instrumentationVersion), + ) +) + +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 ExampleExport() { + exportOpts := []stdout.Option{ + stdout.WithQuantiles([]float64{0.5}), + stdout.WithPrettyPrint(), + // Used in testing to make output predictable. + stdout.WithoutTimestamps(), + } + // 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) + } +} From af2b1a5fc50506283b3fe8720588eb1b2e003e06 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 21 Jul 2020 17:17:11 -0700 Subject: [PATCH 09/19] Update docs --- exporters/stdout/doc.go | 5 +++-- exporters/stdout/metric.go | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/exporters/stdout/doc.go b/exporters/stdout/doc.go index d099942d88b..e6d79f8aa16 100644 --- a/exporters/stdout/doc.go +++ b/exporters/stdout/doc.go @@ -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 teletemtry to be written to an output destination as JSON. +package stdout // import "go.opentelemetry.io/otel/exporters/stdout" diff --git a/exporters/stdout/metric.go b/exporters/stdout/metric.go index cba65368856..b45329e36a5 100644 --- a/exporters/stdout/metric.go +++ b/exporters/stdout/metric.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package stdout // import "go.opentelemetry.io/otel/exporters/metric/stdout" +package stdout import ( "context" From 2c7e05936dbb87ac9b9b6ebb6d4de913c03c2eb1 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 21 Jul 2020 17:55:34 -0700 Subject: [PATCH 10/19] Update example to be for whole package --- exporters/stdout/example_test.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/exporters/stdout/example_test.go b/exporters/stdout/example_test.go index 7cd76b2511d..9ad8df6fab8 100644 --- a/exporters/stdout/example_test.go +++ b/exporters/stdout/example_test.go @@ -31,6 +31,11 @@ const ( ) var ( + tracer = global.TraceProvider().Tracer( + instrumentationName, + trace.WithInstrumentationVersion(instrumentationVersion), + ) + meter = global.MeterProvider().Meter( instrumentationName, metric.WithInstrumentationVersion(instrumentationVersion), @@ -40,11 +45,6 @@ var ( paramValue = metric.Must(meter).NewFloat64ValueRecorder("function.param") nameKey = kv.Key("function.name") - - tracer = global.TraceProvider().Tracer( - instrumentationName, - trace.WithInstrumentationVersion(instrumentationVersion), - ) ) func myFunction(ctx context.Context, values ...float64) error { @@ -58,12 +58,10 @@ func myFunction(ctx context.Context, values ...float64) error { return nil } -func ExampleExport() { +func Example() { exportOpts := []stdout.Option{ stdout.WithQuantiles([]float64{0.5}), stdout.WithPrettyPrint(), - // Used in testing to make output predictable. - stdout.WithoutTimestamps(), } // Registers both a trace and meter Provider globally. pusher, err := stdout.InstallNewPipeline(exportOpts, nil) From 5d5c2f812b95a71f2acefe9696439ca31c27885d Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 21 Jul 2020 18:07:48 -0700 Subject: [PATCH 11/19] Update metric output Closer match the span output. --- exporters/stdout/metric.go | 72 +++++++++++++--------------- exporters/stdout/metric_test.go | 85 +++++++++++++++------------------ 2 files changed, 70 insertions(+), 87 deletions(-) diff --git a/exporters/stdout/metric.go b/exporters/stdout/metric.go index b45329e36a5..2d55c1b51e9 100644 --- a/exporters/stdout/metric.go +++ b/exporters/stdout/metric.go @@ -34,28 +34,23 @@ type metricExporter struct { var _ metric.Exporter = &metricExporter{} -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"` +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 interface{} `json:"quantiles,omitempty"` + Quantiles []quantile `json:"Quantiles,omitempty"` // Note: this is a pointer because omitempty doesn't work when time.IsZero() - Timestamp *time.Time `json:"time,omitempty"` + Timestamp *time.Time `json:"Timestamp,omitempty"` } -type expoQuantile struct { - Q interface{} `json:"q"` - V interface{} `json:"v"` +type quantile struct { + Quantile interface{} `json:"Quantile"` + Value interface{} `json:"Value"` } func (e *metricExporter) ExportKindFor(*apimetric.Descriptor, aggregation.Kind) metric.ExportKind { @@ -64,11 +59,7 @@ func (e *metricExporter) ExportKindFor(*apimetric.Descriptor, aggregation.Kind) func (e *metricExporter) Export(_ context.Context, checkpointSet metric.CheckpointSet) error { var aggError error - var batch expoBatch - if e.config.Timestamps { - ts := time.Now() - batch.Timestamp = &ts - } + var batch []line aggError = checkpointSet.ForEach(e, func(record metric.Record) error { desc := record.Descriptor() agg := record.Aggregation() @@ -85,7 +76,7 @@ func (e *metricExporter) Export(_ context.Context, checkpointSet metric.Checkpoi instSet := label.NewSet(instLabels...) encodedInstLabels := instSet.Encoded(e.config.LabelEncoder) - var expose expoLine + var expose line if sum, ok := agg.(aggregation.Sum); ok { value, err := sum.Sum() @@ -115,19 +106,17 @@ func (e *metricExporter) Export(_ context.Context, checkpointSet metric.Checkpoi expose.Min = min.AsInterface(kind) if dist, ok := agg.(aggregation.Distribution); ok && len(e.config.Quantiles) != 0 { - summary := make([]expoQuantile, len(e.config.Quantiles)) + summary := make([]quantile, 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, + summary[i] = quantile{ + Quantile: q, + Value: value.AsInterface(kind), } } } @@ -169,23 +158,26 @@ func (e *metricExporter) Export(_ context.Context, checkpointSet metric.Checkpoi expose.Name = sb.String() - batch.Updates = append(batch.Updates, expose) + batch = append(batch, 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 len(batch) == 0 { + return aggError } - if err == nil { - fmt.Fprintln(e.config.Writer, string(data)) - } else { + 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) +} diff --git a/exporters/stdout/metric_test.go b/exporters/stdout/metric_test.go index 49030ece5e3..16839ae5e08 100644 --- a/exporters/stdout/metric_test.go +++ b/exporters/stdout/metric_test.go @@ -23,6 +23,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/api/kv" @@ -113,31 +114,23 @@ 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) { @@ -156,7 +149,7 @@ 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) { @@ -174,7 +167,7 @@ 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) { @@ -194,7 +187,7 @@ 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) { @@ -215,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) { @@ -259,7 +250,7 @@ func TestStdoutNoData(t *testing.T) { fix.Export(checkpointSet) - require.Equal(t, `{"updates":null}`, fix.Output()) + require.Equal(t, "", fix.Output()) }) } @@ -281,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) { @@ -333,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()) } } From f4c78bafe5f1e95c4227382be744d3e35b52f251 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 21 Jul 2020 18:08:52 -0700 Subject: [PATCH 12/19] Clean up span output Print as a batch and cleanup marshaling. --- exporters/stdout/trace.go | 31 +++++++++++++++++-------------- exporters/stdout/trace_test.go | 4 ++-- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/exporters/stdout/trace.go b/exporters/stdout/trace.go index 9bf2d6e2441..76ae25e01c0 100644 --- a/exporters/stdout/trace.go +++ b/exporters/stdout/trace.go @@ -17,6 +17,7 @@ package stdout import ( "context" "encoding/json" + "fmt" "go.opentelemetry.io/otel/sdk/export/trace" ) @@ -28,25 +29,27 @@ type traceExporter struct { // ExportSpan writes a SpanData in json format to stdout. func (e *traceExporter) ExportSpan(ctx context.Context, data *trace.SpanData) { - var jsonSpan []byte - var err error - if e.config.PrettyPrint { - jsonSpan, err = json.MarshalIndent(data, "", "\t") - } else { - jsonSpan, err = json.Marshal(data) + 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 len(data) == 0 { + return } + out, err := e.marshal(data) if err != nil { - // ignore writer failures for now - _, _ = e.config.Writer.Write([]byte("Error converting spanData to json: " + err.Error())) + e.config.Writer.Write([]byte("Error converting spanData to json: " + err.Error())) return + } - // ignore writer failures for now - _, _ = e.config.Writer.Write(append(jsonSpan, byte('\n'))) + fmt.Fprintln(e.config.Writer, string(out)) } -// ExportSpans writes SpanData in json format to stdout. -func (e *traceExporter) ExportSpans(ctx context.Context, data []*trace.SpanData) { - for _, sd := range data { - e.ExportSpan(ctx, sd) +// 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) } diff --git a/exporters/stdout/trace_test.go b/exporters/stdout/trace_test.go index e4fb3fa1925..03078d25f33 100644 --- a/exporters/stdout/trace_test.go +++ b/exporters/stdout/trace_test.go @@ -72,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",` + @@ -127,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) From ba3c6b73339aade9472d295a22e05a37574975a6 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 21 Jul 2020 18:22:21 -0700 Subject: [PATCH 13/19] Correct spelling error in doc --- exporters/stdout/doc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exporters/stdout/doc.go b/exporters/stdout/doc.go index e6d79f8aa16..9f577c2e85b 100644 --- a/exporters/stdout/doc.go +++ b/exporters/stdout/doc.go @@ -13,5 +13,5 @@ // limitations under the License. // Package stdout contains an OpenTelemetry exporter for both tracing and -// metric teletemtry to be written to an output destination as JSON. +// metric telemetry to be written to an output destination as JSON. package stdout // import "go.opentelemetry.io/otel/exporters/stdout" From cc52f348206c9569215b60dc0454dd8e7b3da1e7 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 21 Jul 2020 18:22:50 -0700 Subject: [PATCH 14/19] Add Exporters README --- exporters/README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 exporters/README.md diff --git a/exporters/README.md b/exporters/README.md new file mode 100644 index 00000000000..13d52bbfe8a --- /dev/null +++ b/exporters/README.md @@ -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. From 961375515496643d77f716dfba176141284282fa Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 21 Jul 2020 18:31:09 -0700 Subject: [PATCH 15/19] Update Changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c646ecf78b4..f0d64fbbc80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased] +### Changed + +- 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 ### Added From d01b802482510dc18141b2360f74312b4c635089 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 21 Jul 2020 18:59:45 -0700 Subject: [PATCH 16/19] Propagate changes to rest of project --- README.md | 16 ++----- api/global/internal/meter_test.go | 22 +++++----- example/basic/main.go | 44 ++++--------------- example/grpc/config/config.go | 4 +- example/http/client/client.go | 4 +- example/http/server/server.go | 4 +- example/namedtracer/main.go | 4 +- .../othttp/handler_example_test.go | 30 +++---------- sdk/metric/example_test.go | 24 +++++----- 9 files changed, 49 insertions(+), 103 deletions(-) diff --git a/README.md b/README.md index 431fce88a46..3604be53db9 100644 --- a/README.md +++ b/README.md @@ -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)) +func main() { + pusher, err := stdout.InstallNewPipeline(nil, nil) if err != nil { log.Fatal(err) } - global.SetTraceProvider(tp) -} + defer pusher.Stop() -func main() { - initTracer() tracer := global.Tracer("ex.com/basic") tracer.WithSpan(context.Background(), "foo", diff --git a/api/global/internal/meter_test.go b/api/global/internal/meter_test.go index ca47b464972..5413ac25fbe 100644 --- a/api/global/internal/meter_test.go +++ b/api/global/internal/meter_test.go @@ -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()) } diff --git a/example/basic/main.go b/example/basic/main.go index 91135004978..90dc59466cd 100644 --- a/example/basic/main.go +++ b/example/basic/main.go @@ -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, - }) +func main() { + pusher, err := stdout.InstallNewPipeline([]stdout.Option{ + stdout.WithQuantiles([]float64{0.5, 0.9, 0.99}), + stdout.WithPrettyPrint(), + }, nil) if err != nil { - log.Panicf("failed to initialize metric stdout exporter %v", err) + log.Fatalf("failed to initialize stdout export pipeline: %v", err) } - return pusher -} - -func main() { - defer initMeter().Stop() - initTracer() + 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)) diff --git a/example/grpc/config/config.go b/example/grpc/config/config.go index f15b5b6db07..9c1034e7ebf 100644 --- a/example/grpc/config/config.go +++ b/example/grpc/config/config.go @@ -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) } diff --git a/example/http/client/client.go b/example/http/client/client.go index d63ea9579ac..2a4529bb38a 100644 --- a/example/http/client/client.go +++ b/example/http/client/client.go @@ -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) } diff --git a/example/http/server/server.go b/example/http/server/server.go index b6944d5f3bb..d3543818881 100644 --- a/example/http/server/server.go +++ b/example/http/server/server.go @@ -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) } diff --git a/example/namedtracer/main.go b/example/namedtracer/main.go index f0aae45ad75..a98abefc04b 100644 --- a/example/namedtracer/main.go +++ b/example/namedtracer/main.go @@ -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 diff --git a/instrumentation/othttp/handler_example_test.go b/instrumentation/othttp/handler_example_test.go index b819da04727..c01c379ae01 100644 --- a/instrumentation/othttp/handler_example_test.go +++ b/instrumentation/othttp/handler_example_test.go @@ -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) diff --git a/sdk/metric/example_test.go b/sdk/metric/example_test.go index a48b8dfb46c..0669120ed37 100644 --- a/sdk/metric/example_test.go +++ b/sdk/metric/example_test.go @@ -21,14 +21,14 @@ import ( "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 - }) + _, pusher, err := stdout.NewExportPipeline([]stdout.Option{ + stdout.WithPrettyPrint(), + stdout.WithoutTimestamps(), // This makes the output deterministic + }, nil) if err != nil { panic(fmt.Sprintln("Could not initialize stdout exporter:", err)) } @@ -44,12 +44,10 @@ func ExampleNew() { counter.Add(ctx, 100, key.String("value")) // Output: - // { - // "updates": [ - // { - // "name": "a.counter{instrumentation.name=example,key=value}", - // "sum": 100 - // } - // ] - // } + // [ + // { + // "Name": "a.counter{instrumentation.name=example,key=value}", + // "Sum": 100 + // } + // ] } From c027b1a5971fa4ad1e70e0998d4be93b5a1c3723 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 21 Jul 2020 19:02:47 -0700 Subject: [PATCH 17/19] Lint fixes --- exporters/stdout/trace.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exporters/stdout/trace.go b/exporters/stdout/trace.go index 76ae25e01c0..4b04186c46c 100644 --- a/exporters/stdout/trace.go +++ b/exporters/stdout/trace.go @@ -39,7 +39,7 @@ func (e *traceExporter) ExportSpans(ctx context.Context, data []*trace.SpanData) } out, err := e.marshal(data) if err != nil { - e.config.Writer.Write([]byte("Error converting spanData to json: " + err.Error())) + fmt.Fprintf(e.config.Writer, "error converting spanData to json: %v", err) return } From 65d42a491719cceba2a0ffa832d97800863bce4e Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 21 Jul 2020 19:06:04 -0700 Subject: [PATCH 18/19] Fix example test in metric SDK --- sdk/metric/example_test.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/sdk/metric/example_test.go b/sdk/metric/example_test.go index 0669120ed37..28b1c0d40bb 100644 --- a/sdk/metric/example_test.go +++ b/sdk/metric/example_test.go @@ -15,6 +15,7 @@ package metric_test import ( + "bytes" "context" "fmt" @@ -25,24 +26,25 @@ import ( ) func ExampleNew() { + 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: // [ // { From fae3c659342b2acb7afddb06feda5ab72248ad58 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Wed, 22 Jul 2020 11:36:16 -0700 Subject: [PATCH 19/19] Add disable config options for trace and metric --- exporters/stdout/config.go | 54 ++++++++++++++++++++++++++++++-------- exporters/stdout/metric.go | 3 +++ exporters/stdout/trace.go | 5 +++- 3 files changed, 50 insertions(+), 12 deletions(-) diff --git a/exporters/stdout/config.go b/exporters/stdout/config.go index 2dd2104ab67..2255b560958 100644 --- a/exporters/stdout/config.go +++ b/exporters/stdout/config.go @@ -23,11 +23,13 @@ import ( ) var ( - defaultWriter = os.Stdout - defaultPrettyPrint = false - defaultTimestamps = true - defaultQuantiles = []float64{0.5, 0.9, 0.99} - defaultLabelEncoder = label.DefaultEncoder() + 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. @@ -52,18 +54,26 @@ type Config struct { // basis. Quantiles []float64 - // LabelEncoder encodes the labels + // 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, + Writer: defaultWriter, + PrettyPrint: defaultPrettyPrint, + Timestamps: defaultTimestamps, + Quantiles: defaultQuantiles, + LabelEncoder: defaultLabelEncoder, + DisableTraceExport: defaultDisableTraceExport, + DisableMetricExport: defaultDisableMetricExport, } for _, opt := range options { opt.Apply(&config) @@ -141,3 +151,25 @@ type labelEncoderOption struct { 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) +} diff --git a/exporters/stdout/metric.go b/exporters/stdout/metric.go index 2d55c1b51e9..a809809a0ae 100644 --- a/exporters/stdout/metric.go +++ b/exporters/stdout/metric.go @@ -58,6 +58,9 @@ func (e *metricExporter) ExportKindFor(*apimetric.Descriptor, aggregation.Kind) } 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 { diff --git a/exporters/stdout/trace.go b/exporters/stdout/trace.go index 4b04186c46c..af2d3bd81d9 100644 --- a/exporters/stdout/trace.go +++ b/exporters/stdout/trace.go @@ -29,12 +29,15 @@ type traceExporter struct { // 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 len(data) == 0 { + if e.config.DisableTraceExport || len(data) == 0 { return } out, err := e.marshal(data)