Skip to content

Commit 4fad287

Browse files
marctcjpkrohling
andauthored
[connector/exceptions] Add support for exemplars in exceptionsconnector (#31819)
**Description:** <Describe what has changed.> Adds support for exemplars fort the generated metrics from exceptions. It relates the span and trace id with metrics. The ingestion needs to be enabled via config (disbled by default). **Link to tracking Issue:** Resolves #24409 **Documentation:** Added documentation for enabling generation of exemplars. --------- Co-authored-by: Juraci Paixão Kröhling <[email protected]>
1 parent dc9818b commit 4fad287

File tree

6 files changed

+106
-19
lines changed

6 files changed

+106
-19
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: enhancement
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
7+
component: exceptionsconnector
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Add support for exemplars in exceptionsconnector
11+
12+
# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
13+
issues: [24409]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext:
19+
20+
# If your change doesn't affect end users or the exported elements of any package,
21+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
22+
# Optional: The change log or logs in which this entry should be included.
23+
# e.g. '[user]' or '[user, api]'
24+
# Include 'user' if the change is relevant to end users.
25+
# Include 'api' if there is a change to a library API.
26+
# Default: '[user]'
27+
change_logs: []

connector/exceptionsconnector/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ The following settings can be optionally configured:
5151

5252
The provided default config includes `exception.type` and `exception.message` as additional dimensions.
5353

54+
- `exemplars`: Use to configure how to attach exemplars to metrics.
55+
- `enabled` (default: `false`): enabling will add spans as Exemplars.
56+
5457
## Examples
5558

5659
The following is a simple example usage of the `exceptions` connector.

connector/exceptionsconnector/config.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ type Dimension struct {
1515
Default *string `mapstructure:"default"`
1616
}
1717

18+
type Exemplars struct {
19+
Enabled bool `mapstructure:"enabled"`
20+
}
21+
1822
// Config defines the configuration options for exceptionsconnector
1923
type Config struct {
2024
// Dimensions defines the list of additional dimensions on top of the provided:
@@ -25,6 +29,8 @@ type Config struct {
2529
// The dimensions will be fetched from the span's attributes. Examples of some conventionally used attributes:
2630
// https://github.com/open-telemetry/opentelemetry-collector/blob/main/model/semconv/opentelemetry.go.
2731
Dimensions []Dimension `mapstructure:"dimensions"`
32+
// Exemplars defines the configuration for exemplars.
33+
Exemplars Exemplars `mapstructure:"exemplars"`
2834
}
2935

3036
var _ component.ConfigValidator = (*Config)(nil)

connector/exceptionsconnector/config_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ func TestLoadConfig(t *testing.T) {
3636
{Name: exceptionTypeKey},
3737
{Name: exceptionMessageKey},
3838
},
39+
Exemplars: Exemplars{
40+
Enabled: false,
41+
},
3942
},
4043
},
4144
}

connector/exceptionsconnector/connector_metrics.go

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,18 @@ type metricsConnector struct {
3737
component.StartFunc
3838
component.ShutdownFunc
3939

40-
exceptions map[string]*excVal
40+
exceptions map[string]*exception
4141

4242
logger *zap.Logger
4343

4444
// The starting time of the data points.
4545
startTimestamp pcommon.Timestamp
4646
}
4747

48-
type excVal struct {
49-
count int
50-
attrs pcommon.Map
48+
type exception struct {
49+
count int
50+
attrs pcommon.Map
51+
exemplars pmetric.ExemplarSlice
5152
}
5253

5354
func newMetricsConnector(logger *zap.Logger, config component.Config) *metricsConnector {
@@ -59,7 +60,7 @@ func newMetricsConnector(logger *zap.Logger, config component.Config) *metricsCo
5960
dimensions: newDimensions(cfg.Dimensions),
6061
keyBuf: bytes.NewBuffer(make([]byte, 0, 1024)),
6162
startTimestamp: pcommon.NewTimestampFromTime(time.Now()),
62-
exceptions: make(map[string]*excVal),
63+
exceptions: make(map[string]*exception),
6364
}
6465
}
6566

