From 2bde0593131b299b5f8d7da100122c8a2b74fd39 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 2 Aug 2022 09:02:10 -0700 Subject: [PATCH 01/20] PoC stdoutmetric exporter --- .github/dependabot.yml | 9 + exporters/stdout/stdoutmetric/config.go | 66 ++++++ exporters/stdout/stdoutmetric/doc.go | 22 ++ exporters/stdout/stdoutmetric/encoder.go | 49 ++++ exporters/stdout/stdoutmetric/example_test.go | 220 ++++++++++++++++++ exporters/stdout/stdoutmetric/exporter.go | 75 ++++++ exporters/stdout/stdoutmetric/go.mod | 27 +++ exporters/stdout/stdoutmetric/go.sum | 12 + sdk/instrumentation/scope.go | 2 +- sdk/metric/metricdata/data.go | 8 +- sdk/metric/metricdata/temporality.go | 15 ++ 11 files changed, 500 insertions(+), 5 deletions(-) create mode 100644 exporters/stdout/stdoutmetric/config.go create mode 100644 exporters/stdout/stdoutmetric/doc.go create mode 100644 exporters/stdout/stdoutmetric/encoder.go create mode 100644 exporters/stdout/stdoutmetric/example_test.go create mode 100644 exporters/stdout/stdoutmetric/exporter.go create mode 100644 exporters/stdout/stdoutmetric/go.mod create mode 100644 exporters/stdout/stdoutmetric/go.sum diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 556c5ad0309..cd0fec29c29 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -127,6 +127,15 @@ updates: schedule: interval: weekly day: sunday + - package-ecosystem: gomod + directory: /exporters/stdout/stdoutmetric + labels: + - dependencies + - go + - Skip Changelog + schedule: + interval: weekly + day: sunday - package-ecosystem: gomod directory: /exporters/stdout/stdouttrace labels: diff --git a/exporters/stdout/stdoutmetric/config.go b/exporters/stdout/stdoutmetric/config.go new file mode 100644 index 00000000000..04168a3867a --- /dev/null +++ b/exporters/stdout/stdoutmetric/config.go @@ -0,0 +1,66 @@ +// 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. + +//go:build go1.18 +// +build go1.18 + +package stdoutmetric + +import ( + "encoding/json" + "os" +) + +// config contains options for the exporter. +type config struct { + encoder *encoderHolder +} + +// newConfig creates a validated config configured with options. +func newConfig(options ...Option) (config, error) { + cfg := config{} + for _, opt := range options { + cfg = opt.apply(cfg) + } + + if cfg.encoder == nil { + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", "\t") + cfg.encoder = &encoderHolder{encoder: enc} + } + + return cfg, nil +} + +// Option sets exporter option values. +type Option interface { + apply(config) config +} + +type optionFunc func(config) config + +func (o optionFunc) apply(c config) config { + return o(c) +} + +// WithEncoder sets the exporter to use ecoder to encode all the metric +// data-types to an output. +func WithEncoder(encoder Encoder) Option { + return optionFunc(func(c config) config { + if encoder != nil { + c.encoder = &encoderHolder{encoder: encoder} + } + return c + }) +} diff --git a/exporters/stdout/stdoutmetric/doc.go b/exporters/stdout/stdoutmetric/doc.go new file mode 100644 index 00000000000..4710c73f2f3 --- /dev/null +++ b/exporters/stdout/stdoutmetric/doc.go @@ -0,0 +1,22 @@ +// 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 stdoutmetric provides an exporter for OpenTelemetry metric +// telemetry. +// +// The exporter is intended to be used for testing and debugging. It is not an +// exporter meant for production use. Additionally, it does not provide an +// interchange format for OpenTelemetry that is supported with any stability +// or compatibility guarantees. Please use the OTLP instead. +package stdoutmetric // import "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" diff --git a/exporters/stdout/stdoutmetric/encoder.go b/exporters/stdout/stdoutmetric/encoder.go new file mode 100644 index 00000000000..dbfc78e493a --- /dev/null +++ b/exporters/stdout/stdoutmetric/encoder.go @@ -0,0 +1,49 @@ +// 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. + +//go:build go1.18 +// +build go1.18 + +package stdoutmetric + +import "errors" + +var ErrUnrecognized = errors.New("unrecognized metric data") + +// Encoder encodes and outputs OpenTelemetry metric data-types as human +// readable text. +type Encoder interface { + // Encode handles the encoding and writing OpenTelemetry metric data-types + // that the exporter will pass to it. + // + // Any data-type that is not recognized by the encoder and not output to + // the user, will have an ErrUnrecognized returned from Encode. + Encode(v any) error +} + +// encoderHolder is the concrete type used to wrap an Encoder so it can be +// used as a atomic.Value type. +type encoderHolder struct { + encoder Encoder +} + +func (e encoderHolder) Encode(v any) error { return e.encoder.Encode(v) } + +// shutdownEncoder is used when the exporter is shutdown. It always returns +// errShutdown when Encode is called. +type shutdownEncoder struct{} + +var errShutdown = errors.New("exporter shutdown") + +func (shutdownEncoder) Encode(any) error { return errShutdown } diff --git a/exporters/stdout/stdoutmetric/example_test.go b/exporters/stdout/stdoutmetric/example_test.go new file mode 100644 index 00000000000..7403c03c3fc --- /dev/null +++ b/exporters/stdout/stdoutmetric/example_test.go @@ -0,0 +1,220 @@ +// 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. + +//go:build go1.18 +// +build go1.18 + +package stdoutmetric_test + +import ( + "context" + "encoding/json" + "os" + "time" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" + "go.opentelemetry.io/otel/metric/unit" + "go.opentelemetry.io/otel/sdk/instrumentation" + "go.opentelemetry.io/otel/sdk/metric/metricdata" + "go.opentelemetry.io/otel/sdk/resource" + semconv "go.opentelemetry.io/otel/semconv/v1.10.0" +) + +// Sat Jan 01 2000 00:00:00 GMT+0000. +var now = time.Unix(946684800, 0) + +var mockData = metricdata.ResourceMetrics{ + Resource: resource.NewSchemaless(semconv.ServiceNameKey.String("stdoutmetric-example")), + ScopeMetrics: []metricdata.ScopeMetrics{ + { + Scope: instrumentation.Scope{ + Name: "example", + Version: "v0.0.1", + }, + Metrics: []metricdata.Metrics{ + { + Name: "requests", + Description: "Number of requests received", + Unit: unit.Dimensionless, + Data: metricdata.Sum[int64]{ + IsMonotonic: true, + Temporality: metricdata.DeltaTemporality, + DataPoints: []metricdata.DataPoint[int64]{ + { + Attributes: attribute.NewSet(attribute.String("server", "central")), + StartTime: now, + Time: now.Add(1 * time.Second), + Value: 5, + }, + }, + }, + }, + { + Name: "latency", + Description: "Time spend processing received requests", + Unit: unit.Milliseconds, + Data: metricdata.Histogram{ + Temporality: metricdata.DeltaTemporality, + DataPoints: []metricdata.HistogramDataPoint{ + { + Attributes: attribute.NewSet(attribute.String("server", "central")), + StartTime: now, + Time: now.Add(1 * time.Second), + Count: 10, + Bounds: []float64{1, 5, 10}, + BucketCounts: []uint64{1, 3, 6, 0}, + Sum: 57, + }, + }, + }, + }, + { + Name: "temperature", + Description: "CPU global temperature", + Unit: unit.Unit("cel(1 K)"), + Data: metricdata.Gauge[float64]{ + DataPoints: []metricdata.DataPoint[float64]{ + { + Attributes: attribute.NewSet(attribute.String("server", "central")), + Time: now.Add(1 * time.Second), + Value: 32.4, + }, + }, + }, + }, + }, + }, + }, +} + +func Example() { + // Print with a JSON encoder that indents with two spaces. + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", " ") + exp, err := stdoutmetric.New(stdoutmetric.WithEncoder(enc)) + if err != nil { + panic(err) + } + // FIXME: The exporter should be registered with an SDK and the output + // should come from that. + exp.Export(context.Background(), mockData) + + // Output: + // { + // "Resource": [ + // { + // "Key": "service.name", + // "Value": { + // "Type": "STRING", + // "Value": "stdoutmetric-example" + // } + // } + // ], + // "ScopeMetrics": [ + // { + // "Scope": { + // "Name": "example", + // "Version": "v0.0.1" + // }, + // "Metrics": [ + // { + // "Name": "requests", + // "Description": "Number of requests received", + // "Unit": "1", + // "Data": { + // "DataPoints": [ + // { + // "Attributes": [ + // { + // "Key": "server", + // "Value": { + // "Type": "STRING", + // "Value": "central" + // } + // } + // ], + // "StartTime": "1999-12-31T16:00:00-08:00", + // "Time": "1999-12-31T16:00:01-08:00", + // "Value": 5 + // } + // ], + // "Temporality": "delta", + // "IsMonotonic": true + // } + // }, + // { + // "Name": "latency", + // "Description": "Time spend processing received requests", + // "Unit": "ms", + // "Data": { + // "DataPoints": [ + // { + // "Attributes": [ + // { + // "Key": "server", + // "Value": { + // "Type": "STRING", + // "Value": "central" + // } + // } + // ], + // "StartTime": "1999-12-31T16:00:00-08:00", + // "Time": "1999-12-31T16:00:01-08:00", + // "Count": 10, + // "Bounds": [ + // 1, + // 5, + // 10 + // ], + // "BucketCounts": [ + // 1, + // 3, + // 6, + // 0 + // ], + // "Sum": 57 + // } + // ], + // "Temporality": "delta" + // } + // }, + // { + // "Name": "temperature", + // "Description": "CPU global temperature", + // "Unit": "cel(1 K)", + // "Data": { + // "DataPoints": [ + // { + // "Attributes": [ + // { + // "Key": "server", + // "Value": { + // "Type": "STRING", + // "Value": "central" + // } + // } + // ], + // "StartTime": "0001-01-01T00:00:00Z", + // "Time": "1999-12-31T16:00:01-08:00", + // "Value": 32.4 + // } + // ] + // } + // } + // ] + // } + // ] + // } +} diff --git a/exporters/stdout/stdoutmetric/exporter.go b/exporters/stdout/stdoutmetric/exporter.go new file mode 100644 index 00000000000..084c887c9bb --- /dev/null +++ b/exporters/stdout/stdoutmetric/exporter.go @@ -0,0 +1,75 @@ +// 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. + +//go:build go1.18 +// +build go1.18 + +package stdoutmetric // import "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" + +import ( + "context" + "sync" + "sync/atomic" + + "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/metric/metricdata" +) + +// exporter is an OpenTelemetry metric exporter. +type exporter struct { + encVal atomic.Value // encoderHolder + + shutdownOnce sync.Once +} + +// New returns a configured metric Exporter. +// +// If no options are passed, the default exporter returned will use a JSON +// encoder with tab indentations. +func New(options ...Option) (metric.Exporter, error) { + cfg, err := newConfig(options...) + if err != nil { + return nil, err + } + + exp := &exporter{} + exp.encVal.Store(*cfg.encoder) + return exp, nil +} + +func (e *exporter) Export(ctx context.Context, data metricdata.ResourceMetrics) error { + select { + case <-ctx.Done(): + // Don't do anything if the context has already timed out. + return ctx.Err() + default: + // Context is still valid, continue. + } + + return e.encVal.Load().(encoderHolder).Encode(data) +} + +func (e *exporter) ForceFlush(context.Context) error { + // exporter holds no state, nothing to flush. + return nil +} + +func (e *exporter) Shutdown(context.Context) error { + e.shutdownOnce.Do(func() { + e.encVal.Store(encoderHolder{ + encoder: shutdownEncoder{}, + }) + }) + return nil +} diff --git a/exporters/stdout/stdoutmetric/go.mod b/exporters/stdout/stdoutmetric/go.mod new file mode 100644 index 00000000000..b6940a6db45 --- /dev/null +++ b/exporters/stdout/stdoutmetric/go.mod @@ -0,0 +1,27 @@ +module go.opentelemetry.io/otel/exporters/stdout/stdoutmetric + +go 1.18 + +require ( + go.opentelemetry.io/otel v1.7.0 + go.opentelemetry.io/otel/sdk v0.0.0-00010101000000-000000000000 + go.opentelemetry.io/otel/sdk/metric v0.0.0-00010101000000-000000000000 +) + +require ( + github.com/go-logr/logr v1.2.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + go.opentelemetry.io/otel/metric v0.0.0-00010101000000-000000000000 // indirect + go.opentelemetry.io/otel/trace v1.7.0 // indirect + golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7 // indirect +) + +replace go.opentelemetry.io/otel/metric => ../../../metric + +replace go.opentelemetry.io/otel => ../../.. + +replace go.opentelemetry.io/otel/sdk/metric => ../../../sdk/metric + +replace go.opentelemetry.io/otel/trace => ../../../trace + +replace go.opentelemetry.io/otel/sdk => ../../../sdk diff --git a/exporters/stdout/stdoutmetric/go.sum b/exporters/stdout/stdoutmetric/go.sum new file mode 100644 index 00000000000..2af466654a5 --- /dev/null +++ b/exporters/stdout/stdoutmetric/go.sum @@ -0,0 +1,12 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7 h1:iGu644GcxtEcrInvDsQRCwJjtCIOlT2V7IRt6ah2Whw= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= diff --git a/sdk/instrumentation/scope.go b/sdk/instrumentation/scope.go index 775de40e30c..bbbed01c5b3 100644 --- a/sdk/instrumentation/scope.go +++ b/sdk/instrumentation/scope.go @@ -29,5 +29,5 @@ type Scope struct { // Version is the version of the instrumentation scope. Version string // SchemaURL of the telemetry emitted by the scope. - SchemaURL string + SchemaURL string `json:",omitempty"` } diff --git a/sdk/metric/metricdata/data.go b/sdk/metric/metricdata/data.go index b770b702346..f02adbbdf71 100644 --- a/sdk/metric/metricdata/data.go +++ b/sdk/metric/metricdata/data.go @@ -91,9 +91,9 @@ type DataPoint[N int64 | float64] struct { // timeseries. Attributes attribute.Set // StartTime is when the timeseries was started. (optional) - StartTime time.Time + StartTime time.Time `json:",omitempty"` // Time is the time when the timeseries was recorded. (optional) - Time time.Time + Time time.Time `json:",omitempty"` // Value is the value of this data point. Value N } @@ -128,9 +128,9 @@ type HistogramDataPoint struct { BucketCounts []uint64 // Min is the minimum value recorded. (optional) - Min *float64 + Min *float64 `json:",omitempty"` // Max is the maximum value recorded. (optional) - Max *float64 + Max *float64 `json:",omitempty"` // Sum is the sum of the values recorded. Sum float64 } diff --git a/sdk/metric/metricdata/temporality.go b/sdk/metric/metricdata/temporality.go index 561fdab50c3..f058bac1aad 100644 --- a/sdk/metric/metricdata/temporality.go +++ b/sdk/metric/metricdata/temporality.go @@ -35,3 +35,18 @@ const ( // from other cycles do not affect them. DeltaTemporality ) + +var temporalityString = map[Temporality]string{ + CumulativeTemporality: "cumulative", + DeltaTemporality: "delta", +} + +// String returns the string name of t. +func (t Temporality) String() string { + return temporalityString[t] +} + +// MarshalText returns the byte encoded of t. +func (t Temporality) MarshalText() ([]byte, error) { + return []byte(temporalityString[t]), nil +} From 406fc8a1a72e1e930778d971e74225c8095174e7 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 2 Aug 2022 11:11:55 -0700 Subject: [PATCH 02/20] Use stringer to generate String for Temporality --- sdk/metric/metricdata/temporality.go | 13 ++--------- sdk/metric/metricdata/temporality_string.go | 25 +++++++++++++++++++++ 2 files changed, 27 insertions(+), 11 deletions(-) create mode 100644 sdk/metric/metricdata/temporality_string.go diff --git a/sdk/metric/metricdata/temporality.go b/sdk/metric/metricdata/temporality.go index f058bac1aad..5cf105b7947 100644 --- a/sdk/metric/metricdata/temporality.go +++ b/sdk/metric/metricdata/temporality.go @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:generate stringer -type=Temporality //go:build go1.17 // +build go1.17 @@ -36,17 +37,7 @@ const ( DeltaTemporality ) -var temporalityString = map[Temporality]string{ - CumulativeTemporality: "cumulative", - DeltaTemporality: "delta", -} - -// String returns the string name of t. -func (t Temporality) String() string { - return temporalityString[t] -} - // MarshalText returns the byte encoded of t. func (t Temporality) MarshalText() ([]byte, error) { - return []byte(temporalityString[t]), nil + return []byte(t.String()), nil } diff --git a/sdk/metric/metricdata/temporality_string.go b/sdk/metric/metricdata/temporality_string.go new file mode 100644 index 00000000000..4da833cdce2 --- /dev/null +++ b/sdk/metric/metricdata/temporality_string.go @@ -0,0 +1,25 @@ +// Code generated by "stringer -type=Temporality"; DO NOT EDIT. + +package metricdata + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[undefinedTemporality-0] + _ = x[CumulativeTemporality-1] + _ = x[DeltaTemporality-2] +} + +const _Temporality_name = "undefinedTemporalityCumulativeTemporalityDeltaTemporality" + +var _Temporality_index = [...]uint8{0, 20, 41, 57} + +func (i Temporality) String() string { + if i >= Temporality(len(_Temporality_index)-1) { + return "Temporality(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Temporality_name[_Temporality_index[i]:_Temporality_index[i+1]] +} From 308007d7f38589913c0e1d2dd9fe9d203513b286 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 2 Aug 2022 11:12:28 -0700 Subject: [PATCH 03/20] Add vanity imports --- exporters/stdout/stdoutmetric/config.go | 2 +- exporters/stdout/stdoutmetric/encoder.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/exporters/stdout/stdoutmetric/config.go b/exporters/stdout/stdoutmetric/config.go index 04168a3867a..b18dc0b280c 100644 --- a/exporters/stdout/stdoutmetric/config.go +++ b/exporters/stdout/stdoutmetric/config.go @@ -15,7 +15,7 @@ //go:build go1.18 // +build go1.18 -package stdoutmetric +package stdoutmetric // import "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" import ( "encoding/json" diff --git a/exporters/stdout/stdoutmetric/encoder.go b/exporters/stdout/stdoutmetric/encoder.go index dbfc78e493a..de397f2953e 100644 --- a/exporters/stdout/stdoutmetric/encoder.go +++ b/exporters/stdout/stdoutmetric/encoder.go @@ -15,7 +15,7 @@ //go:build go1.18 // +build go1.18 -package stdoutmetric +package stdoutmetric // import "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" import "errors" From d9a6180c9eeaa941cd21916100b834357e246a83 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 2 Aug 2022 11:13:29 -0700 Subject: [PATCH 04/20] Update Temporality string expected output --- exporters/stdout/stdoutmetric/example_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exporters/stdout/stdoutmetric/example_test.go b/exporters/stdout/stdoutmetric/example_test.go index 7403c03c3fc..4fb2eb7d893 100644 --- a/exporters/stdout/stdoutmetric/example_test.go +++ b/exporters/stdout/stdoutmetric/example_test.go @@ -150,7 +150,7 @@ func Example() { // "Value": 5 // } // ], - // "Temporality": "delta", + // "Temporality": "DeltaTemporality", // "IsMonotonic": true // } // }, @@ -187,7 +187,7 @@ func Example() { // "Sum": 57 // } // ], - // "Temporality": "delta" + // "Temporality": "DeltaTemporality" // } // }, // { From 949a0aad1dc70e8070706b01ff16bbbfd0ea63e8 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 2 Aug 2022 11:14:46 -0700 Subject: [PATCH 05/20] Do not return error from newConfig --- exporters/stdout/stdoutmetric/config.go | 5 ++--- exporters/stdout/stdoutmetric/exporter.go | 6 +----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/exporters/stdout/stdoutmetric/config.go b/exporters/stdout/stdoutmetric/config.go index b18dc0b280c..b044ff7993d 100644 --- a/exporters/stdout/stdoutmetric/config.go +++ b/exporters/stdout/stdoutmetric/config.go @@ -1,5 +1,4 @@ // 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 @@ -28,7 +27,7 @@ type config struct { } // newConfig creates a validated config configured with options. -func newConfig(options ...Option) (config, error) { +func newConfig(options ...Option) config { cfg := config{} for _, opt := range options { cfg = opt.apply(cfg) @@ -40,7 +39,7 @@ func newConfig(options ...Option) (config, error) { cfg.encoder = &encoderHolder{encoder: enc} } - return cfg, nil + return cfg } // Option sets exporter option values. diff --git a/exporters/stdout/stdoutmetric/exporter.go b/exporters/stdout/stdoutmetric/exporter.go index 084c887c9bb..448fd373cce 100644 --- a/exporters/stdout/stdoutmetric/exporter.go +++ b/exporters/stdout/stdoutmetric/exporter.go @@ -38,11 +38,7 @@ type exporter struct { // If no options are passed, the default exporter returned will use a JSON // encoder with tab indentations. func New(options ...Option) (metric.Exporter, error) { - cfg, err := newConfig(options...) - if err != nil { - return nil, err - } - + cfg := newConfig(options...) exp := &exporter{} exp.encVal.Store(*cfg.encoder) return exp, nil From e0ffcd5dbb07371c3cbc4d58528f5f921ab52714 Mon Sep 17 00:00:00 2001 From: Aaron Clawson <3766680+MadVikingGod@users.noreply.github.com> Date: Tue, 2 Aug 2022 15:14:48 +0000 Subject: [PATCH 06/20] Add shutdown unit tests --- exporters/stdout/stdoutmetric/exporter.go | 8 +-- .../stdout/stdoutmetric/exporter_test.go | 68 +++++++++++++++++++ exporters/stdout/stdoutmetric/go.mod | 6 +- exporters/stdout/stdoutmetric/go.sum | 7 ++ 4 files changed, 84 insertions(+), 5 deletions(-) create mode 100644 exporters/stdout/stdoutmetric/exporter_test.go diff --git a/exporters/stdout/stdoutmetric/exporter.go b/exporters/stdout/stdoutmetric/exporter.go index 448fd373cce..c72fef6c39c 100644 --- a/exporters/stdout/stdoutmetric/exporter.go +++ b/exporters/stdout/stdoutmetric/exporter.go @@ -56,16 +56,16 @@ func (e *exporter) Export(ctx context.Context, data metricdata.ResourceMetrics) return e.encVal.Load().(encoderHolder).Encode(data) } -func (e *exporter) ForceFlush(context.Context) error { +func (e *exporter) ForceFlush(ctx context.Context) error { // exporter holds no state, nothing to flush. - return nil + return ctx.Err() } -func (e *exporter) Shutdown(context.Context) error { +func (e *exporter) Shutdown(ctx context.Context) error { e.shutdownOnce.Do(func() { e.encVal.Store(encoderHolder{ encoder: shutdownEncoder{}, }) }) - return nil + return ctx.Err() } diff --git a/exporters/stdout/stdoutmetric/exporter_test.go b/exporters/stdout/stdoutmetric/exporter_test.go new file mode 100644 index 00000000000..43128899ecd --- /dev/null +++ b/exporters/stdout/stdoutmetric/exporter_test.go @@ -0,0 +1,68 @@ +// 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. + +//go:build go1.18 +// +build go1.18 + +package stdoutmetric // import "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestExporterShutdownHonorsTimeout(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) + defer cancel() + + e, err := New() + if err != nil { + t.Fatalf("failed to create exporter: %v", err) + } + + innerCtx, innerCancel := context.WithTimeout(ctx, time.Nanosecond) + defer innerCancel() + <-innerCtx.Done() + err = e.Shutdown(innerCtx) + assert.ErrorIs(t, err, context.DeadlineExceeded) +} + +func TestExporterShutdownHonorsCancel(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) + defer cancel() + + e, err := New() + if err != nil { + t.Fatalf("failed to create exporter: %v", err) + } + + innerCtx, innerCancel := context.WithCancel(ctx) + innerCancel() + err = e.Shutdown(innerCtx) + assert.ErrorIs(t, err, context.Canceled) +} + +func TestExporterShutdownNoError(t *testing.T) { + e, err := New() + if err != nil { + t.Fatalf("failed to create exporter: %v", err) + } + + if err := e.Shutdown(context.Background()); err != nil { + t.Errorf("shutdown errored: expected nil, got %v", err) + } +} diff --git a/exporters/stdout/stdoutmetric/go.mod b/exporters/stdout/stdoutmetric/go.mod index b6940a6db45..7795f9d53e5 100644 --- a/exporters/stdout/stdoutmetric/go.mod +++ b/exporters/stdout/stdoutmetric/go.mod @@ -3,17 +3,21 @@ module go.opentelemetry.io/otel/exporters/stdout/stdoutmetric go 1.18 require ( + github.com/stretchr/testify v1.7.1 go.opentelemetry.io/otel v1.7.0 + go.opentelemetry.io/otel/metric v0.0.0-00010101000000-000000000000 go.opentelemetry.io/otel/sdk v0.0.0-00010101000000-000000000000 go.opentelemetry.io/otel/sdk/metric v0.0.0-00010101000000-000000000000 ) require ( + github.com/davecgh/go-spew v1.1.0 // indirect github.com/go-logr/logr v1.2.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - go.opentelemetry.io/otel/metric v0.0.0-00010101000000-000000000000 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel/trace v1.7.0 // indirect golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7 // indirect + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect ) replace go.opentelemetry.io/otel/metric => ../../../metric diff --git a/exporters/stdout/stdoutmetric/go.sum b/exporters/stdout/stdoutmetric/go.sum index 2af466654a5..2e2aed63d24 100644 --- a/exporters/stdout/stdoutmetric/go.sum +++ b/exporters/stdout/stdoutmetric/go.sum @@ -1,4 +1,5 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -6,7 +7,13 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7 h1:iGu644GcxtEcrInvDsQRCwJjtCIOlT2V7IRt6ah2Whw= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 949cf28367e99698beb8a94fe4603a29f750254f Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 2 Aug 2022 11:24:16 -0700 Subject: [PATCH 07/20] Fix spelling error --- exporters/stdout/stdoutmetric/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exporters/stdout/stdoutmetric/config.go b/exporters/stdout/stdoutmetric/config.go index b044ff7993d..63ba9fc592e 100644 --- a/exporters/stdout/stdoutmetric/config.go +++ b/exporters/stdout/stdoutmetric/config.go @@ -53,7 +53,7 @@ func (o optionFunc) apply(c config) config { return o(c) } -// WithEncoder sets the exporter to use ecoder to encode all the metric +// WithEncoder sets the exporter to use encoder to encode all the metric // data-types to an output. func WithEncoder(encoder Encoder) Option { return optionFunc(func(c config) config { From ad8785f90a5e0f5e1a52946d5d9f668dfd3c4a5c Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 2 Aug 2022 11:40:21 -0700 Subject: [PATCH 08/20] Unify testing of ctx errors and test ForceFlush --- .../stdout/stdoutmetric/exporter_test.go | 70 ++++++++++--------- 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/exporters/stdout/stdoutmetric/exporter_test.go b/exporters/stdout/stdoutmetric/exporter_test.go index 43128899ecd..a6562d54994 100644 --- a/exporters/stdout/stdoutmetric/exporter_test.go +++ b/exporters/stdout/stdoutmetric/exporter_test.go @@ -15,7 +15,7 @@ //go:build go1.18 // +build go1.18 -package stdoutmetric // import "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" +package stdoutmetric_test // import "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" import ( "context" @@ -23,46 +23,50 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" ) -func TestExporterShutdownHonorsTimeout(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) - defer cancel() +func testCtxErrHonored(factory func(*testing.T) func(context.Context) error) func(t *testing.T) { + return func(t *testing.T) { + t.Helper() + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) - e, err := New() - if err != nil { - t.Fatalf("failed to create exporter: %v", err) - } + t.Run("DeadlineExceeded", func(t *testing.T) { + innerCtx, innerCancel := context.WithTimeout(ctx, time.Nanosecond) + t.Cleanup(innerCancel) + <-innerCtx.Done() - innerCtx, innerCancel := context.WithTimeout(ctx, time.Nanosecond) - defer innerCancel() - <-innerCtx.Done() - err = e.Shutdown(innerCtx) - assert.ErrorIs(t, err, context.DeadlineExceeded) -} + f := factory(t) + assert.ErrorIs(t, f(innerCtx), context.DeadlineExceeded) + }) -func TestExporterShutdownHonorsCancel(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) - defer cancel() + t.Run("Canceled", func(t *testing.T) { + innerCtx, innerCancel := context.WithCancel(ctx) + innerCancel() - e, err := New() - if err != nil { - t.Fatalf("failed to create exporter: %v", err) - } + f := factory(t) + assert.ErrorIs(t, f(innerCtx), context.Canceled) + }) - innerCtx, innerCancel := context.WithCancel(ctx) - innerCancel() - err = e.Shutdown(innerCtx) - assert.ErrorIs(t, err, context.Canceled) + t.Run("NoError", func(t *testing.T) { + f := factory(t) + assert.NoError(t, f(ctx)) + }) + } } -func TestExporterShutdownNoError(t *testing.T) { - e, err := New() - if err != nil { - t.Fatalf("failed to create exporter: %v", err) - } +func TestExporterHonorsContextErrors(t *testing.T) { + t.Run("Shutdown", testCtxErrHonored(func(t *testing.T) func(context.Context) error { + exp, err := stdoutmetric.New() + require.NoError(t, err) + return exp.Shutdown + })) - if err := e.Shutdown(context.Background()); err != nil { - t.Errorf("shutdown errored: expected nil, got %v", err) - } + t.Run("ForceFlush", testCtxErrHonored(func(t *testing.T) func(context.Context) error { + exp, err := stdoutmetric.New() + require.NoError(t, err) + return exp.ForceFlush + })) } From 31f86bbac7f351d37b5f8996ba3fb473550eb347 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 2 Aug 2022 11:43:43 -0700 Subject: [PATCH 09/20] Add unit test for Export handle of ctx errs --- exporters/stdout/stdoutmetric/exporter_test.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/exporters/stdout/stdoutmetric/exporter_test.go b/exporters/stdout/stdoutmetric/exporter_test.go index a6562d54994..db4a3bc535b 100644 --- a/exporters/stdout/stdoutmetric/exporter_test.go +++ b/exporters/stdout/stdoutmetric/exporter_test.go @@ -25,6 +25,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" + "go.opentelemetry.io/otel/sdk/metric/metricdata" ) func testCtxErrHonored(factory func(*testing.T) func(context.Context) error) func(t *testing.T) { @@ -69,4 +70,13 @@ func TestExporterHonorsContextErrors(t *testing.T) { require.NoError(t, err) return exp.ForceFlush })) + + t.Run("Export", testCtxErrHonored(func(t *testing.T) func(context.Context) error { + exp, err := stdoutmetric.New() + require.NoError(t, err) + return func(ctx context.Context) error { + var data metricdata.ResourceMetrics + return exp.Export(ctx, data) + } + })) } From d4319c9da36e5a2c82ecf9cacdbe1901acf4c705 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 2 Aug 2022 12:03:15 -0700 Subject: [PATCH 10/20] Clarify documentation about alt OTLP exporter --- exporters/stdout/stdoutmetric/doc.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/exporters/stdout/stdoutmetric/doc.go b/exporters/stdout/stdoutmetric/doc.go index 4710c73f2f3..eea70f8a6fa 100644 --- a/exporters/stdout/stdoutmetric/doc.go +++ b/exporters/stdout/stdoutmetric/doc.go @@ -18,5 +18,6 @@ // The exporter is intended to be used for testing and debugging. It is not an // exporter meant for production use. Additionally, it does not provide an // interchange format for OpenTelemetry that is supported with any stability -// or compatibility guarantees. Please use the OTLP instead. +// or compatibility guarantees. If these are needed features, please use the +// OTLP exporter instead. package stdoutmetric // import "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" From 0f82e3867d4be1dc06f3c8be83c87d108a08af03 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 2 Aug 2022 12:03:36 -0700 Subject: [PATCH 11/20] Remove unused ErrUnrecognized A third party encoder can produce their own errors. This code does nothing unique with this error, therefore, it is removed. --- exporters/stdout/stdoutmetric/encoder.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/exporters/stdout/stdoutmetric/encoder.go b/exporters/stdout/stdoutmetric/encoder.go index de397f2953e..b2bc34c9f13 100644 --- a/exporters/stdout/stdoutmetric/encoder.go +++ b/exporters/stdout/stdoutmetric/encoder.go @@ -19,16 +19,11 @@ package stdoutmetric // import "go.opentelemetry.io/otel/exporters/stdout/stdout import "errors" -var ErrUnrecognized = errors.New("unrecognized metric data") - // Encoder encodes and outputs OpenTelemetry metric data-types as human // readable text. type Encoder interface { // Encode handles the encoding and writing OpenTelemetry metric data-types // that the exporter will pass to it. - // - // Any data-type that is not recognized by the encoder and not output to - // the user, will have an ErrUnrecognized returned from Encode. Encode(v any) error } From 25f2f0ae646399456b1851b778d4e74e71dc9633 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 2 Aug 2022 12:04:41 -0700 Subject: [PATCH 12/20] Lint exporter_test.go --- exporters/stdout/stdoutmetric/exporter_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/exporters/stdout/stdoutmetric/exporter_test.go b/exporters/stdout/stdoutmetric/exporter_test.go index db4a3bc535b..25b26fb1587 100644 --- a/exporters/stdout/stdoutmetric/exporter_test.go +++ b/exporters/stdout/stdoutmetric/exporter_test.go @@ -24,6 +24,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" "go.opentelemetry.io/otel/sdk/metric/metricdata" ) From 830e97a55ad23400192e9bfb71992e0316c9f8c2 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 2 Aug 2022 12:05:38 -0700 Subject: [PATCH 13/20] Refactor example_test.go removing FIXME --- exporters/stdout/stdoutmetric/example_test.go | 128 ++++++++++-------- 1 file changed, 72 insertions(+), 56 deletions(-) diff --git a/exporters/stdout/stdoutmetric/example_test.go b/exporters/stdout/stdoutmetric/example_test.go index 4fb2eb7d893..d19c3eee9c8 100644 --- a/exporters/stdout/stdoutmetric/example_test.go +++ b/exporters/stdout/stdoutmetric/example_test.go @@ -27,77 +27,81 @@ import ( "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" "go.opentelemetry.io/otel/metric/unit" "go.opentelemetry.io/otel/sdk/instrumentation" + "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.10.0" ) -// Sat Jan 01 2000 00:00:00 GMT+0000. -var now = time.Unix(946684800, 0) +var ( + // Sat Jan 01 2000 00:00:00 GMT+0000. + now = time.Unix(946684800, 0) -var mockData = metricdata.ResourceMetrics{ - Resource: resource.NewSchemaless(semconv.ServiceNameKey.String("stdoutmetric-example")), - ScopeMetrics: []metricdata.ScopeMetrics{ - { - Scope: instrumentation.Scope{ - Name: "example", - Version: "v0.0.1", - }, - Metrics: []metricdata.Metrics{ - { - Name: "requests", - Description: "Number of requests received", - Unit: unit.Dimensionless, - Data: metricdata.Sum[int64]{ - IsMonotonic: true, - Temporality: metricdata.DeltaTemporality, - DataPoints: []metricdata.DataPoint[int64]{ - { - Attributes: attribute.NewSet(attribute.String("server", "central")), - StartTime: now, - Time: now.Add(1 * time.Second), - Value: 5, + res = resource.NewSchemaless( + semconv.ServiceNameKey.String("stdoutmetric-example"), + ) + + mockData = metricdata.ResourceMetrics{ + Resource: res, + ScopeMetrics: []metricdata.ScopeMetrics{ + { + Scope: instrumentation.Scope{Name: "example", Version: "v0.0.1"}, + Metrics: []metricdata.Metrics{ + { + Name: "requests", + Description: "Number of requests received", + Unit: unit.Dimensionless, + Data: metricdata.Sum[int64]{ + IsMonotonic: true, + Temporality: metricdata.DeltaTemporality, + DataPoints: []metricdata.DataPoint[int64]{ + { + Attributes: attribute.NewSet(attribute.String("server", "central")), + StartTime: now, + Time: now.Add(1 * time.Second), + Value: 5, + }, }, }, }, - }, - { - Name: "latency", - Description: "Time spend processing received requests", - Unit: unit.Milliseconds, - Data: metricdata.Histogram{ - Temporality: metricdata.DeltaTemporality, - DataPoints: []metricdata.HistogramDataPoint{ - { - Attributes: attribute.NewSet(attribute.String("server", "central")), - StartTime: now, - Time: now.Add(1 * time.Second), - Count: 10, - Bounds: []float64{1, 5, 10}, - BucketCounts: []uint64{1, 3, 6, 0}, - Sum: 57, + { + Name: "latency", + Description: "Time spend processing received requests", + Unit: unit.Milliseconds, + Data: metricdata.Histogram{ + Temporality: metricdata.DeltaTemporality, + DataPoints: []metricdata.HistogramDataPoint{ + { + Attributes: attribute.NewSet(attribute.String("server", "central")), + StartTime: now, + Time: now.Add(1 * time.Second), + Count: 10, + Bounds: []float64{1, 5, 10}, + BucketCounts: []uint64{1, 3, 6, 0}, + Sum: 57, + }, }, }, }, - }, - { - Name: "temperature", - Description: "CPU global temperature", - Unit: unit.Unit("cel(1 K)"), - Data: metricdata.Gauge[float64]{ - DataPoints: []metricdata.DataPoint[float64]{ - { - Attributes: attribute.NewSet(attribute.String("server", "central")), - Time: now.Add(1 * time.Second), - Value: 32.4, + { + Name: "temperature", + Description: "CPU global temperature", + Unit: unit.Unit("cel(1 K)"), + Data: metricdata.Gauge[float64]{ + DataPoints: []metricdata.DataPoint[float64]{ + { + Attributes: attribute.NewSet(attribute.String("server", "central")), + Time: now.Add(1 * time.Second), + Value: 32.4, + }, }, }, }, }, }, }, - }, -} + } +) func Example() { // Print with a JSON encoder that indents with two spaces. @@ -107,9 +111,21 @@ func Example() { if err != nil { panic(err) } - // FIXME: The exporter should be registered with an SDK and the output - // should come from that. - exp.Export(context.Background(), mockData) + + // Register the exporter with an SDK via a periodic reader. + sdk := metric.NewMeterProvider( + metric.WithResource(res), + metric.WithReader(metric.NewPeriodicReader(exp)), + ) + + ctx := context.Background() + // This is where the sdk would be used to create a Meter and from that + // instruments that would make measurments of your code. To simulate that + // behavior, call export directly with mocked data. + _ = exp.Export(ctx, mockData) + + // Ensure the periodic reader is cleaned up by shutting down the sdk. + sdk.Shutdown(ctx) // Output: // { From 8e490873c757aa3b41f8c4549d8cd16351c00c0f Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 2 Aug 2022 12:13:08 -0700 Subject: [PATCH 14/20] Add test for Export shutdown err --- exporters/stdout/stdoutmetric/exporter_test.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/exporters/stdout/stdoutmetric/exporter_test.go b/exporters/stdout/stdoutmetric/exporter_test.go index 25b26fb1587..bd88fd4804d 100644 --- a/exporters/stdout/stdoutmetric/exporter_test.go +++ b/exporters/stdout/stdoutmetric/exporter_test.go @@ -81,3 +81,14 @@ func TestExporterHonorsContextErrors(t *testing.T) { } })) } + +func TestShutdownExporterReturnsShutdownErrorOnExport(t *testing.T) { + var ( + data metricdata.ResourceMetrics + ctx = context.Background() + exp, err = stdoutmetric.New() + ) + require.NoError(t, err) + require.NoError(t, exp.Shutdown(ctx)) + assert.EqualError(t, exp.Export(ctx, data), "exporter shutdown") +} From 59602ba51fb9774bc7e45bc6f24f8a321045928c Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 2 Aug 2022 12:30:38 -0700 Subject: [PATCH 15/20] Add a discard encoder for testing --- exporters/stdout/stdoutmetric/exporter_test.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/exporters/stdout/stdoutmetric/exporter_test.go b/exporters/stdout/stdoutmetric/exporter_test.go index bd88fd4804d..4e55637ca40 100644 --- a/exporters/stdout/stdoutmetric/exporter_test.go +++ b/exporters/stdout/stdoutmetric/exporter_test.go @@ -19,6 +19,8 @@ package stdoutmetric_test // import "go.opentelemetry.io/otel/exporters/stdout/s import ( "context" + "encoding/json" + "io" "testing" "time" @@ -29,6 +31,12 @@ import ( "go.opentelemetry.io/otel/sdk/metric/metricdata" ) +func testEncoderOption(t *testing.T) stdoutmetric.Option { + // Discard export output for testing. + enc := json.NewEncoder(io.Discard) + return stdoutmetric.WithEncoder(enc) +} + func testCtxErrHonored(factory func(*testing.T) func(context.Context) error) func(t *testing.T) { return func(t *testing.T) { t.Helper() @@ -61,19 +69,19 @@ func testCtxErrHonored(factory func(*testing.T) func(context.Context) error) fun func TestExporterHonorsContextErrors(t *testing.T) { t.Run("Shutdown", testCtxErrHonored(func(t *testing.T) func(context.Context) error { - exp, err := stdoutmetric.New() + exp, err := stdoutmetric.New(testEncoderOption(t)) require.NoError(t, err) return exp.Shutdown })) t.Run("ForceFlush", testCtxErrHonored(func(t *testing.T) func(context.Context) error { - exp, err := stdoutmetric.New() + exp, err := stdoutmetric.New(testEncoderOption(t)) require.NoError(t, err) return exp.ForceFlush })) t.Run("Export", testCtxErrHonored(func(t *testing.T) func(context.Context) error { - exp, err := stdoutmetric.New() + exp, err := stdoutmetric.New(testEncoderOption(t)) require.NoError(t, err) return func(ctx context.Context) error { var data metricdata.ResourceMetrics @@ -86,7 +94,7 @@ func TestShutdownExporterReturnsShutdownErrorOnExport(t *testing.T) { var ( data metricdata.ResourceMetrics ctx = context.Background() - exp, err = stdoutmetric.New() + exp, err = stdoutmetric.New(testEncoderOption(t)) ) require.NoError(t, err) require.NoError(t, exp.Shutdown(ctx)) From 070f15980a469bf0e93f02b76f9f642e4079255c Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 2 Aug 2022 13:03:35 -0700 Subject: [PATCH 16/20] Acknowledged error is returned from Shutdown --- exporters/stdout/stdoutmetric/example_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exporters/stdout/stdoutmetric/example_test.go b/exporters/stdout/stdoutmetric/example_test.go index d19c3eee9c8..784c4dee42d 100644 --- a/exporters/stdout/stdoutmetric/example_test.go +++ b/exporters/stdout/stdoutmetric/example_test.go @@ -125,7 +125,7 @@ func Example() { _ = exp.Export(ctx, mockData) // Ensure the periodic reader is cleaned up by shutting down the sdk. - sdk.Shutdown(ctx) + _ = sdk.Shutdown(ctx) // Output: // { From e25735a1e03209dceeb3c0604dc136d5445165ef Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 2 Aug 2022 13:04:13 -0700 Subject: [PATCH 17/20] Remove unexpected SchemaURL from stdouttrace test --- exporters/stdout/stdouttrace/trace_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/exporters/stdout/stdouttrace/trace_test.go b/exporters/stdout/stdouttrace/trace_test.go index 649312bf697..8f056460cdb 100644 --- a/exporters/stdout/stdouttrace/trace_test.go +++ b/exporters/stdout/stdouttrace/trace_test.go @@ -185,8 +185,7 @@ func expectedJSON(now time.Time) string { ], "InstrumentationLibrary": { "Name": "", - "Version": "", - "SchemaURL": "" + "Version": "" } } ` From 61fa5ad4ee6bec191e545c6710a5919b418ad388 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 2 Aug 2022 13:09:16 -0700 Subject: [PATCH 18/20] Remove unneeded *testing.T arg from testEncoderOption --- exporters/stdout/stdoutmetric/exporter_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/exporters/stdout/stdoutmetric/exporter_test.go b/exporters/stdout/stdoutmetric/exporter_test.go index 4e55637ca40..9e8faa3e8ec 100644 --- a/exporters/stdout/stdoutmetric/exporter_test.go +++ b/exporters/stdout/stdoutmetric/exporter_test.go @@ -31,7 +31,7 @@ import ( "go.opentelemetry.io/otel/sdk/metric/metricdata" ) -func testEncoderOption(t *testing.T) stdoutmetric.Option { +func testEncoderOption() stdoutmetric.Option { // Discard export output for testing. enc := json.NewEncoder(io.Discard) return stdoutmetric.WithEncoder(enc) @@ -69,19 +69,19 @@ func testCtxErrHonored(factory func(*testing.T) func(context.Context) error) fun func TestExporterHonorsContextErrors(t *testing.T) { t.Run("Shutdown", testCtxErrHonored(func(t *testing.T) func(context.Context) error { - exp, err := stdoutmetric.New(testEncoderOption(t)) + exp, err := stdoutmetric.New(testEncoderOption()) require.NoError(t, err) return exp.Shutdown })) t.Run("ForceFlush", testCtxErrHonored(func(t *testing.T) func(context.Context) error { - exp, err := stdoutmetric.New(testEncoderOption(t)) + exp, err := stdoutmetric.New(testEncoderOption()) require.NoError(t, err) return exp.ForceFlush })) t.Run("Export", testCtxErrHonored(func(t *testing.T) func(context.Context) error { - exp, err := stdoutmetric.New(testEncoderOption(t)) + exp, err := stdoutmetric.New(testEncoderOption()) require.NoError(t, err) return func(ctx context.Context) error { var data metricdata.ResourceMetrics @@ -94,7 +94,7 @@ func TestShutdownExporterReturnsShutdownErrorOnExport(t *testing.T) { var ( data metricdata.ResourceMetrics ctx = context.Background() - exp, err = stdoutmetric.New(testEncoderOption(t)) + exp, err = stdoutmetric.New(testEncoderOption()) ) require.NoError(t, err) require.NoError(t, exp.Shutdown(ctx)) From e2926a093053ac217beb84882219abe6a51ffd23 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 2 Aug 2022 14:27:45 -0700 Subject: [PATCH 19/20] Fix the location of now --- exporters/stdout/stdoutmetric/example_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/exporters/stdout/stdoutmetric/example_test.go b/exporters/stdout/stdoutmetric/example_test.go index 784c4dee42d..8a2528661dd 100644 --- a/exporters/stdout/stdoutmetric/example_test.go +++ b/exporters/stdout/stdoutmetric/example_test.go @@ -35,7 +35,7 @@ import ( var ( // Sat Jan 01 2000 00:00:00 GMT+0000. - now = time.Unix(946684800, 0) + now = time.Date(2000, time.January, 01, 0, 0, 0, 0, time.FixedZone("GMT", 0)) res = resource.NewSchemaless( semconv.ServiceNameKey.String("stdoutmetric-example"), @@ -161,8 +161,8 @@ func Example() { // } // } // ], - // "StartTime": "1999-12-31T16:00:00-08:00", - // "Time": "1999-12-31T16:00:01-08:00", + // "StartTime": "2000-01-01T00:00:00Z", + // "Time": "2000-01-01T00:00:01Z", // "Value": 5 // } // ], @@ -186,8 +186,8 @@ func Example() { // } // } // ], - // "StartTime": "1999-12-31T16:00:00-08:00", - // "Time": "1999-12-31T16:00:01-08:00", + // "StartTime": "2000-01-01T00:00:00Z", + // "Time": "2000-01-01T00:00:01Z", // "Count": 10, // "Bounds": [ // 1, @@ -223,7 +223,7 @@ func Example() { // } // ], // "StartTime": "0001-01-01T00:00:00Z", - // "Time": "1999-12-31T16:00:01-08:00", + // "Time": "2000-01-01T00:00:01Z", // "Value": 32.4 // } // ] From 54a3e2489a6666c3c368b9aebc373594b94425ef Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 2 Aug 2022 14:41:22 -0700 Subject: [PATCH 20/20] Revise and edit docs --- exporters/stdout/stdoutmetric/doc.go | 10 +++++----- exporters/stdout/stdoutmetric/encoder.go | 3 +-- exporters/stdout/stdoutmetric/exporter.go | 4 ++-- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/exporters/stdout/stdoutmetric/doc.go b/exporters/stdout/stdoutmetric/doc.go index eea70f8a6fa..fc766ad0bea 100644 --- a/exporters/stdout/stdoutmetric/doc.go +++ b/exporters/stdout/stdoutmetric/doc.go @@ -15,9 +15,9 @@ // Package stdoutmetric provides an exporter for OpenTelemetry metric // telemetry. // -// The exporter is intended to be used for testing and debugging. It is not an -// exporter meant for production use. Additionally, it does not provide an -// interchange format for OpenTelemetry that is supported with any stability -// or compatibility guarantees. If these are needed features, please use the -// OTLP exporter instead. +// The exporter is intended to be used for testing and debugging, it is not +// meant for production use. Additionally, it does not provide an interchange +// format for OpenTelemetry that is supported with any stability or +// compatibility guarantees. If these are needed features, please use the OTLP +// exporter instead. package stdoutmetric // import "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" diff --git a/exporters/stdout/stdoutmetric/encoder.go b/exporters/stdout/stdoutmetric/encoder.go index b2bc34c9f13..ab5510afcbe 100644 --- a/exporters/stdout/stdoutmetric/encoder.go +++ b/exporters/stdout/stdoutmetric/encoder.go @@ -22,8 +22,7 @@ import "errors" // Encoder encodes and outputs OpenTelemetry metric data-types as human // readable text. type Encoder interface { - // Encode handles the encoding and writing OpenTelemetry metric data-types - // that the exporter will pass to it. + // Encode handles the encoding and writing of OpenTelemetry metric data. Encode(v any) error } diff --git a/exporters/stdout/stdoutmetric/exporter.go b/exporters/stdout/stdoutmetric/exporter.go index c72fef6c39c..f7976d5993f 100644 --- a/exporters/stdout/stdoutmetric/exporter.go +++ b/exporters/stdout/stdoutmetric/exporter.go @@ -33,10 +33,10 @@ type exporter struct { shutdownOnce sync.Once } -// New returns a configured metric Exporter. +// New returns a configured metric exporter. // // If no options are passed, the default exporter returned will use a JSON -// encoder with tab indentations. +// encoder with tab indentations that output to STDOUT. func New(options ...Option) (metric.Exporter, error) { cfg := newConfig(options...) exp := &exporter{}