Repository: open-telemetry/opentelemetry-collector-contrib
Pull request #38001: [spanmetricsconnector] Add instrumentation scope
Author: login: iblancasa
Labels: name: connector/spanmetrics
Created: 2025-02-18T10:48:50Z | Merged: 2025-03-27T20:57:30Z

#### Description
Added the instrumentation scope to the metrics generated by the span metrics connector.

#### Link to tracking issue
Fixes #23662

#### Testing
Added a new unit test to cover this feature.

#### Documentation
Some notes to the `README.md` file explaining the new config files.

Linked issue #23662: Add option to add instrumentation scope name as a dimension
### Component(s)

connector/spanmetrics

### Is your feature request related to a problem? Please describe.

In my current project, I need the instrumentation scope name as a dimension in the exported span metrics.

### Describe the solution you'd like

Ideally, a new option `instrumentation_scope` that allows you to configure which part of the scope should be included as a dimension:

```yaml
spanmetrics:
  instrumentation_scope:
    name: true
    version: false
```

Additionally, `dimensions` should also look at the instrumentation scope attributes.

### Describe alternatives you've considered

Alternative, I'd have to add the name of the instrumentation scope to the span's attributes before it's send to the collector and then use the `dimensions` option. This isn't ideal, since every span would now have an additional tag.

### Additional context

_No response_
Linked issue comments: user: login: github-actions[bot]
created_at: 2023-06-24T14:59:22Z
body: Pinging code owners:
- connector/spanmetrics: @albertteoh @kovrus

