Skip to content

Commit c20c36b

Browse files
authored
refactor: Updated otel bridge to centralize mapping rules for a given span attribute to accomondate semantic convention spec updates (#3010)
1 parent 8fab715 commit c20c36b

31 files changed

+1548
-531
lines changed

THIRD_PARTY_NOTICES.md

+144-114
Large diffs are not rendered by default.

lib/otel/attr-mapping/db.js

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright 2025 New Relic Corporation. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
'use strict'
7+
8+
// These mappings are compliant with v1.24.0 and have mappings with v1.20.0 of semantic conventions
9+
// https://github.com/open-telemetry/semantic-conventions/blob/v1.24.0/docs/database/database-spans.md
10+
11+
const constants = require('../constants')
12+
const createMapper = require('./utils')
13+
const attrMappings = {
14+
dynamoTable: {
15+
attrs: [constants.ATTR_DYNAMO_TABLE_NAMES]
16+
},
17+
name: {
18+
attrs: [constants.ATTR_DB_NAME],
19+
mapping({ segment }) {
20+
return (value) => segment.addAttribute('database_name', value)
21+
}
22+
},
23+
operation: {
24+
attrs: [constants.ATTR_DB_OPERATION]
25+
},
26+
port: {
27+
attrs: [constants.ATTR_NETWORK_PEER_PORT, constants.ATTR_NET_PEER_PORT],
28+
mapping({ segment }) {
29+
return (value) => segment.addAttribute('port_path_or_id', value)
30+
}
31+
},
32+
/*
33+
* This attribute was collected in `onStart`
34+
* and was passed to `ParsedStatement`. It adds
35+
* this segment attribute as `sql` or `sql_obfuscated`
36+
* and then when the span is built from segment
37+
* re-assigns to `db.statement`. This needs
38+
* to be skipped because it will be the raw value.
39+
*/
40+
query: {
41+
attrs: [constants.ATTR_DB_STATEMENT],
42+
mapping() {
43+
return () => {}
44+
}
45+
},
46+
server: {
47+
attrs: [constants.ATTR_SERVER_ADDRESS, constants.ATTR_NET_PEER_NAME],
48+
mapping({ segment }) {
49+
return (value) => segment.addAttribute('host', value)
50+
}
51+
},
52+
system: {
53+
attrs: [constants.ATTR_DB_SYSTEM],
54+
mapping({ segment }) {
55+
return (value) => segment.addAttribute('product', value)
56+
}
57+
},
58+
table: {
59+
attrs: [constants.ATTR_DB_SQL_TABLE, constants.ATTR_MONGODB_COLLECTION]
60+
}
61+
}
62+
63+
const { getAttr: dbAttr, attributesMapper } = createMapper(attrMappings)
64+
65+
function dbMapper({ segment }) {
66+
const dbPortMapping = attributesMapper({ key: 'port', segment })
67+
const dbServerMapping = attributesMapper({ key: 'server', segment })
68+
const dbNameMapping = attributesMapper({ key: 'name', segment })
69+
const dbSystemMapping = attributesMapper({ key: 'system', segment })
70+
const dbQueryMapping = attributesMapper({ key: 'query' })
71+
return {
72+
...dbPortMapping,
73+
...dbServerMapping,
74+
...dbNameMapping,
75+
...dbSystemMapping,
76+
...dbQueryMapping
77+
}
78+
}
79+
80+
module.exports = {
81+
dbAttr,
82+
dbMapper
83+
}

lib/otel/attr-mapping/exceptions.js

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright 2025 New Relic Corporation. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
'use strict'
7+
8+
const constants = require('../constants')
9+
const createMapper = require('./utils')
10+
const attrMappings = {
11+
msg: {
12+
attrs: [constants.EXCEPTION_MESSAGE, constants.EXCEPTION_TYPE]
13+
},
14+
stack: {
15+
attrs: [constants.EXCEPTION_STACKTRACE]
16+
},
17+
}
18+
19+
const { getAttr: exceptionAttr } = createMapper(attrMappings)
20+
21+
module.exports = {
22+
exceptionAttr
23+
}

lib/otel/attr-mapping/faas.js

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright 2025 New Relic Corporation. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
'use strict'
7+
8+
const constants = require('../constants')
9+
const createMapper = require('./utils')
10+
const attrMappings = {
11+
name: {
12+
attrs: [constants.ATTR_FAAS_INVOKED_NAME]
13+
},
14+
provider: {
15+
attrs: [constants.ATTR_FAAS_INVOKED_PROVIDER]
16+
},
17+
region: {
18+
attrs: [constants.ATTR_FAAS_INVOKED_REGION, constants.ATTR_AWS_REGION]
19+
}
20+
}
21+
const { getAttr: faasAttr } = createMapper(attrMappings)
22+
23+
module.exports = {
24+
faasAttr
25+
}

lib/otel/attr-mapping/http.js

+193
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
/*
2+
* Copyright 2025 New Relic Corporation. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
const constants = require('../constants')
7+
const createMapper = require('./utils')
8+
const { DESTINATIONS } = require('../../config/attribute-filter')
9+
10+
// These mappings are compliant with v1.23.0 and have mappings with v1.20.0 of semantic conventions
11+
// https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/http/http-spans.md
12+
13+
const attrMappings = {
14+
clientHost: {
15+
attrs: [constants.ATTR_SERVER_ADDRESS, constants.ATTR_NET_PEER_NAME],
16+
mapping() {
17+
return () => {}
18+
}
19+
},
20+
clientPort: {
21+
attrs: [constants.ATTR_SERVER_PORT, constants.ATTR_NETWORK_PEER_PORT, constants.ATTR_NET_PEER_PORT],
22+
mapping() {
23+
return () => {}
24+
}
25+
},
26+
clientUrl: {
27+
attrs: [constants.ATTR_FULL_URL, constants.ATTR_HTTP_URL],
28+
mapping() {
29+
return () => {}
30+
}
31+
},
32+
grpcStatusCode: {
33+
attrs: [constants.ATTR_GRPC_STATUS_CODE],
34+
mapping({ segment, transaction }) {
35+
return (value) => {
36+
transaction.trace.attributes.addAttribute(DESTINATIONS.TRANS_COMMON, 'response.status', value)
37+
segment.addAttribute(constants.ATTR_GRPC_STATUS_CODE, value)
38+
}
39+
}
40+
},
41+
rpcMethod: {
42+
attrs: [constants.ATTR_RPC_METHOD],
43+
mapping({ transaction }) {
44+
return (value) => transaction.trace.attributes.addAttribute(DESTINATIONS.TRANS_COMMON, 'request.method', value)
45+
}
46+
},
47+
rpcSystem: {
48+
attrs: [constants.ATTR_RPC_SYSTEM],
49+
mapping({ segment }) {
50+
return (value) => segment.addAttribute('component', value)
51+
}
52+
},
53+
rpcService: {
54+
attrs: [constants.ATTR_RPC_SERVICE],
55+
},
56+
host: {
57+
attrs: [constants.ATTR_SERVER_ADDRESS, constants.ATTR_NET_HOST_NAME],
58+
mapping({ segment }) {
59+
return (value) => {
60+
segment.addAttribute('host', value)
61+
}
62+
}
63+
},
64+
method: {
65+
attrs: [constants.ATTR_HTTP_REQUEST_METHOD, constants.ATTR_HTTP_METHOD],
66+
mapping({ transaction }) {
67+
return (value) => {
68+
if (transaction) {
69+
transaction.trace.attributes.addAttribute(DESTINATIONS.TRANS_COMMON, 'request.method', value)
70+
}
71+
}
72+
}
73+
},
74+
port: {
75+
attrs: [constants.ATTR_SERVER_PORT, constants.ATTR_NET_HOST_PORT],
76+
mapping({ segment }) {
77+
return (value) => {
78+
segment.addAttribute('port', value)
79+
}
80+
}
81+
},
82+
route: {
83+
attrs: [constants.ATTR_HTTP_ROUTE],
84+
mapping({ segment, transaction }) {
85+
return (value) => {
86+
transaction.nameState.appendPath(value)
87+
segment.addAttribute('http.route', value)
88+
}
89+
}
90+
},
91+
statusCode: {
92+
attrs: [constants.ATTR_HTTP_RES_STATUS_CODE, constants.ATTR_HTTP_STATUS_CODE],
93+
mapping({ segment, transaction }) {
94+
return (value) => {
95+
if (segment) {
96+
segment.addAttribute('http.statusCode', value)
97+
} else {
98+
transaction.statusCode = value
99+
transaction.trace.attributes.addAttribute(DESTINATIONS.TRANS_COMMON, 'http.statusCode', value)
100+
}
101+
}
102+
}
103+
},
104+
statusText: {
105+
attrs: [constants.ATTR_HTTP_STATUS_TEXT],
106+
mapping({ segment, transaction }) {
107+
return (value) => {
108+
if (segment) {
109+
segment.addAttribute('http.statusText', value)
110+
} else {
111+
transaction.trace.attributes.addAttribute(DESTINATIONS.TRANS_COMMON, 'http.statusText', value)
112+
}
113+
}
114+
}
115+
},
116+
url: {
117+
attrs({ span }) {
118+
let value
119+
if (span.attributes[constants.ATTR_HTTP_URL]) {
120+
value = span.attributes[constants.ATTR_HTTP_URL]
121+
} else {
122+
const protocol = span.attributes[constants.ATTR_URL_SCHEME] ?? 'https'
123+
const host = span.attributes[constants.ATTR_SERVER_ADDRESS] ?? 'unknown'
124+
const port = span.attributes[constants.ATTR_SERVER_PORT]
125+
const path = span.attributes[constants.ATTR_URL_PATH] ?? '/unknown'
126+
const qp = span.attributes[constants.ATTR_URL_QUERY]
127+
value = `${protocol}://${host}`
128+
if (port) {
129+
value += `:${port}`
130+
}
131+
value += path
132+
if (qp) {
133+
value += qp
134+
}
135+
}
136+
137+
return value
138+
}
139+
}
140+
}
141+
142+
const { getAttr: httpAttr, attributesMapper } = createMapper(attrMappings)
143+
144+
function rpcMapper({ segment, transaction }) {
145+
const statusMapping = attributesMapper({ key: 'grpcStatusCode', segment, transaction })
146+
const systemMapping = attributesMapper({ key: 'rpcSystem', segment })
147+
const rpcMethodMapping = attributesMapper({ key: 'rpcMethod', transaction })
148+
return {
149+
...rpcMethodMapping,
150+
...statusMapping,
151+
...systemMapping
152+
}
153+
}
154+
155+
function serverMapper({ segment, transaction }) {
156+
// TODO: if route params are available, assign them as well
157+
const methodMapping = attributesMapper({ key: 'method', transaction })
158+
const statusCodeMapping = attributesMapper({ key: 'statusCode', transaction })
159+
const statusTextMapping = attributesMapper({ key: 'statusText', transaction })
160+
const serverPortMapping = attributesMapper({ key: 'port', segment })
161+
const httpRouteMapping = attributesMapper({ key: 'route', segment, transaction })
162+
const httpHostMapping = attributesMapper({ key: 'host', segment, transaction })
163+
return {
164+
...httpHostMapping,
165+
...httpRouteMapping,
166+
...methodMapping,
167+
...serverPortMapping,
168+
...statusCodeMapping,
169+
...statusTextMapping
170+
}
171+
}
172+
173+
function clientMapper({ segment }) {
174+
const statusCodeMapping = attributesMapper({ key: 'statusCode', segment })
175+
const statusTextMapping = attributesMapper({ key: 'statusText', segment })
176+
const clientHostMapping = attributesMapper({ key: 'clientHost', segment })
177+
const clientUrlMapping = attributesMapper({ key: 'clientUrl', segment })
178+
const clientPortMapping = attributesMapper({ key: 'clientPort', segment })
179+
return {
180+
...clientHostMapping,
181+
...clientPortMapping,
182+
...clientUrlMapping,
183+
...statusCodeMapping,
184+
...statusTextMapping
185+
}
186+
}
187+
188+
module.exports = {
189+
clientMapper,
190+
httpAttr,
191+
rpcMapper,
192+
serverMapper,
193+
}

0 commit comments

Comments
 (0)