Skip to content

[spanmetricsconnector] Add instrumentation scope #38001

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .chloggen/23662.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Use this changelog template to create an entry for release notes.

# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: enhancement

# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
component: spanmetricsconnector

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Add instrumentation scope to span metrics connector.

# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
issues: [23662]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext: |
This change adds the instrumentation scope to the span metrics connector, which allows users to specify the instrumentation scope for the connector.
Now, the connector has a new configuration option:
- `include_instrumentation_scope`: A list of instrumentation scope names to include from the traces.

The instrumentation scope name is the name of the instrumentation library that collected the span.

# If your change doesn't affect end users or the exported elements of any package,
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
# Optional: The change log or logs in which this entry should be included.
# e.g. '[user]' or '[user, api]'
# Include 'user' if the change is relevant to end users.
# Include 'api' if there is a change to a library API.
# Default: '[user]'
change_logs: []
5 changes: 4 additions & 1 deletion connector/spanmetricsconnector/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ The following settings can be optionally configured:

If no `default` is provided, this dimension will be **omitted** from the metric.
- `exclude_dimensions`: the list of dimensions to be excluded from the default set of dimensions. Use to exclude unneeded data from metrics.
- `dimensions_cache_size` (default: `1000`): the size of cache for storing Dimensions to improve collectors memory usage. Must be a positive number.
- `dimensions_cache_size` (default: `1000`): the size of cache for storing Dimensions to improve collectors memory usage. Must be a positive number.
- `include_instrumentation_scope`: a list of instrumentation scope names to include from the traces.
- `resource_metrics_cache_size` (default: `1000`): the size of the cache holding metrics for a service. This is mostly relevant for
cumulative temporality to avoid memory leaks and correct metric timestamp resets.
- `aggregation_temporality` (default: `AGGREGATION_TEMPORALITY_CUMULATIVE`): Defines the aggregation temporality of the generated metrics.
Expand Down Expand Up @@ -164,6 +165,8 @@ connectors:
- service.name
- telemetry.sdk.language
- telemetry.sdk.name
include_instrumentation_scope:
- express

service:
pipelines:
Expand Down
2 changes: 2 additions & 0 deletions connector/spanmetricsconnector/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ type Config struct {

// Events defines the configuration for events section of spans.
Events EventsConfig `mapstructure:"events"`

IncludeInstrumentationScope []string `mapstructure:"include_instrumentation_scope"`
}