@@ -95,7 +96,8 @@ func (c *metricsConnector) ConsumeTraces(ctx context.Context, traces ptrace.Trac
9596
key := c.keyBuf.String()
9697

9798
attrs := buildDimensionKVs(c.dimensions, serviceName, span, eventAttrs)
98-
c.addException(key, attrs)
99+
exc := c.addException(key, attrs)
100+
c.addExemplar(exc, span.TraceID(), span.SpanID())
99101
}
100102
}
101103
}
@@ -132,28 +134,45 @@ func (c *metricsConnector) collectExceptions(ilm pmetric.ScopeMetrics) error {
132134
dps := mCalls.Sum().DataPoints()
133135
dps.EnsureCapacity(len(c.exceptions))
134136
timestamp := pcommon.NewTimestampFromTime(time.Now())
135-
for _, val := range c.exceptions {
136-
dpCalls := dps.AppendEmpty()
137-
dpCalls.SetStartTimestamp(c.startTimestamp)
138-
dpCalls.SetTimestamp(timestamp)
139-
140-
dpCalls.SetIntValue(int64(val.count))
141-
142-
val.attrs.CopyTo(dpCalls.Attributes())
137+
for _, exc := range c.exceptions {
138+
dp := dps.AppendEmpty()
139+
dp.SetStartTimestamp(c.startTimestamp)
140+
dp.SetTimestamp(timestamp)
141+
dp.SetIntValue(int64(exc.count))
142+
for i := 0; i < exc.exemplars.Len(); i++ {
143+
exc.exemplars.At(i).SetTimestamp(timestamp)
144+
}
145+
dp.Exemplars().EnsureCapacity(exc.exemplars.Len())
146+
exc.exemplars.CopyTo(dp.Exemplars())
147+
exc.attrs.CopyTo(dp.Attributes())
148+
// Reset the exemplars for the next batch of spans.
149+
exc.exemplars = pmetric.NewExemplarSlice()
143150
}
144151
return nil
145152
}
146153

147-
func (c *metricsConnector) addException(excKey string, attrs pcommon.Map) {
154+
func (c *metricsConnector) addException(excKey string, attrs pcommon.Map) *exception {
148155
exc, ok := c.exceptions[excKey]
149156
if !ok {
150-
c.exceptions[excKey] = &excVal{
151-
count: 1,
152-
attrs: attrs,
157+
c.exceptions[excKey] = &exception{
158+
count: 1,
159+
attrs: attrs,
160+
exemplars: pmetric.NewExemplarSlice(),
153161
}
154-
return
162+
return c.exceptions[excKey]
155163
}
156164
exc.count++
165+
return exc
166+
}
167+
168+
func (c *metricsConnector) addExemplar(exc *exception, traceID pcommon.TraceID, spanID pcommon.SpanID) {
169+
if !c.config.Exemplars.Enabled || traceID.IsEmpty() {
170+
return
171+
}
172+
e := exc.exemplars.AppendEmpty()
173+
e.SetTraceID(traceID)
174+
e.SetSpanID(spanID)
175+
e.SetDoubleValue(float64(exc.count))
157176
}
158177

159178
func buildDimensionKVs(dimensions []dimension, serviceName string, span ptrace.Span, eventAttrs pcommon.Map) pcommon.Map {

connector/exceptionsconnector/connector_metrics_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,25 @@ func TestConnectorConsumeTraces(t *testing.T) {
8888
}
8989
})
9090
}
91+
t.Run("Test without exemplars", func(t *testing.T) {
92+
msink := &consumertest.MetricsSink{}
93+
94+
p := newTestMetricsConnector(msink, stringp("defaultNullValue"), zaptest.NewLogger(t))
95+
p.config.Exemplars.Enabled = false
96+
97+
ctx := metadata.NewIncomingContext(context.Background(), nil)
98+
err := p.Start(ctx, componenttest.NewNopHost())
99+
defer func() { sdErr := p.Shutdown(ctx); require.NoError(t, sdErr) }()
100+
require.NoError(t, err)
101+
102+
err = p.ConsumeTraces(ctx, buildBadSampleTrace())
103+
assert.NoError(t, err)
104+
105+
metrics := msink.AllMetrics()
106+
assert.Greater(t, len(metrics), 0)
107+
verifyBadMetricsOkay(t, metrics[len(metrics)-1])
108+
})
109+
91110
}
92111

93112
func BenchmarkConnectorConsumeTraces(b *testing.B) {
@@ -123,6 +142,9 @@ func newTestMetricsConnector(mcon consumer.Metrics, defaultNullValue *string, lo
123142
{exceptionTypeKey, nil},
124143
{exceptionMessageKey, nil},
125144
},
145+
Exemplars: Exemplars{
146+
Enabled: true,
147+
},
126148
}
127149
c := newMetricsConnector(logger, cfg)
128150
c.metricsConsumer = mcon
@@ -175,6 +197,13 @@ func verifyConsumeMetricsInput(t testing.TB, input pmetric.Metrics, numCumulativ
175197
assert.NotZero(t, dp.StartTimestamp(), "StartTimestamp should be set")
176198
assert.NotZero(t, dp.Timestamp(), "Timestamp should be set")
177199
verifyMetricLabels(dp, t, seenMetricIDs)
200+
201+
assert.Equal(t, 1, dp.Exemplars().Len())
202+
exemplar := dp.Exemplars().At(0)
203+
assert.NotZero(t, exemplar.Timestamp())
204+
assert.NotZero(t, exemplar.TraceID())
205+
assert.NotZero(t, exemplar.SpanID())
206+
178207
}
179208
return true
180209
}

0 commit comments

Comments
 (0)