Skip to content

Commit c5df0fe

Browse files
authored
Merge pull request #1340 from circleci/dm/bpg-shim
Make it easier to use a custom provider
2 parents 50d7c4f + f875e35 commit c5df0fe

File tree

4 files changed

+51
-25
lines changed

4 files changed

+51
-25
lines changed

config/o11y/otel.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ type OtelConfig struct {
7575

7676
// SpanExporters allows you explicitly provide a set of exporters, as an advanced use-case.
7777
SpanExporters []sdktrace.SpanExporter
78+
79+
// ProviderFunc is used to provide a custom provider.
80+
ProviderFunc func(conf otel.Config) (o11y.Provider, error)
7881
}
7982

8083
// Otel is the primary entrypoint to initialize the o11y system for otel.
@@ -90,7 +93,10 @@ func Otel(ctx context.Context, o OtelConfig) (context.Context, func(context.Cont
9093
}
9194
cfg.Metrics = mProv
9295

93-
o11yProvider, err := otel.New(cfg)
96+
if o.ProviderFunc == nil {
97+
o.ProviderFunc = otel.New
98+
}
99+
o11yProvider, err := o.ProviderFunc(cfg)
94100
if err != nil {
95101
return ctx, nil, err
96102
}

o11y/otel/custom.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ import (
99

1010
var globalFields = Annotator{}
1111

12+
// GlobalFieldsAnnotator exposes the global fields annotator. When used in conjunction with a Provider, such as the
13+
// MetricsOnly Provider a custom provider can implement the global fields behaviour.
14+
func GlobalFieldsAnnotator() *Annotator {
15+
return &globalFields
16+
}
17+
18+
var _ sdktrace.SpanProcessor = &Annotator{}
19+
1220
// Annotator is a SpanProcessor that adds attributes to all started spans.
1321
type Annotator struct {
1422
attrs []attribute.KeyValue

o11y/otel/otel.go

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
sdktrace "go.opentelemetry.io/otel/sdk/trace"
2323
semconv "go.opentelemetry.io/otel/semconv/v1.12.0"
2424
"go.opentelemetry.io/otel/trace"
25+
"go.opentelemetry.io/otel/trace/noop"
2526

2627
"github.com/circleci/ex/config/secret"
2728
"github.com/circleci/ex/o11y"
@@ -82,11 +83,11 @@ func New(conf Config) (o11y.Provider, error) {
8283
exporters = append(exporters, http)
8384
}
8485

85-
var sampler *deterministicSampler
86+
var sampler *DeterministicSampler
8687
if conf.SampleTraces {
87-
sampler = &deterministicSampler{
88-
sampleKeyFunc: conf.SampleKeyFunc,
89-
sampleRates: conf.SampleRates,
88+
sampler = &DeterministicSampler{
89+
SampleKeyFunc: conf.SampleKeyFunc,
90+
SampleRates: conf.SampleRates,
9091
}
9192
}
9293

@@ -104,9 +105,9 @@ func New(conf Config) (o11y.Provider, error) {
104105
exporters = append(exporters, text)
105106
}
106107

107-
tp := traceProvider(multipleExporter{
108-
exporters: exporters,
109-
sampler: sampler,
108+
tp := traceProvider(MultipleExporter{
109+
Exporters: exporters,
110+
Sampler: sampler,
110111
}, conf)
111112

112113
// set the global options
@@ -121,6 +122,15 @@ func New(conf Config) (o11y.Provider, error) {
121122
}, nil
122123
}
123124

125+
// NewMetricsOnly returns a metrics only provider, to capture the span metrics behavior.
126+
// This can be used to compose with a custom provider to access span based metrics.
127+
func NewMetricsOnly(metrics o11y.ClosableMetricsProvider) o11y.Provider {
128+
return &Provider{
129+
metricsProvider: metrics,
130+
tracer: noop.NewTracerProvider().Tracer(""),
131+
}
132+
}
133+
124134
func NewHttpExporter(conf Config) (*otlptrace.Exporter, error) {
125135
var serviceName, serviceVersion string
126136
for _, a := range conf.ResourceAttributes {
@@ -326,7 +336,9 @@ func (o Provider) Log(ctx context.Context, name string, fields ...o11y.Pair) {
326336

327337
func (o Provider) Close(ctx context.Context) {
328338
// TODO Handle these errors in a sensible manner where possible
329-
_ = o.tp.Shutdown(ctx)
339+
if o.tp != nil {
340+
_ = o.tp.Shutdown(ctx)
341+
}
330342
if o.metricsProvider != nil {
331343
_ = o.metricsProvider.Close()
332344
}
@@ -543,37 +555,37 @@ func (s *span) snapshotFields() map[string]any {
543555
return res
544556
}
545557

546-
type multipleExporter struct {
547-
exporters []sdktrace.SpanExporter
548-
sampler *deterministicSampler
558+
type MultipleExporter struct {
559+
Exporters []sdktrace.SpanExporter
560+
Sampler *DeterministicSampler
549561
}
550562

551-
func (m multipleExporter) ExportSpans(ctx context.Context, spans []sdktrace.ReadOnlySpan) error {
563+
func (m MultipleExporter) ExportSpans(ctx context.Context, spans []sdktrace.ReadOnlySpan) error {
552564
spans = m.sampleSpans(spans)
553-
for _, e := range m.exporters {
565+
for _, e := range m.Exporters {
554566
if err := e.ExportSpans(ctx, spans); err != nil {
555567
return err
556568
}
557569
}
558570
return nil
559571
}
560572

561-
func (m multipleExporter) Shutdown(ctx context.Context) error {
562-
for _, e := range m.exporters {
573+
func (m MultipleExporter) Shutdown(ctx context.Context) error {
574+
for _, e := range m.Exporters {
563575
if err := e.Shutdown(ctx); err != nil {
564576
return err
565577
}
566578
}
567579
return nil
568580
}
569581

570-
func (m multipleExporter) sampleSpans(spans []sdktrace.ReadOnlySpan) []sdktrace.ReadOnlySpan {
571-
if m.sampler == nil {
582+
func (m MultipleExporter) sampleSpans(spans []sdktrace.ReadOnlySpan) []sdktrace.ReadOnlySpan {
583+
if m.Sampler == nil {
572584
return spans
573585
}
574586
ss := make([]sdktrace.ReadOnlySpan, 0, len(spans))
575587
for _, s := range spans {
576-
if ok, rate := m.sampler.shouldSample(s); ok {
588+
if ok, rate := m.Sampler.shouldSample(s); ok {
577589
ss = append(ss, sampleRateSpan{ReadOnlySpan: s, rate: rate})
578590
}
579591
}

o11y/otel/sampler.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ import (
77
sdktrace "go.opentelemetry.io/otel/sdk/trace"
88
)
99

10-
type deterministicSampler struct {
11-
sampleKeyFunc func(map[string]any) string
12-
sampleRates map[string]uint
10+
type DeterministicSampler struct {
11+
SampleKeyFunc func(map[string]any) string
12+
SampleRates map[string]uint
1313
}
1414

1515
// shouldSample means should sample in, returning true if the span should be sampled in (kept)
16-
func (s deterministicSampler) shouldSample(p sdktrace.ReadOnlySpan) (bool, uint) {
16+
func (s DeterministicSampler) shouldSample(p sdktrace.ReadOnlySpan) (bool, uint) {
1717
fields := map[string]any{}
1818
for _, attr := range p.Attributes() {
1919
fields[string(attr.Key)] = attr.Value.AsInterface()
@@ -30,8 +30,8 @@ func (s deterministicSampler) shouldSample(p sdktrace.ReadOnlySpan) (bool, uint)
3030
fields["duration_ms"] = int(p.EndTime().Sub(p.StartTime()).Milliseconds())
3131
fields["name"] = p.Name()
3232

33-
key := s.sampleKeyFunc(fields)
34-
rate, ok := s.sampleRates[key] // no rate found means keep
33+
key := s.SampleKeyFunc(fields)
34+
rate, ok := s.SampleRates[key] // no rate found means keep
3535
if !ok {
3636
return true, 1 // and is a sample rate of 1/1
3737
}

0 commit comments

Comments
 (0)