type HistogramConfig struct {
Expand Down
27 changes: 19 additions & 8 deletions connector/spanmetricsconnector/connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@ import (
)

const (
serviceNameKey = conventions.AttributeServiceName
spanNameKey = "span.name" // OpenTelemetry non-standard constant.
spanKindKey = "span.kind" // OpenTelemetry non-standard constant.
statusCodeKey = "status.code" // OpenTelemetry non-standard constant.
metricKeySeparator = string(byte(0))
serviceNameKey = conventions.AttributeServiceName
spanNameKey = "span.name" // OpenTelemetry non-standard constant.
spanKindKey = "span.kind" // OpenTelemetry non-standard constant.
statusCodeKey = "status.code" // OpenTelemetry non-standard constant.
instrumentationScopeNameKey = "span.instrumentation.scope.name" // OpenTelemetry non-standard constant.
instrumentationScopeVersionKey = "span.instrumentation.scope.version" // OpenTelemetry non-standard constant.
metricKeySeparator = string(byte(0))

defaultDimensionsCacheSize = 1000
defaultResourceMetricsCacheSize = 1000
Expand Down Expand Up @@ -401,7 +403,7 @@ func (p *connectorImp) aggregateMetrics(traces ptrace.Traces) {

attributes, ok := p.metricKeyToDimensions.Get(key)
if !ok {
attributes = p.buildAttributes(serviceName, span, resourceAttr, p.dimensions)
attributes = p.buildAttributes(serviceName, span, resourceAttr, p.dimensions, ils.Scope())
p.metricKeyToDimensions.Add(key, attributes)
}
if !p.config.Histogram.Disable {
Expand Down Expand Up @@ -432,7 +434,7 @@ func (p *connectorImp) aggregateMetrics(traces ptrace.Traces) {
eKey := p.buildKey(serviceName, span, eDimensions, rscAndEventAttrs)
eAttributes, ok := p.metricKeyToDimensions.Get(eKey)
if !ok {
eAttributes = p.buildAttributes(serviceName, span, rscAndEventAttrs, eDimensions)
eAttributes = p.buildAttributes(serviceName, span, rscAndEventAttrs, eDimensions, ils.Scope())
p.metricKeyToDimensions.Add(eKey, eAttributes)
}
e := events.GetOrCreate(eKey, eAttributes)
Expand Down Expand Up @@ -505,7 +507,7 @@ func contains(elements []string, value string) bool {
return false
}

func (p *connectorImp) buildAttributes(serviceName string, span ptrace.Span, resourceAttrs pcommon.Map, dimensions []utilattri.Dimension) pcommon.Map {
func (p *connectorImp) buildAttributes(serviceName string, span ptrace.Span, resourceAttrs pcommon.Map, dimensions []utilattri.Dimension, instrumentationScope pcommon.InstrumentationScope) pcommon.Map {
attr := pcommon.NewMap()
attr.EnsureCapacity(4 + len(dimensions))
if !contains(p.config.ExcludeDimensions, serviceNameKey) {
Expand All @@ -520,11 +522,20 @@ func (p *connectorImp) buildAttributes(serviceName string, span ptrace.Span, res
if !contains(p.config.ExcludeDimensions, statusCodeKey) {
attr.PutStr(statusCodeKey, traceutil.StatusCodeStr(span.Status().Code()))
}

for _, d := range dimensions {
if v, ok := utilattri.GetDimensionValue(d, span.Attributes(), resourceAttrs); ok {
v.CopyTo(attr.PutEmpty(d.Name))
}
}

if contains(p.config.IncludeInstrumentationScope, instrumentationScope.Name()) && instrumentationScope.Name() != "" {
attr.PutStr(instrumentationScopeNameKey, instrumentationScope.Name())
if instrumentationScope.Version() != "" {
attr.PutStr(instrumentationScopeVersionKey, instrumentationScope.Version())
}
}

return attr
}

Expand Down
107 changes: 107 additions & 0 deletions connector/spanmetricsconnector/connector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1874,3 +1874,110 @@ func (c alwaysIncreasingClock) Now() time.Time {
c.Clock.(*clockwork.FakeClock).Advance(time.Millisecond)
return c.Clock.Now()
}

func TestBuildAttributes_InstrumentationScope(t *testing.T) {
tests := []struct {
name string
instrumentationScope pcommon.InstrumentationScope
config Config
want map[string]string
}{
{
name: "with instrumentation scope name and version",
instrumentationScope: func() pcommon.InstrumentationScope {
scope := pcommon.NewInstrumentationScope()
scope.SetName("express")
scope.SetVersion("1.0.0")
return scope
}(),
config: Config{
IncludeInstrumentationScope: []string{"express"},
},
want: map[string]string{
serviceNameKey: "test_service",
spanNameKey: "test_span",
spanKindKey: "SPAN_KIND_INTERNAL",
statusCodeKey: "STATUS_CODE_UNSET",
instrumentationScopeNameKey: "express",
instrumentationScopeVersionKey: "1.0.0",
},
},
{
name: "with instrumentation scope but not included",
instrumentationScope: func() pcommon.InstrumentationScope {
scope := pcommon.NewInstrumentationScope()
scope.SetName("express")
scope.SetVersion("1.0.0")
return scope
}(),
config: Config{},
want: map[string]string{
serviceNameKey: "test_service",
spanNameKey: "test_span",
spanKindKey: "SPAN_KIND_INTERNAL",
statusCodeKey: "STATUS_CODE_UNSET",
},
},
{
name: "without instrumentation scope but version and included in config",
instrumentationScope: func() pcommon.InstrumentationScope {
scope := pcommon.NewInstrumentationScope()
scope.SetVersion("1.0.0")
return scope
}(),
config: Config{
IncludeInstrumentationScope: []string{"express"},
},
want: map[string]string{
serviceNameKey: "test_service",
spanNameKey: "test_span",
spanKindKey: "SPAN_KIND_INTERNAL",
statusCodeKey: "STATUS_CODE_UNSET",
},
},

{
name: "with instrumentation scope and instrumentation scope name but no version and included in config",
instrumentationScope: func() pcommon.InstrumentationScope {
scope := pcommon.NewInstrumentationScope()
scope.SetName("express")
return scope
}(),
config: Config{
IncludeInstrumentationScope: []string{"express"},
},
want: map[string]string{
serviceNameKey: "test_service",
spanNameKey: "test_span",
spanKindKey: "SPAN_KIND_INTERNAL",
statusCodeKey: "STATUS_CODE_UNSET",
instrumentationScopeNameKey: "express",
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create connector
p := &connectorImp{
config: tt.config,
}

// Create basic span
span := ptrace.NewSpan()
span.SetName("test_span")
span.SetKind(ptrace.SpanKindInternal)

// Build attributes
attrs := p.buildAttributes("test_service", span, pcommon.NewMap(), nil, tt.instrumentationScope)

// Verify results
assert.Equal(t, len(tt.want), attrs.Len())
for k, v := range tt.want {
val, ok := attrs.Get(k)
assert.True(t, ok)
assert.Equal(t, v, val.Str())
}
})
}
}