Skip to content

Commit 033f414

Browse files
migrate semconv from v1.25.0
Signed-off-by: singhvibhanshu <find.vibhanshu@gmail.com>
1 parent 76dacb3 commit 033f414

7 files changed

Lines changed: 171 additions & 22 deletions

File tree

internal/coreinternal/documentation.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@ This component has the following feature gates:
1313
| `internal.coreinternal.goldendataset.DontEmitV0HTTPConventions` | alpha | When enabled, goldendataset no longer generates spans with deprecated semconv HTTP attributes like http.user_agent and http.client_ip. | v0.154.0 | N/A | [Link](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/45293) |
1414
| `internal.coreinternal.goldendataset.DontEmitV0MessagingConventions` | alpha | When enabled, goldendataset no longer generates spans with deprecated semconv v1.16.0 attribute messaging.destination. | v0.155.0 | N/A | [Link](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/45077) |
1515
| `internal.coreinternal.goldendataset.DontEmitV0NetworkConventions` | beta | When enabled, goldendataset no longer generates spans with deprecated semconv v1.12.0 attributes (net.host.ip, net.peer.ip, http.host, http.server_name). | v0.147.0 | N/A | [Link](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/45076) |
16+
| `internal.coreinternal.goldendataset.DontEmitV0NetworkV125Conventions` | alpha | When enabled, goldendataset no longer generates spans with deprecated semconv v1.25.0 attributes (net.host.port, net.peer.name, net.peer.port, net.transport). | v0.155.0 | N/A | [Link](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/45297) |
1617
| `internal.coreinternal.goldendataset.DontEmitV0RPCConventions` | beta | When enabled, goldendataset no longer generates spans with deprecated semconv RPC attributes rpc.service and peer.service. | v0.148.0 | N/A | [Link](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/47548) |
1718
| `internal.coreinternal.goldendataset.EmitV1DatabaseConventions` | alpha | When enabled, goldendataset generates spans with db.system.name (semconv v1.40.0) instead of the deprecated db.system (semconv v1.28.0). | v0.154.0 | N/A | [Link](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/45299) |
1819
| `internal.coreinternal.goldendataset.EmitV1FaaSConventions` | alpha | When enabled, goldendataset generates spans with faas.invocation_id (semconv v1.40.0) alongside legacy faas.execution to support migration. | v0.155.0 | N/A | [Link](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/45293) |
1920
| `internal.coreinternal.goldendataset.EmitV1HTTPConventions` | alpha | When enabled, goldendataset generates spans with user_agent.original and client.address (semconv v1.40.0) alongside legacy http.user_agent and http.client_ip to support migration. | v0.154.0 | N/A | [Link](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/45293) |
2021
| `internal.coreinternal.goldendataset.EmitV1MessagingConventions` | alpha | When enabled, goldendataset generates spans with messaging.destination.name (semconv v1.40.0) alongside legacy messaging.destination to support migration. | v0.155.0 | N/A | [Link](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/45077) |
2122
| `internal.coreinternal.goldendataset.EmitV1NetworkConventions` | beta | When enabled, goldendataset generates spans with network.local.address, network.peer.address, and server.address (semconv v1.38.0) instead of the deprecated net.host.ip, net.peer.ip, http.host, http.server_name (semconv v1.12.0). Enable together with pkg.translator.zipkin.EmitV1NetworkConventions for consistent round-trip translation in tests. | v0.147.0 | N/A | [Link](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/45076) |
23+
| `internal.coreinternal.goldendataset.EmitV1NetworkV125Conventions` | alpha | When enabled, goldendataset generates spans with network.transport, server.address, server.port, client.address, and client.port instead of the deprecated net.transport, net.peer.name, net.host.port, and net.peer.port (semconv v1.25.0). | v0.155.0 | N/A | [Link](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/45297) |
2224
| `internal.coreinternal.goldendataset.EmitV1RPCConventions` | beta | When enabled, goldendataset generates spans with rpc.method and service.peer.name (semconv v1.40.0) alongside legacy rpc.service and peer.service to support migration. | v0.148.0 | N/A | [Link](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/47548) |
2325

