You've already forked opentelemetry-go
							
							
				mirror of
				https://github.com/open-telemetry/opentelemetry-go.git
				synced 2025-10-31 00:07:40 +02:00 
			
		
		
		
	Use first-seen instrument name for name conflicts (#4428)
* Add acceptance test * Return the first-seen for instrument name conflicts * Add changes to changelog
This commit is contained in:
		| @@ -65,6 +65,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm | |||||||
| - Do not block the metric SDK when OTLP metric exports are blocked in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc` and `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`. (#3925, #4395) | - Do not block the metric SDK when OTLP metric exports are blocked in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc` and `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`. (#3925, #4395) | ||||||
| - Do not append _total if the counter already ends in total `go.opentelemetry.io/otel/exporter/prometheus`. (#4373) | - Do not append _total if the counter already ends in total `go.opentelemetry.io/otel/exporter/prometheus`. (#4373) | ||||||
| - Fix resource detection data race in `go.opentelemetry.io/otel/sdk/resource`. (#4409) | - Fix resource detection data race in `go.opentelemetry.io/otel/sdk/resource`. (#4409) | ||||||
|  | - Use the first-seen instrument name during instrument name conflicts in `go.opentelemetry.io/otel/sdk/metric`. (#4428) | ||||||
|  |  | ||||||
| ### Deprecated | ### Deprecated | ||||||
|  |  | ||||||
|   | |||||||
| @@ -20,6 +20,7 @@ import ( | |||||||
| 	"context" | 	"context" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
| 	"go.opentelemetry.io/otel/attribute" | 	"go.opentelemetry.io/otel/attribute" | ||||||
| 	"go.opentelemetry.io/otel/metric" | 	"go.opentelemetry.io/otel/metric" | ||||||
| @@ -187,6 +188,16 @@ type instID struct { | |||||||
| 	Number string | 	Number string | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Returns a normalized copy of the instID i. | ||||||
|  | // | ||||||
|  | // Instrument names are considered case-insensitive. Standardize the instrument | ||||||
|  | // name to always be lowercase for the returned instID so it can be compared | ||||||
|  | // without the name casing affecting the comparison. | ||||||
|  | func (i instID) normalize() instID { | ||||||
|  | 	i.Name = strings.ToLower(i.Name) | ||||||
|  | 	return i | ||||||
|  | } | ||||||
|  |  | ||||||
| type int64Inst struct { | type int64Inst struct { | ||||||
| 	measures []aggregate.Measure[int64] | 	measures []aggregate.Measure[int64] | ||||||
|  |  | ||||||
|   | |||||||
| @@ -329,7 +329,12 @@ func (i *inserter[N]) cachedAggregator(scope instrumentation.Scope, kind Instrum | |||||||
| 	// If there is a conflict, the specification says the view should | 	// If there is a conflict, the specification says the view should | ||||||
| 	// still be applied and a warning should be logged. | 	// still be applied and a warning should be logged. | ||||||
| 	i.logConflict(id) | 	i.logConflict(id) | ||||||
| 	cv := i.aggregators.Lookup(id, func() aggVal[N] { |  | ||||||
|  | 	// If there are requests for the same instrument with different name | ||||||
|  | 	// casing, the first-seen needs to be returned. Use a normalize ID for the | ||||||
|  | 	// cache lookup to ensure the correct comparison. | ||||||
|  | 	normID := id.normalize() | ||||||
|  | 	cv := i.aggregators.Lookup(normID, func() aggVal[N] { | ||||||
| 		b := aggregate.Builder[N]{ | 		b := aggregate.Builder[N]{ | ||||||
| 			Temporality: i.pipeline.reader.temporality(kind), | 			Temporality: i.pipeline.reader.temporality(kind), | ||||||
| 		} | 		} | ||||||
| @@ -344,6 +349,8 @@ func (i *inserter[N]) cachedAggregator(scope instrumentation.Scope, kind Instrum | |||||||
| 			return aggVal[N]{0, nil, nil} | 			return aggVal[N]{0, nil, nil} | ||||||
| 		} | 		} | ||||||
| 		i.pipeline.addSync(scope, instrumentSync{ | 		i.pipeline.addSync(scope, instrumentSync{ | ||||||
|  | 			// Use the first-seen name casing for this and all subsequent | ||||||
|  | 			// requests of this instrument. | ||||||
| 			name:        stream.Name, | 			name:        stream.Name, | ||||||
| 			description: stream.Description, | 			description: stream.Description, | ||||||
| 			unit:        stream.Unit, | 			unit:        stream.Unit, | ||||||
| @@ -355,12 +362,13 @@ func (i *inserter[N]) cachedAggregator(scope instrumentation.Scope, kind Instrum | |||||||
| 	return cv.Measure, cv.ID, cv.Err | 	return cv.Measure, cv.ID, cv.Err | ||||||
| } | } | ||||||
|  |  | ||||||
| // logConflict validates if an instrument with the same name as id has already | // logConflict validates if an instrument with the same case-insensitive name | ||||||
| // been created. If that instrument conflicts with id, a warning is logged. | // as id has already been created. If that instrument conflicts with id, a | ||||||
|  | // warning is logged. | ||||||
| func (i *inserter[N]) logConflict(id instID) { | func (i *inserter[N]) logConflict(id instID) { | ||||||
| 	// The API specification defines names as case-insensitive. If there is a | 	// The API specification defines names as case-insensitive. If there is a | ||||||
| 	// different casing of a name it needs to be a conflict. | 	// different casing of a name it needs to be a conflict. | ||||||
| 	name := strings.ToLower(id.Name) | 	name := id.normalize().Name | ||||||
| 	existing := i.views.Lookup(name, func() instID { return id }) | 	existing := i.views.Lookup(name, func() instID { return id }) | ||||||
| 	if id == existing { | 	if id == existing { | ||||||
| 		return | 		return | ||||||
|   | |||||||
| @@ -32,6 +32,7 @@ import ( | |||||||
| 	"go.opentelemetry.io/otel" | 	"go.opentelemetry.io/otel" | ||||||
| 	"go.opentelemetry.io/otel/attribute" | 	"go.opentelemetry.io/otel/attribute" | ||||||
| 	"go.opentelemetry.io/otel/sdk/instrumentation" | 	"go.opentelemetry.io/otel/sdk/instrumentation" | ||||||
|  | 	"go.opentelemetry.io/otel/sdk/metric/aggregation" | ||||||
| 	"go.opentelemetry.io/otel/sdk/metric/metricdata" | 	"go.opentelemetry.io/otel/sdk/metric/metricdata" | ||||||
| 	"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" | 	"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" | ||||||
| 	"go.opentelemetry.io/otel/sdk/resource" | 	"go.opentelemetry.io/otel/sdk/resource" | ||||||
| @@ -358,3 +359,37 @@ func TestLogConflictSuggestView(t *testing.T) { | |||||||
| 		msg = "" | 		msg = "" | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func TestInserterCachedAggregatorNameConflict(t *testing.T) { | ||||||
|  | 	const name = "requestCount" | ||||||
|  | 	scope := instrumentation.Scope{Name: "pipeline_test"} | ||||||
|  | 	kind := InstrumentKindCounter | ||||||
|  | 	stream := Stream{ | ||||||
|  | 		Name:        name, | ||||||
|  | 		Aggregation: aggregation.Sum{}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var vc cache[string, instID] | ||||||
|  | 	pipe := newPipeline(nil, NewManualReader(), nil) | ||||||
|  | 	i := newInserter[int64](pipe, &vc) | ||||||
|  |  | ||||||
|  | 	_, origID, err := i.cachedAggregator(scope, kind, stream) | ||||||
|  | 	require.NoError(t, err) | ||||||
|  |  | ||||||
|  | 	require.Len(t, pipe.aggregations, 1) | ||||||
|  | 	require.Contains(t, pipe.aggregations, scope) | ||||||
|  | 	iSync := pipe.aggregations[scope] | ||||||
|  | 	require.Len(t, iSync, 1) | ||||||
|  | 	require.Equal(t, name, iSync[0].name) | ||||||
|  |  | ||||||
|  | 	stream.Name = "RequestCount" | ||||||
|  | 	_, id, err := i.cachedAggregator(scope, kind, stream) | ||||||
|  | 	require.NoError(t, err) | ||||||
|  | 	assert.Equal(t, origID, id, "multiple aggregators for equivalent name") | ||||||
|  |  | ||||||
|  | 	assert.Len(t, pipe.aggregations, 1, "additional scope added") | ||||||
|  | 	require.Contains(t, pipe.aggregations, scope, "original scope removed") | ||||||
|  | 	iSync = pipe.aggregations[scope] | ||||||
|  | 	require.Len(t, iSync, 1, "registered instrumentSync changed") | ||||||
|  | 	assert.Equal(t, name, iSync[0].name, "stream name changed") | ||||||
|  | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user