You've already forked opentelemetry-go
							
							
				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:
		| @@ -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 | ||||
|  | ||||
|   | ||||
							
								
								
									
										22
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								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)) | ||||
| 	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", | ||||
|   | ||||
| @@ -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()) | ||||
| } | ||||
|   | ||||
| @@ -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)) | ||||
|  | ||||
|   | ||||
| @@ -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) | ||||
| 	} | ||||
|   | ||||
| @@ -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) | ||||
| 	} | ||||
|   | ||||
| @@ -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) | ||||
| 	} | ||||
|   | ||||
| @@ -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
									
								
							
							
						
						
									
										18
									
								
								exporters/README.md
									
									
									
									
									
										Normal 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. | ||||
| @@ -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 | ||||
| 	// 		} | ||||
| 	// 	] | ||||
| 	// } | ||||
| } | ||||
| @@ -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 = ×tamp | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		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
									
								
							
							
						
						
									
										175
									
								
								exporters/stdout/config.go
									
									
									
									
									
										Normal 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) | ||||
| } | ||||
| @@ -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" | ||||
							
								
								
									
										91
									
								
								exporters/stdout/example_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								exporters/stdout/example_test.go
									
									
									
									
									
										Normal 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) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										94
									
								
								exporters/stdout/exporter.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								exporters/stdout/exporter.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										186
									
								
								exporters/stdout/metric.go
									
									
									
									
									
										Normal 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 = ×tamp | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		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) | ||||
| } | ||||
| @@ -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
									
								
							
							
						
						
									
										58
									
								
								exporters/stdout/trace.go
									
									
									
									
									
										Normal 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) | ||||
| } | ||||
| @@ -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) | ||||
| @@ -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'))) | ||||
| } | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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 | ||||
| 	// 	} | ||||
| 	// ] | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user