2426
For more information about feature gates, see the [Feature Gates](https://github.com/open-telemetry/opentelemetry-collector/blob/main/featuregate/README.md) documentation.

internal/coreinternal/goldendataset/span_generator.go

Lines changed: 49 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -207,20 +207,24 @@ func appendDatabaseSQLAttributes(attrMap pcommon.Map) {
207207
attrMap.PutStr(string(conventionsv125.DBUserKey), "billing_user")
208208
if !metadata.InternalCoreinternalGoldendatasetDontEmitV0NetworkConventionsFeatureGate.IsEnabled() {
209209
attrMap.PutStr("net.host.ip", "192.0.3.122")
210-
}
211-
if metadata.InternalCoreinternalGoldendatasetEmitV1NetworkConventionsFeatureGate.IsEnabled() {
212-
attrMap.PutStr(string(conventions.NetworkLocalAddressKey), "192.0.3.122")
213-
}
214-
attrMap.PutInt(string(conventionsv125.NetHostPortKey), 51306)
215-
attrMap.PutStr(string(conventionsv125.NetPeerNameKey), "shopdb.example.com")
216-
if !metadata.InternalCoreinternalGoldendatasetDontEmitV0NetworkConventionsFeatureGate.IsEnabled() {
217210
attrMap.PutStr("net.peer.ip", "192.0.2.12")
218211
}
212+
if !metadata.InternalCoreinternalGoldendatasetDontEmitV0NetworkV125ConventionsFeatureGate.IsEnabled() {
213+
attrMap.PutInt(string(conventionsv125.NetHostPortKey), 51306)
214+
attrMap.PutStr(string(conventionsv125.NetPeerNameKey), "shopdb.example.com")
215+
attrMap.PutInt(string(conventionsv125.NetPeerPortKey), 3306)
216+
attrMap.PutStr(string(conventionsv125.NetTransportKey), "IP.TCP")
217+
}
219218
if metadata.InternalCoreinternalGoldendatasetEmitV1NetworkConventionsFeatureGate.IsEnabled() {
219+
attrMap.PutStr(string(conventions.NetworkLocalAddressKey), "192.0.3.122")
220220
attrMap.PutStr(string(conventions.NetworkPeerAddressKey), "192.0.2.12")
221221
}
222-
attrMap.PutInt(string(conventionsv125.NetPeerPortKey), 3306)
223-
attrMap.PutStr(string(conventionsv125.NetTransportKey), "IP.TCP")
222+
if metadata.InternalCoreinternalGoldendatasetEmitV1NetworkV125ConventionsFeatureGate.IsEnabled() {
223+
attrMap.PutInt(string(conventions.ClientPortKey), 51306)
224+
attrMap.PutStr(string(conventions.ServerAddressKey), "shopdb.example.com")
225+
attrMap.PutInt(string(conventions.ServerPortKey), 3306)
226+
attrMap.PutStr(string(conventions.NetworkTransportKey), "tcp")
227+
}
224228
attrMap.PutStr(string(conventionsv125.DBNameKey), "shopdb")
225229
attrMap.PutStr(string(conventionsv125.DBStatementKey), "SELECT * FROM orders WHERE order_id = 'o4711'")
226230
attrMap.PutStr(string(conventionsv126.EnduserIDKey), "unittest")
@@ -234,15 +238,22 @@ func appendDatabaseNoSQLAttributes(attrMap pcommon.Map) {
234238
attrMap.PutStr(string(conventions.DBSystemNameKey), "mongodb")
235239
}
236240
attrMap.PutStr(string(conventionsv125.DBUserKey), "the_user")
237-
attrMap.PutStr(string(conventionsv125.NetPeerNameKey), "mongodb0.example.com")
238241
if !metadata.InternalCoreinternalGoldendatasetDontEmitV0NetworkConventionsFeatureGate.IsEnabled() {
239242
attrMap.PutStr("net.peer.ip", "192.0.2.14")
240243
}
244+
if !metadata.InternalCoreinternalGoldendatasetDontEmitV0NetworkV125ConventionsFeatureGate.IsEnabled() {
245+
attrMap.PutStr(string(conventionsv125.NetPeerNameKey), "mongodb0.example.com")
246+
attrMap.PutInt(string(conventionsv125.NetPeerPortKey), 27017)
247+
attrMap.PutStr(string(conventionsv125.NetTransportKey), "IP.TCP")
248+
}
241249
if metadata.InternalCoreinternalGoldendatasetEmitV1NetworkConventionsFeatureGate.IsEnabled() {
242250
attrMap.PutStr(string(conventions.NetworkPeerAddressKey), "192.0.2.14")
243251
}
244-
attrMap.PutInt(string(conventionsv125.NetPeerPortKey), 27017)
245-
attrMap.PutStr(string(conventionsv125.NetTransportKey), "IP.TCP")
252+
if metadata.InternalCoreinternalGoldendatasetEmitV1NetworkV125ConventionsFeatureGate.IsEnabled() {
253+
attrMap.PutStr(string(conventions.ServerAddressKey), "mongodb0.example.com")
254+
attrMap.PutInt(string(conventions.ServerPortKey), 27017)
255+
attrMap.PutStr(string(conventions.NetworkTransportKey), "tcp")
256+
}
246257
attrMap.PutStr(string(conventionsv125.DBNameKey), "shopDb")
247258
attrMap.PutStr(string(conventionsv125.DBOperationKey), "findAndModify")
248259
attrMap.PutStr(string(conventionsv125.DBMongoDBCollectionKey), "products")
@@ -340,10 +351,15 @@ func appendHTTPServerAttributes(includeStatus bool, attrMap pcommon.Map) {
340351
if !metadata.InternalCoreinternalGoldendatasetDontEmitV0NetworkConventionsFeatureGate.IsEnabled() {
341352
attrMap.PutStr("http.server_name", "api22.opentelemetry.io")
342353
}
354+
if !metadata.InternalCoreinternalGoldendatasetDontEmitV0NetworkV125ConventionsFeatureGate.IsEnabled() {
355+
attrMap.PutInt(string(conventionsv125.NetHostPortKey), 443)
356+
}
343357
if metadata.InternalCoreinternalGoldendatasetEmitV1NetworkConventionsFeatureGate.IsEnabled() {
344358
attrMap.PutStr(string(conventions.ServerAddressKey), "api22.opentelemetry.io")
345359
}
346-
attrMap.PutInt(string(conventionsv125.NetHostPortKey), 443)
360+
if metadata.InternalCoreinternalGoldendatasetEmitV1NetworkV125ConventionsFeatureGate.IsEnabled() {
361+
attrMap.PutInt(string(conventions.ServerPortKey), 443)
362+
}
347363
attrMap.PutStr(string(conventionsv125.HTTPTargetKey), "/blog/posts")
348364
attrMap.PutStr(string(conventionsv119.HTTPFlavorKey), "2")
349365
if includeStatus {
@@ -415,10 +431,15 @@ func appendGRPCClientAttributes(attrMap pcommon.Map) {
415431
if !metadata.InternalCoreinternalGoldendatasetDontEmitV0NetworkConventionsFeatureGate.IsEnabled() {
416432
attrMap.PutStr("net.peer.ip", "2600:1700:1f00:11c0:4de0:c223:a800:4e87")
417433
}
434+
if !metadata.InternalCoreinternalGoldendatasetDontEmitV0NetworkV125ConventionsFeatureGate.IsEnabled() {
435+
attrMap.PutInt(string(conventionsv125.NetHostPortKey), 8443)
436+
}
418437
if metadata.InternalCoreinternalGoldendatasetEmitV1NetworkConventionsFeatureGate.IsEnabled() {
419438
attrMap.PutStr(string(conventions.NetworkPeerAddressKey), "2600:1700:1f00:11c0:4de0:c223:a800:4e87")
420439
}
421-
attrMap.PutInt(string(conventionsv125.NetHostPortKey), 8443)
440+
if metadata.InternalCoreinternalGoldendatasetEmitV1NetworkV125ConventionsFeatureGate.IsEnabled() {
441+
attrMap.PutInt(string(conventions.ServerPortKey), 8443)
442+
}
422443
attrMap.PutStr(string(conventionsv126.EnduserIDKey), "unittest")
423444
}
424445

@@ -448,18 +469,19 @@ func appendMaxCountAttributes(includeStatus bool, attrMap pcommon.Map) {
448469
attrMap.PutStr(string(conventionsv125.HTTPSchemeKey), "https")
449470
if !metadata.InternalCoreinternalGoldendatasetDontEmitV0NetworkConventionsFeatureGate.IsEnabled() {
450471
attrMap.PutStr("http.host", "api.opentelemetry.io")
451-
}
452-
if metadata.InternalCoreinternalGoldendatasetEmitV1NetworkConventionsFeatureGate.IsEnabled() {
453-
attrMap.PutStr(string(conventions.ServerAddressKey), "api.opentelemetry.io")
454-
}
455-
attrMap.PutStr(string(conventionsv125.NetHostNameKey), "api22.opentelemetry.io")
456-
if !metadata.InternalCoreinternalGoldendatasetDontEmitV0NetworkConventionsFeatureGate.IsEnabled() {
457472
attrMap.PutStr("net.host.ip", "2600:1700:1f00:11c0:1ced:afa5:fd88:9d48")
458473
}
474+
if !metadata.InternalCoreinternalGoldendatasetDontEmitV0NetworkV125ConventionsFeatureGate.IsEnabled() {
475+
attrMap.PutInt(string(conventionsv125.NetHostPortKey), 443)
476+
}
459477
if metadata.InternalCoreinternalGoldendatasetEmitV1NetworkConventionsFeatureGate.IsEnabled() {
478+
attrMap.PutStr(string(conventions.ServerAddressKey), "api.opentelemetry.io")
460479
attrMap.PutStr(string(conventions.NetworkLocalAddressKey), "2600:1700:1f00:11c0:1ced:afa5:fd88:9d48")
461480
}
462-
attrMap.PutInt(string(conventionsv125.NetHostPortKey), 443)
481+
if metadata.InternalCoreinternalGoldendatasetEmitV1NetworkV125ConventionsFeatureGate.IsEnabled() {
482+
attrMap.PutInt(string(conventions.ServerPortKey), 443)
483+
}
484+
attrMap.PutStr(string(conventionsv125.NetHostNameKey), "api22.opentelemetry.io")
463485
attrMap.PutStr(string(conventionsv125.HTTPTargetKey), "/blog/posts")
464486
attrMap.PutStr(string(conventionsv119.HTTPFlavorKey), "2")
465487
if includeStatus {
@@ -490,10 +512,15 @@ func appendMaxCountAttributes(includeStatus bool, attrMap pcommon.Map) {
490512
if !metadata.InternalCoreinternalGoldendatasetDontEmitV0NetworkConventionsFeatureGate.IsEnabled() {
491513
attrMap.PutStr("net.peer.ip", "2600:1700:1f00:11c0:1ced:afa5:fd77:9ddc")
492514
}
515+
if !metadata.InternalCoreinternalGoldendatasetDontEmitV0NetworkV125ConventionsFeatureGate.IsEnabled() {
516+
attrMap.PutInt(string(conventionsv125.NetPeerPortKey), 39111)
517+
}
493518
if metadata.InternalCoreinternalGoldendatasetEmitV1NetworkConventionsFeatureGate.IsEnabled() {
494519
attrMap.PutStr(string(conventions.NetworkPeerAddressKey), "2600:1700:1f00:11c0:1ced:afa5:fd77:9ddc")
495520
}
496-
attrMap.PutInt(string(conventionsv125.NetPeerPortKey), 39111)
521+
if metadata.InternalCoreinternalGoldendatasetEmitV1NetworkV125ConventionsFeatureGate.IsEnabled() {
522+
attrMap.PutInt(string(conventions.ClientPortKey), 39111)
523+
}
497524
attrMap.PutDouble("ai-sampler.weight", 0.07)
498525
attrMap.PutBool("ai-sampler.absolute", false)
499526
attrMap.PutInt("ai-sampler.maxhops", 6)

internal/coreinternal/goldendataset/span_generator_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,75 @@ func TestGenerateMessagingProducerSpanFeatureGates(t *testing.T) {
145145
})
146146
}
147147
}
148+
149+
func TestGenerateNetworkConventionsFeatureGates(t *testing.T) {
150+
testCases := []struct {
151+
name string
152+
dontEmitV0 bool
153+
emitV1 bool
154+
expectedKeys []string
155+
absentKeys []string
156+
}{
157+
{
158+
name: "default_v0_only",
159+
dontEmitV0: false,
160+
emitV1: false,
161+
expectedKeys: []string{"net.host.ip", "net.host.port", "net.peer.name", "net.peer.ip", "net.peer.port", "net.transport"},
162+
absentKeys: []string{"network.local.address", "client.port", "server.address", "network.peer.address", "server.port", "network.transport"},
163+
},
164+
{
165+
name: "double_publish",
166+
dontEmitV0: false,
167+
emitV1: true,
168+
expectedKeys: []string{"net.host.ip", "net.host.port", "net.peer.name", "net.peer.ip", "net.peer.port", "net.transport", "network.local.address", "client.port", "server.address", "network.peer.address", "server.port", "network.transport"},
169+
absentKeys: []string{},
170+
},
171+
{
172+
name: "v1_only",
173+
dontEmitV0: true,
174+
emitV1: true,
175+
expectedKeys: []string{"network.local.address", "client.port", "server.address", "network.peer.address", "server.port", "network.transport"},
176+
absentKeys: []string{"net.host.ip", "net.host.port", "net.peer.name", "net.peer.ip", "net.peer.port", "net.transport"},
177+
},
178+
}
179+
180+
for _, tc := range testCases {
181+
t.Run(tc.name, func(t *testing.T) {
182+
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.InternalCoreinternalGoldendatasetDontEmitV0NetworkConventionsFeatureGate.ID(), tc.dontEmitV0))
183+
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.InternalCoreinternalGoldendatasetEmitV1NetworkConventionsFeatureGate.ID(), tc.emitV1))
184+
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.InternalCoreinternalGoldendatasetDontEmitV0NetworkV125ConventionsFeatureGate.ID(), tc.dontEmitV0))
185+
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.InternalCoreinternalGoldendatasetEmitV1NetworkV125ConventionsFeatureGate.ID(), tc.emitV1))
186+
187+
t.Cleanup(func() {
188+
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.InternalCoreinternalGoldendatasetDontEmitV0NetworkConventionsFeatureGate.ID(), false))
189+
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.InternalCoreinternalGoldendatasetEmitV1NetworkConventionsFeatureGate.ID(), false))
190+
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.InternalCoreinternalGoldendatasetDontEmitV0NetworkV125ConventionsFeatureGate.ID(), false))
191+
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.InternalCoreinternalGoldendatasetEmitV1NetworkV125ConventionsFeatureGate.ID(), false))
192+
})
193+
194+
random := rand.Reader
195+
traceID := generateTraceID(random)
196+
spanInputs := &PICTSpanInputs{
197+
Parent: SpanParentRoot,
198+
Tracestate: TraceStateEmpty,
199+
Kind: SpanKindClient,
200+
Attributes: SpanAttrDatabaseSQL,
201+
Events: SpanChildCountEmpty,
202+
Links: SpanChildCountEmpty,
203+
Status: SpanStatusOk,
204+
}
205+
span := ptrace.NewSpan()
206+
fillSpan(traceID, pcommon.SpanID([8]byte{}), "/gotest-parent", spanInputs, random, span)
207+
208+
attrs := span.Attributes()
209+
for _, k := range tc.expectedKeys {
210+
_, ok := attrs.Get(k)
211+
assert.True(t, ok, "Expected attribute %s to be present", k)
212+
}
213+
for _, k := range tc.absentKeys {
214+
_, ok := attrs.Get(k)
215+
assert.False(t, ok, "Expected attribute %s to be absent", k)
216+
}
217+
})
218+
}
219+
}