See [Adding Labels via Comments](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#adding-labels-via-comments) if you do not have permissions to add labels yourself.
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2023-08-24T03:29:12Z
body: This issue has been inactive for 60 days. It will be closed in 60 days if there is no activity. To ping code owners by adding a component label, see [Adding Labels via Comments](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#adding-labels-via-comments), or if you are unsure of which component this issue relates to, please ping `@open-telemetry/collector-contrib-triagers`. If this issue is still relevant, please ping the code owners or leave a comment explaining why it is still relevant. Otherwise, please close it.

Pinging code owners:
- needs: Github issue template generation code needs this to generate the corresponding labels.
- connector/spanmetrics: @albertteoh

See [Adding Labels via Comments](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#adding-labels-via-comments) if you do not have permissions to add labels yourself.
author_association: CONTRIBUTOR
user: login: erri120
created_at: 2023-08-25T09:11:44Z
body: Still relevant, nothing has changed.
author_association: NONE
user: login: crobert-1
created_at: 2023-10-24T15:12:36Z
body: This sounds like a valid request to me, we'd essentially just be copying instrumentation scope from the incoming traces to the scope of the outgoing metrics.

The configuration may need another look to match the style of existing options. It's usually something like `include_` and `exclude_`. The current config also already has `dimensions` and `exclude_dimensions` so the difference would have to be clearly marked.

Are you interested in adding this enhancement?
author_association: MEMBER
user: login: github-actions[bot]
created_at: 2023-12-25T03:29:58Z
body: This issue has been inactive for 60 days. It will be closed in 60 days if there is no activity. To ping code owners by adding a component label, see [Adding Labels via Comments](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#adding-labels-via-comments), or if you are unsure of which component this issue relates to, please ping `@open-telemetry/collector-contrib-triagers`. If this issue is still relevant, please ping the code owners or leave a comment explaining why it is still relevant. Otherwise, please close it.

Pinging code owners:
- connector/spanmetrics: @albertteoh

See [Adding Labels via Comments](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#adding-labels-via-comments) if you do not have permissions to add labels yourself.
author_association: CONTRIBUTOR
user: login: erri120
created_at: 2023-12-27T01:50:08Z
body: Still relevant, nothing has changed.
author_association: NONE
user: login: github-actions[bot]
created_at: 2024-02-26T03:29:23Z
body: This issue has been inactive for 60 days. It will be closed in 60 days if there is no activity. To ping code owners by adding a component label, see [Adding Labels via Comments](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#adding-labels-via-comments), or if you are unsure of which component this issue relates to, please ping `@open-telemetry/collector-contrib-triagers`. If this issue is still relevant, please ping the code owners or leave a comment explaining why it is still relevant. Otherwise, please close it.

Pinging code owners:
- connector/spanmetrics: @portertech

See [Adding Labels via Comments](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#adding-labels-via-comments) if you do not have permissions to add labels yourself.
author_association: CONTRIBUTOR

Diff:
diff --git a/.chloggen/23662.yaml b/.chloggen/23662.yaml
new file mode 100644
index 0000000000000..230dae5802fbc
--- /dev/null
+++ b/.chloggen/23662.yaml
@@ -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: []
diff --git a/connector/spanmetricsconnector/README.md b/connector/spanmetricsconnector/README.md
index f64c98172f0f6..c52a1278fef55 100644
--- a/connector/spanmetricsconnector/README.md
+++ b/connector/spanmetricsconnector/README.md
@@ -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. 
@@ -164,6 +165,8 @@ connectors:
       - service.name
       - telemetry.sdk.language
       - telemetry.sdk.name
+    include_instrumentation_scope:
+      - express
 
 service:
   pipelines:
diff --git a/connector/spanmetricsconnector/config.go b/connector/spanmetricsconnector/config.go
index 63ed3e79ecae1..f7ec3e516cc23 100644
--- a/connector/spanmetricsconnector/config.go
+++ b/connector/spanmetricsconnector/config.go
@@ -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 {
diff --git a/connector/spanmetricsconnector/connector.go b/connector/spanmetricsconnector/connector.go
index 3a0678dd2839f..5e178375a3f65 100644
--- a/connector/spanmetricsconnector/connector.go
+++ b/connector/spanmetricsconnector/connector.go
@@ -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
@@ -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 {
@@ -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)
@@ -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) {
@@ -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
 }
 
diff --git a/connector/spanmetricsconnector/connector_test.go b/connector/spanmetricsconnector/connector_test.go
index 12c6b2ed696b4..5588bff6256a9 100644
--- a/connector/spanmetricsconnector/connector_test.go
+++ b/connector/spanmetricsconnector/connector_test.go
@@ -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())
+			}
+		})
+	}
+}


Reviews:
user: login: atoulme
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-20T00:07:07Z
user: login: atoulme
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-20T00:07:41Z
user: login: atoulme
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-20T00:09:18Z
user: login: atoulme
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-20T00:09:27Z
user: login: atoulme
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-20T00:10:23Z
user: login: iblancasa
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-20T17:27:20Z
user: login: iblancasa
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-20T17:28:32Z
user: login: iblancasa
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-20T17:29:50Z
user: login: Frapschen
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T02:25:35Z
user: login: Frapschen
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T02:40:51Z
user: login: Frapschen
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T02:43:44Z
user: login: Frapschen
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T03:07:24Z
user: login: iblancasa
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T10:38:27Z
user: login: iblancasa
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T10:39:28Z
user: login: iblancasa
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T10:42:58Z
user: login: Frapschen
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T12:14:31Z
user: login: Frapschen
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T12:25:35Z
user: login: Frapschen
body: LGTM
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T02:08:09Z
user: login: atoulme
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T20:57:21Z

Review comments:
diff_hunk: @@ -106,7 +106,9 @@ 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.
+- `instrumentation_scope`: a list of instrumentation scope names to include in the metrics. They are defined using `name` and `default` fields.
+- `exclude_instrumentation_scope`: a list of instrumentation scope names to exclude from the metrics.
path: connector/spanmetricsconnector/README.md
user: login: atoulme
body: could this be a regex?
created_at: 2025-02-20T00:07:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -31,6 +31,11 @@ type Dimension struct {
 	Default *string `mapstructure:"default"`
 }
 
+type InstrumentationScope struct {
+	Name    string  `mapstructure:"name"`
+	Default *string `mapstructure:"default"`
path: connector/spanmetricsconnector/config.go
user: login: atoulme
body: Is a default really necessary?
created_at: 2025-02-20T00:07:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -520,11 +522,29 @@ 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.ExcludeInstrumentationScope, instrumentationScopeNameKey) && instrumentationScope.Name() != "" {
+		attr.PutStr(instrumentationScopeNameKey, instrumentationScope.Name())
+	}
+	if !contains(p.config.ExcludeInstrumentationScope, instrumentationScopeVersionKey) && instrumentationScope.Version() != "" {
+		attr.PutStr(instrumentationScopeVersionKey, instrumentationScope.Version())
+	}
+
+	for _, p := range p.config.InstrumentationScope {
+		attrs := instrumentationScope.Attributes()
path: connector/spanmetricsconnector/connector.go
user: login: atoulme
body: so even if excluded we set those?
created_at: 2025-02-20T00:09:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -520,11 +522,29 @@ 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.ExcludeInstrumentationScope, instrumentationScopeNameKey) && instrumentationScope.Name() != "" {
+		attr.PutStr(instrumentationScopeNameKey, instrumentationScope.Name())
+	}
+	if !contains(p.config.ExcludeInstrumentationScope, instrumentationScopeVersionKey) && instrumentationScope.Version() != "" {
path: connector/spanmetricsconnector/connector.go
user: login: atoulme
body: those 2 if statements can be combined - ah no it's more tricky than that.

Hmm, this feels weird.
created_at: 2025-02-20T00:09:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -106,7 +106,9 @@ 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.
+- `instrumentation_scope`: a list of instrumentation scope names to include in the metrics. They are defined using `name` and `default` fields.
+- `exclude_instrumentation_scope`: a list of instrumentation scope names to exclude from the metrics.
path: connector/spanmetricsconnector/README.md
user: login: atoulme
body: and scope versions too?
created_at: 2025-02-20T00:10:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -520,11 +522,29 @@ 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.ExcludeInstrumentationScope, instrumentationScopeNameKey) && instrumentationScope.Name() != "" {
+		attr.PutStr(instrumentationScopeNameKey, instrumentationScope.Name())
+	}
+	if !contains(p.config.ExcludeInstrumentationScope, instrumentationScopeVersionKey) && instrumentationScope.Version() != "" {
+		attr.PutStr(instrumentationScopeVersionKey, instrumentationScope.Version())
+	}
+
+	for _, p := range p.config.InstrumentationScope {
+		attrs := instrumentationScope.Attributes()
path: connector/spanmetricsconnector/connector.go
user: login: iblancasa
body: Actually this can be moved to the line before the `for` statement.

> so even if excluded we set those?

Note this is a map and in the next lines we only add those that are part of the config.
created_at: 2025-02-20T17:27:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -106,7 +106,9 @@ 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.
+- `instrumentation_scope`: a list of instrumentation scope names to include in the metrics. They are defined using `name` and `default` fields.
+- `exclude_instrumentation_scope`: a list of instrumentation scope names to exclude from the metrics.
path: connector/spanmetricsconnector/README.md
user: login: iblancasa
body: I tried to follow the same pattern than for dimensions. I'm not sure if a regex will make this easier to configure.
created_at: 2025-02-20T17:28:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -31,6 +31,11 @@ type Dimension struct {
 	Default *string `mapstructure:"default"`
 }
 
+type InstrumentationScope struct {
+	Name    string  `mapstructure:"name"`
+	Default *string `mapstructure:"default"`
path: connector/spanmetricsconnector/config.go
user: login: iblancasa
body: Well, I can remove and simplify this. Yes, I will do.
created_at: 2025-02-20T17:29:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -520,11 +522,27 @@ 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.ExcludeInstrumentationScope, instrumentationScopeNameKey) && instrumentationScope.Name() != "" {
+		attr.PutStr(instrumentationScopeNameKey, instrumentationScope.Name())
+	}
+	if !contains(p.config.ExcludeInstrumentationScope, instrumentationScopeVersionKey) && instrumentationScope.Version() != "" {
+		attr.PutStr(instrumentationScopeVersionKey, instrumentationScope.Version())
+	}
+
+	attrs := instrumentationScope.Attributes()
+	for _, p := range p.config.InstrumentationScope {
path: connector/spanmetricsconnector/connector.go
user: login: Frapschen
body: why we need to add this loop?
created_at: 2025-03-18T02:25:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -520,11 +522,27 @@ 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.ExcludeInstrumentationScope, instrumentationScopeNameKey) && instrumentationScope.Name() != "" {
+		attr.PutStr(instrumentationScopeNameKey, instrumentationScope.Name())
+	}
+	if !contains(p.config.ExcludeInstrumentationScope, instrumentationScopeVersionKey) && instrumentationScope.Version() != "" {
+		attr.PutStr(instrumentationScopeVersionKey, instrumentationScope.Version())
+	}
+
+	attrs := instrumentationScope.Attributes()
+	for _, p := range p.config.InstrumentationScope {
path: connector/spanmetricsconnector/connector.go
user: login: Frapschen
body: I'm a little confuse, In this PR ,we only need add two spanmetrics labels: `span.instrumentation.scope.name`, `span.instrumentation.scope.version`  right?


created_at: 2025-03-18T02:40:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -520,11 +522,27 @@ 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.ExcludeInstrumentationScope, instrumentationScopeNameKey) && instrumentationScope.Name() != "" {
+		attr.PutStr(instrumentationScopeNameKey, instrumentationScope.Name())
+	}
+	if !contains(p.config.ExcludeInstrumentationScope, instrumentationScopeVersionKey) && instrumentationScope.Version() != "" {
path: connector/spanmetricsconnector/connector.go
user: login: Frapschen
body: As the changelod doc described, we only need check the `instrumentationScope.Name()`. 
created_at: 2025-03-18T02:43:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -520,11 +522,27 @@ 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.ExcludeInstrumentationScope, instrumentationScopeNameKey) && instrumentationScope.Name() != "" {
path: connector/spanmetricsconnector/connector.go
user: login: Frapschen
body: if a user config this:
```
instrumentation_scope:
- library.language

exclude_instrumentation_scope
- library.language
```

What would the SpanMetrics labels look like?

created_at: 2025-03-18T03:07:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -520,11 +522,27 @@ 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.ExcludeInstrumentationScope, instrumentationScopeNameKey) && instrumentationScope.Name() != "" {
path: connector/spanmetricsconnector/connector.go
user: login: iblancasa
body: It would happen the same as with `dimensions`: it will be not added.
created_at: 2025-03-21T10:38:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -520,11 +522,27 @@ 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.ExcludeInstrumentationScope, instrumentationScopeNameKey) && instrumentationScope.Name() != "" {
+		attr.PutStr(instrumentationScopeNameKey, instrumentationScope.Name())
+	}
+	if !contains(p.config.ExcludeInstrumentationScope, instrumentationScopeVersionKey) && instrumentationScope.Version() != "" {
path: connector/spanmetricsconnector/connector.go
user: login: iblancasa
body: Ok, I will remove the version then.
created_at: 2025-03-21T10:39:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -520,11 +522,27 @@ 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.ExcludeInstrumentationScope, instrumentationScopeNameKey) && instrumentationScope.Name() != "" {
+		attr.PutStr(instrumentationScopeNameKey, instrumentationScope.Name())
+	}
+	if !contains(p.config.ExcludeInstrumentationScope, instrumentationScopeVersionKey) && instrumentationScope.Version() != "" {
+		attr.PutStr(instrumentationScopeVersionKey, instrumentationScope.Version())
+	}
+
+	attrs := instrumentationScope.Attributes()
+	for _, p := range p.config.InstrumentationScope {
path: connector/spanmetricsconnector/connector.go
user: login: iblancasa
body: Sorry but I misunderstood something from this comment: https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/23662#issuecomment-1777451635
And thought we wanted to add all the attributes.

Sorry for that. Fixing...
created_at: 2025-03-21T10:42:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -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.ExcludeInstrumentationScope, instrumentationScope.Name()) && instrumentationScope.Name() != "" {
path: connector/spanmetricsconnector/connector.go
user: login: Frapschen
body: @iblancasa I would recommend using `IncludeInstrumentationScope` as it is more intuitive. 
created_at: 2025-03-24T12:14:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -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.ExcludeInstrumentationScope, instrumentationScope.Name()) && instrumentationScope.Name() != "" {
path: connector/spanmetricsconnector/connector.go
user: login: Frapschen
body: In most cases (when exclude_instrumentation_scope is empty), your PR would allow any scope attribute to be converted into a spanmetrics label, which is not what we expect.
created_at: 2025-03-24T12:25:35Z
author_association: CONTRIBUTOR

PR comments:
user: login: iblancasa
created_at: 2025-02-26T11:10:00Z
body: cc @portertech @Frapschen 
author_association: CONTRIBUTOR
user: login: iblancasa
created_at: 2025-03-04T15:17:10Z
body: cc @portertech @Frapschen can you take a look?
author_association: CONTRIBUTOR
user: login: atoulme
created_at: 2025-03-23T03:41:53Z
body: @Frapschen @portertech please review as codeowners.
author_association: CONTRIBUTOR
user: login: iblancasa
created_at: 2025-03-26T18:54:10Z
body: @open-telemetry/android-maintainers this was approved by the code owner. Could we merge it?
author_association: CONTRIBUTOR