Skip to content
This repository was archived by the owner on Nov 7, 2022. It is now read-only.

Commit 51944b6

Browse files
author
Bogdan Drutu
authored
Add exporterhelper to wrap all exporters with observability. (#484)
* Add exporterhelper to wrap all exporters with observability. * Remove the nop view exporter, no needed. * Move constants in constants.go, add TODO to handle gRPC errors. * Don't export the errors exporterhelper returns.
1 parent a19848f commit 51944b6

File tree

16 files changed

+773
-191
lines changed

16 files changed

+773
-191
lines changed

cmd/occollector/app/collector/processors.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,8 @@ func startProcessor(v *viper.Viper, logger *zap.Logger) (processor.SpanProcessor
248248
_ = metricsExporters
249249

250250
if builder.LoggingExporterEnabled(v) {
251-
dbgProc := processor.NewTraceExporterProcessor(loggingexporter.NewTraceExporter(logger))
251+
tle, _ := loggingexporter.NewTraceExporter(logger)
252+
dbgProc := processor.NewTraceExporterProcessor(tle)
252253
// TODO: Add this to the exporters list and avoid treating it specially. Don't know all the implications.
253254
nameToSpanProcessor["debug"] = dbgProc
254255
spanProcessors = append(spanProcessors, dbgProc)

exporter/exporterhelper/common.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright 2019, OpenCensus Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package exporterhelper
16+
17+
import (
18+
"go.opencensus.io/trace"
19+
)
20+
21+
var (
22+
okStatus = trace.Status{Code: trace.StatusCodeOK}
23+
)
24+
25+
// ExporterOptions contains options concerning how an Exporter is configured.
26+
type ExporterOptions struct {
27+
// TOOD: Retry logic must be in the same place as metrics recording because
28+
// if a request is retried we should not record metrics otherwise number of
29+
// spans received + dropped will be different than the number of received spans
30+
// in the receiver.
31+
recordMetrics bool
32+
spanName string
33+
}
34+
35+
// ExporterOption apply changes to ExporterOptions.
36+
type ExporterOption func(*ExporterOptions)
37+
38+
// WithRecordMetrics makes new Exporter to record metrics for every request.
39+
func WithRecordMetrics(recordMetrics bool) ExporterOption {
40+
return func(o *ExporterOptions) {
41+
o.recordMetrics = recordMetrics
42+
}
43+
}
44+
45+
// WithSpanName makes new Exporter to wrap every request with a Span.
46+
func WithSpanName(spanName string) ExporterOption {
47+
return func(o *ExporterOptions) {
48+
o.spanName = spanName
49+
}
50+
}
51+
52+
// Construct the ExporterOptions from multiple ExporterOption.
53+
func newExporterOptions(options ...ExporterOption) ExporterOptions {
54+
var opts ExporterOptions
55+
for _, op := range options {
56+
op(&opts)
57+
}
58+
return opts
59+
}
60+
61+
func errToStatus(err error) trace.Status {
62+
if err != nil {
63+
return trace.Status{Code: trace.StatusCodeUnknown, Message: err.Error()}
64+
}
65+
return okStatus
66+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright 2019, OpenCensus Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
package exporterhelper
15+
16+
import (
17+
"errors"
18+
"testing"
19+
20+
"go.opencensus.io/trace"
21+
)
22+
23+
func TestDefaultOptions(t *testing.T) {
24+
checkRecordMetrics(t, newExporterOptions(), false)
25+
checkSpanName(t, newExporterOptions(), "")
26+
}
27+
28+
func TestWithRecordMetrics(t *testing.T) {
29+
checkRecordMetrics(t, newExporterOptions(WithRecordMetrics(true)), true)
30+
checkRecordMetrics(t, newExporterOptions(WithRecordMetrics(false)), false)
31+
}
32+
33+
func TestWithSpanName(t *testing.T) {
34+
checkSpanName(t, newExporterOptions(WithSpanName("my_span")), "my_span")
35+
checkSpanName(t, newExporterOptions(WithSpanName("")), "")
36+
}
37+
38+
func TestErrorToStatus(t *testing.T) {
39+
if g, w := errToStatus(nil), okStatus; g != w {
40+
t.Fatalf("Status: Want %v Got %v", w, g)
41+
}
42+
43+
errorStatus := trace.Status{Code: trace.StatusCodeUnknown, Message: "my_error"}
44+
if g, w := errToStatus(errors.New("my_error")), errorStatus; g != w {
45+
t.Fatalf("Status: Want %v Got %v", w, g)
46+
}
47+
}
48+
49+
func checkRecordMetrics(t *testing.T, opts ExporterOptions, recordMetrics bool) {
50+
if opts.recordMetrics != recordMetrics {
51+
t.Errorf("Wrong recordMetrics Want: %t Got: %t", opts.recordMetrics, recordMetrics)
52+
return
53+
}
54+
}
55+
56+
func checkSpanName(t *testing.T, opts ExporterOptions, spanName string) {
57+
if opts.spanName != spanName {
58+
t.Errorf("Wrong spanName Want: %s Got: %s", opts.spanName, spanName)
59+
return
60+
}
61+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright 2019, OpenCensus Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package exporterhelper
16+
17+
import (
18+
"errors"
19+
)
20+
21+
var (
22+
// errEmptyExporterFormat is returned when an empty name is given.
23+
errEmptyExporterFormat = errors.New("empty exporter format")
24+
// errNilPushTraceData is returned when a nil pushTraceData is given.
25+
errNilPushTraceData = errors.New("nil pushTraceData")
26+
// errNilPushMetricsData is returned when a nil pushMetricsData is given.
27+
errNilPushMetricsData = errors.New("nil pushMetricsData")
28+
)
29+
30+
const (
31+
numDroppedMetricsAttribute = "num_dropped_metrics"
32+
numReceivedMetricsAttribute = "num_received_metrics"
33+
numDroppedSpansAttribute = "num_dropped_spans"
34+
numReceivedSpansAttribute = "num_received_spans"
35+
)
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Copyright 2019, OpenCensus Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package exporterhelper
16+
17+
import (
18+
"context"
19+
20+
"github.com/census-instrumentation/opencensus-service/observability"
21+
"go.opencensus.io/trace"
22+
23+
"github.com/census-instrumentation/opencensus-service/data"
24+
"github.com/census-instrumentation/opencensus-service/exporter"
25+
)
26+
27+
// PushMetricsData is a helper function that is similar to ConsumeMetricsData but also returns
28+
// the number of dropped metrics.
29+
type PushMetricsData func(ctx context.Context, td data.MetricsData) (droppedMetrics int, err error)
30+
31+
type metricsExporter struct {
32+
exporterFormat string
33+
pushMetricsData PushMetricsData
34+
}
35+
36+
var _ (exporter.MetricsExporter) = (*metricsExporter)(nil)
37+
38+
func (me *metricsExporter) MetricsExportFormat() string {
39+
return me.exporterFormat
40+
}
41+
42+
func (me *metricsExporter) ConsumeMetricsData(ctx context.Context, md data.MetricsData) error {
43+
exporterCtx := observability.ContextWithExporterName(ctx, me.exporterFormat)
44+
_, err := me.pushMetricsData(exporterCtx, md)
45+
return err
46+
}
47+
48+
// NewMetricsExporter creates an MetricsExporter that can record metrics and can wrap every request with a Span.
49+
// If no options are passed it just adds the exporter format as a tag in the Context.
50+
// TODO: Add support for recordMetrics.
51+
// TODO: Add support for retries.
52+
func NewMetricsExporter(exporterFormat string, pushMetricsData PushMetricsData, options ...ExporterOption) (exporter.MetricsExporter, error) {
53+
if exporterFormat == "" {
54+
return nil, errEmptyExporterFormat
55+
}
56+
57+
if pushMetricsData == nil {
58+
return nil, errNilPushMetricsData
59+
}
60+
61+
opts := newExporterOptions(options...)
62+
63+
if opts.spanName != "" {
64+
pushMetricsData = pushMetricsDataWithSpan(pushMetricsData, opts.spanName)
65+
}
66+
67+
return &metricsExporter{
68+
exporterFormat: exporterFormat,
69+
pushMetricsData: pushMetricsData,
70+
}, nil
71+
}
72+
73+
func pushMetricsDataWithSpan(next PushMetricsData, spanName string) PushMetricsData {
74+
return func(ctx context.Context, md data.MetricsData) (int, error) {
75+
ctx, span := trace.StartSpan(ctx, spanName)
76+
defer span.End()
77+
// Call next stage.
78+
droppedMetrics, err := next(ctx, md)
79+
if span.IsRecordingEvents() {
80+
span.AddAttributes(
81+
trace.Int64Attribute(numReceivedMetricsAttribute, int64(len(md.Metrics))),
82+
trace.Int64Attribute(numDroppedMetricsAttribute, int64(droppedMetrics)),
83+
)
84+
if err != nil {
85+
span.SetStatus(errToStatus(err))
86+
}
87+
}
88+
return droppedMetrics, err
89+
}
90+
}

0 commit comments

Comments
 (0)