internal/coreinternal/goldendataset/traces_generator.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ func GenerateTraces(tracePairsFile, spanPairsFile string) ([]ptrace.Traces, erro
3333
if metadata.InternalCoreinternalGoldendatasetDontEmitV0MessagingConventionsFeatureGate.IsEnabled() && !metadata.InternalCoreinternalGoldendatasetEmitV1MessagingConventionsFeatureGate.IsEnabled() {
3434
return nil, errors.New("internal.coreinternal.goldendataset.DontEmitV0MessagingConventions cannot be enabled without enabling internal.coreinternal.goldendataset.EmitV1MessagingConventions")
3535
}
36+
if metadata.InternalCoreinternalGoldendatasetDontEmitV0NetworkV125ConventionsFeatureGate.IsEnabled() && !metadata.InternalCoreinternalGoldendatasetEmitV1NetworkV125ConventionsFeatureGate.IsEnabled() {
37+
return nil, errors.New("internal.coreinternal.goldendataset.DontEmitV0NetworkV125Conventions cannot be enabled without enabling internal.coreinternal.goldendataset.EmitV1NetworkV125Conventions")
38+
}
3639
random := (*randReader)(rand.New(rand.NewPCG(42, 0)))
3740
pairsData, err := loadPictOutputFile(tracePairsFile)
3841
if err != nil {

internal/coreinternal/goldendataset/traces_generator_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,15 @@ func TestGenerateTracesInvalidMessagingFeatureGateCombination(t *testing.T) {
5555
_, err := GenerateTraces("testdata/generated_pict_pairs_traces.txt", "testdata/generated_pict_pairs_spans.txt")
5656
require.ErrorContains(t, err, "internal.coreinternal.goldendataset.DontEmitV0MessagingConventions cannot be enabled without enabling internal.coreinternal.goldendataset.EmitV1MessagingConventions")
5757
}
58+
59+
func TestGenerateTracesInvalidNetworkV125FeatureGateCombination(t *testing.T) {
60+
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.InternalCoreinternalGoldendatasetDontEmitV0NetworkV125ConventionsFeatureGate.ID(), true))
61+
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.InternalCoreinternalGoldendatasetEmitV1NetworkV125ConventionsFeatureGate.ID(), false))
62+
t.Cleanup(func() {
63+
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.InternalCoreinternalGoldendatasetDontEmitV0NetworkV125ConventionsFeatureGate.ID(), false))
64+
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.InternalCoreinternalGoldendatasetEmitV1NetworkV125ConventionsFeatureGate.ID(), false))
65+
})
66+
67+
_, err := GenerateTraces("testdata/generated_pict_pairs_traces.txt", "testdata/generated_pict_pairs_spans.txt")
68+
require.ErrorContains(t, err, "internal.coreinternal.goldendataset.DontEmitV0NetworkV125Conventions cannot be enabled without enabling internal.coreinternal.goldendataset.EmitV1NetworkV125Conventions")
69+
}

0 commit comments

Comments
 (0)