diff --git a/generated/attributes/client.md b/generated/attributes/client.md index 982bfc0..57c9a74 100644 --- a/generated/attributes/client.md +++ b/generated/attributes/client.md @@ -18,7 +18,7 @@ Client address - domain name if available without reverse DNS lookup; otherwise, | Has PII | false | | Exists in OpenTelemetry | Yes | | Example | `example.com` | -| Aliases | `http.client_id` | +| Aliases | `http.client_ip` | ### client.port diff --git a/generated/attributes/db.md b/generated/attributes/db.md index bb752b8..93240c8 100644 --- a/generated/attributes/db.md +++ b/generated/attributes/db.md @@ -32,7 +32,6 @@ The name of a collection (table, container) within the database. | Has PII | false | | Exists in OpenTelemetry | Yes | | Example | `users` | -| Aliases | `db.collection` | ### db.namespace diff --git a/generated/attributes/http.md b/generated/attributes/http.md index bda70d7..11fac6e 100644 --- a/generated/attributes/http.md +++ b/generated/attributes/http.md @@ -229,7 +229,7 @@ The encoded body size of the response (in bytes). | Has PII | false | | Exists in OpenTelemetry | Yes | | Example | `123` | -| Aliases | `http.response_content_length`, `http.response.header.content-length`, `http.response.header['content-length']` | +| Aliases | `http.response_content_length`, `http.response.header.content-length` | ### http.response.header.\ @@ -305,7 +305,7 @@ Client address - domain name if available without reverse DNS lookup; otherwise, | Has PII | false | | Exists in OpenTelemetry | Yes | | Example | `example.com` | -| Deprecated | Yes, use `http.client_ip` instead | +| Deprecated | Yes, use `client.address` instead | | Aliases | `client.address` | ### http.flavor @@ -359,7 +359,7 @@ The encoded body size of the response (in bytes). | Exists in OpenTelemetry | Yes | | Example | `123` | | Deprecated | Yes, use `http.response.body.size` instead | -| Aliases | `http.response.body.size`, `http.response.header.content-length`, `http.response.header['content-length']` | +| Aliases | `http.response.body.size`, `http.response.header.content-length` | ### http.response_transfer_size diff --git a/generated/attributes/sentry.md b/generated/attributes/sentry.md index b2645c9..a1b498e 100644 --- a/generated/attributes/sentry.md +++ b/generated/attributes/sentry.md @@ -130,7 +130,6 @@ A parameterized route for a function in Next.js that contributes to Server-Side | Has PII | false | | Exists in OpenTelemetry | No | | Example | `/posts/[id]/layout` | -| Aliases | `sentry.nextjs.function.route` | ### sentry.nextjs.ssr.function.type @@ -142,7 +141,6 @@ A descriptor for a for a function in Next.js that contributes to Server-Side Ren | Has PII | false | | Exists in OpenTelemetry | No | | Example | `generateMetadata` | -| Aliases | `sentry.nextjs.function.type` | ### sentry.op diff --git a/javascript/sentry-conventions/src/attributes.ts b/javascript/sentry-conventions/src/attributes.ts index c4348fe..4a6ef8c 100644 --- a/javascript/sentry-conventions/src/attributes.ts +++ b/javascript/sentry-conventions/src/attributes.ts @@ -979,7 +979,7 @@ export type CHANNEL_TYPE = string; * * Attribute defined in OTEL: Yes * - * Aliases: {@link HTTP_CLIENT_ID} `http.client_id` + * Aliases: {@link HTTP_CLIENT_IP} `http.client_ip` * * @example "example.com" */ @@ -1237,8 +1237,6 @@ export type CODE_NAMESPACE_TYPE = string; * * Attribute defined in OTEL: Yes * - * Aliases: {@link DB_COLLECTION} `db.collection` - * * @example "users" */ export const DB_COLLECTION_NAME = 'db.collection.name'; @@ -2908,7 +2906,7 @@ export type GRAPHQL_OPERATION_TYPE_TYPE = string; * * Aliases: {@link CLIENT_ADDRESS} `client.address` * - * @deprecated Use {@link HTTP_CLIENT_IP} (http.client_ip) instead + * @deprecated Use {@link CLIENT_ADDRESS} (client.address) instead * @example "example.com" */ export const HTTP_CLIENT_IP = 'http.client_ip'; @@ -3322,7 +3320,7 @@ export type HTTP_REQUEST_SECURE_CONNECTION_START_TYPE = number; * * Attribute defined in OTEL: Yes * - * Aliases: {@link HTTP_RESPONSE_CONTENT_LENGTH} `http.response_content_length`, {@link HTTP_RESPONSE_HEADER_CONTENT_LENGTH} `http.response.header.content-length`, {@link HTTP_RESPONSE_HEADER['CONTENT_LENGTH']} `http.response.header['content-length']` + * Aliases: {@link HTTP_RESPONSE_CONTENT_LENGTH} `http.response_content_length`, {@link HTTP_RESPONSE_HEADER_CONTENT_LENGTH} `http.response.header.content-length` * * @example 123 */ @@ -3432,7 +3430,7 @@ export type HTTP_RESPONSE_STATUS_CODE_TYPE = number; * * Attribute defined in OTEL: Yes * - * Aliases: {@link HTTP_RESPONSE_BODY_SIZE} `http.response.body.size`, {@link HTTP_RESPONSE_HEADER_CONTENT_LENGTH} `http.response.header.content-length`, {@link HTTP_RESPONSE_HEADER['CONTENT_LENGTH']} `http.response.header['content-length']` + * Aliases: {@link HTTP_RESPONSE_BODY_SIZE} `http.response.body.size`, {@link HTTP_RESPONSE_HEADER_CONTENT_LENGTH} `http.response.header.content-length` * * @deprecated Use {@link HTTP_RESPONSE_BODY_SIZE} (http.response.body.size) instead * @example 123 @@ -5389,8 +5387,6 @@ export type SENTRY_MODULE_KEY_TYPE = string; * * Attribute defined in OTEL: No * - * Aliases: {@link SENTRY_NEXTJS_FUNCTION_ROUTE} `sentry.nextjs.function.route` - * * @example "/posts/[id]/layout" */ export const SENTRY_NEXTJS_SSR_FUNCTION_ROUTE = 'sentry.nextjs.ssr.function.route'; @@ -5411,8 +5407,6 @@ export type SENTRY_NEXTJS_SSR_FUNCTION_ROUTE_TYPE = string; * * Attribute defined in OTEL: No * - * Aliases: {@link SENTRY_NEXTJS_FUNCTION_TYPE} `sentry.nextjs.function.type` - * * @example "generateMetadata" */ export const SENTRY_NEXTJS_SSR_FUNCTION_TYPE = 'sentry.nextjs.ssr.function.type'; diff --git a/model/attributes/client/client__address.json b/model/attributes/client/client__address.json index 693bee5..7c3dd36 100644 --- a/model/attributes/client/client__address.json +++ b/model/attributes/client/client__address.json @@ -7,5 +7,5 @@ }, "is_in_otel": true, "example": "example.com", - "alias": ["http.client_id"] + "alias": ["http.client_ip"] } diff --git a/model/attributes/db/db__collection__name.json b/model/attributes/db/db__collection__name.json index 4409d98..ea59b3b 100644 --- a/model/attributes/db/db__collection__name.json +++ b/model/attributes/db/db__collection__name.json @@ -6,6 +6,5 @@ "key": "false" }, "is_in_otel": true, - "example": "users", - "alias": ["db.collection"] + "example": "users" } diff --git a/model/attributes/http/http__client_ip.json b/model/attributes/http/http__client_ip.json index c1c3863..a12c642 100644 --- a/model/attributes/http/http__client_ip.json +++ b/model/attributes/http/http__client_ip.json @@ -9,7 +9,7 @@ "example": "example.com", "deprecation": { "_status": null, - "replacement": "http.client_ip" + "replacement": "client.address" }, "alias": ["client.address"] } diff --git a/model/attributes/http/http__response__body__size.json b/model/attributes/http/http__response__body__size.json index 083885a..a125e4f 100644 --- a/model/attributes/http/http__response__body__size.json +++ b/model/attributes/http/http__response__body__size.json @@ -7,9 +7,5 @@ }, "is_in_otel": true, "example": 123, - "alias": [ - "http.response_content_length", - "http.response.header.content-length", - "http.response.header['content-length']" - ] + "alias": ["http.response_content_length", "http.response.header.content-length"] } diff --git a/model/attributes/http/http__response_content_length.json b/model/attributes/http/http__response_content_length.json index 0507ad9..5e1930d 100644 --- a/model/attributes/http/http__response_content_length.json +++ b/model/attributes/http/http__response_content_length.json @@ -11,5 +11,5 @@ "_status": "backfill", "replacement": "http.response.body.size" }, - "alias": ["http.response.body.size", "http.response.header.content-length", "http.response.header['content-length']"] + "alias": ["http.response.body.size", "http.response.header.content-length"] } diff --git a/model/attributes/sentry/sentry__nextjs__ssr__function__route.json b/model/attributes/sentry/sentry__nextjs__ssr__function__route.json index bdfc0d6..d88bf18 100644 --- a/model/attributes/sentry/sentry__nextjs__ssr__function__route.json +++ b/model/attributes/sentry/sentry__nextjs__ssr__function__route.json @@ -7,6 +7,5 @@ }, "is_in_otel": false, "example": "/posts/[id]/layout", - "alias": ["sentry.nextjs.function.route"], "sdks": ["javascript"] } diff --git a/model/attributes/sentry/sentry__nextjs__ssr__function__type.json b/model/attributes/sentry/sentry__nextjs__ssr__function__type.json index 0d43d72..fbf8295 100644 --- a/model/attributes/sentry/sentry__nextjs__ssr__function__type.json +++ b/model/attributes/sentry/sentry__nextjs__ssr__function__type.json @@ -7,6 +7,5 @@ }, "is_in_otel": false, "example": "generateMetadata", - "alias": ["sentry.nextjs.function.type"], "sdks": ["javascript"] } diff --git a/scripts/generate_attribute_docs.ts b/scripts/generate_attribute_docs.ts index e98341e..8f4f76e 100644 --- a/scripts/generate_attribute_docs.ts +++ b/scripts/generate_attribute_docs.ts @@ -1,24 +1,7 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; -interface AttributeJson { - key: string; - brief: string; - has_dynamic_suffix?: boolean; - type: 'string' | 'boolean' | 'integer' | 'double' | 'string[]' | 'boolean[]' | 'integer[]' | 'double[]'; - pii: { - key: 'true' | 'maybe' | 'false'; - reason?: string; - }; - is_in_otel: boolean; - example?: string | boolean | number | string[] | boolean[] | number[]; - deprecation?: { - replacement: string; - reason?: string; - }; - alias?: string[]; - sdks?: string[]; -} +import type { AttributeJson } from './types'; // Function to read and parse a JSON file function readJsonFile(filePath: string): AttributeJson { diff --git a/scripts/generate_attributes.ts b/scripts/generate_attributes.ts index 785b418..6e9c1e7 100644 --- a/scripts/generate_attributes.ts +++ b/scripts/generate_attributes.ts @@ -1,24 +1,6 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; - -interface AttributeJson { - key: string; - brief: string; - has_dynamic_suffix?: boolean; - type: 'string' | 'boolean' | 'integer' | 'double' | 'string[]' | 'boolean[]' | 'integer[]' | 'double[]'; - pii: { - key: 'true' | 'maybe' | 'false'; - reason?: string; - }; - is_in_otel: boolean; - example?: string | boolean | number | string[] | boolean[] | number[]; - deprecation?: { - replacement: string; - reason?: string; - }; - alias?: string[]; - sdks?: string[]; -} +import type { AttributeJson } from './types'; export async function generateAttributes() { const attributesDir = path.join(__dirname, '..', 'model', 'attributes'); diff --git a/scripts/generate_deprecated_attributes_json.ts b/scripts/generate_deprecated_attributes_json.ts index feb66c1..24c5814 100644 --- a/scripts/generate_deprecated_attributes_json.ts +++ b/scripts/generate_deprecated_attributes_json.ts @@ -1,26 +1,7 @@ import { execSync } from 'node:child_process'; import * as fs from 'node:fs'; import * as path from 'node:path'; - -interface AttributeJson { - key: string; - brief: string; - has_dynamic_suffix?: boolean; - type: 'string' | 'boolean' | 'integer' | 'double' | 'string[]' | 'boolean[]' | 'integer[]' | 'double[]'; - pii: { - key: 'true' | 'maybe' | 'false'; - reason?: string; - }; - is_in_otel: boolean; - example?: string | boolean | number | string[] | boolean[] | number[]; - deprecation?: { - replacement: string; - reason?: string; - _status?: string; - }; - alias?: string[]; - sdks?: string[]; -} +import type { AttributeJson } from './types'; // Function to read and parse a JSON file function readJsonFile(filePath: string): AttributeJson { diff --git a/scripts/types.ts b/scripts/types.ts new file mode 100644 index 0000000..1a84855 --- /dev/null +++ b/scripts/types.ts @@ -0,0 +1,19 @@ +export interface AttributeJson { + key: string; + brief: string; + has_dynamic_suffix?: boolean; + type: 'string' | 'boolean' | 'integer' | 'double' | 'string[]' | 'boolean[]' | 'integer[]' | 'double[]'; + pii: { + key: 'true' | 'maybe' | 'false'; + reason?: string; + }; + is_in_otel: boolean; + example?: string | boolean | number | string[] | boolean[] | number[]; + deprecation?: { + replacement: string; + reason?: string; + _status?: string; + }; + alias?: string[]; + sdks?: string[]; +} diff --git a/scripts/utils.ts b/scripts/utils.ts new file mode 100644 index 0000000..a929e89 --- /dev/null +++ b/scripts/utils.ts @@ -0,0 +1,10 @@ +export function attributeKeyToFileName(key: string): string { + return key.replace('', '[key]').split('.').join('__').concat('.json'); +} + +export function fileNameToAttributeKey(fileName: string): string { + return fileName + .replace(/\.json$/, '') + .replaceAll('__', '.') + .replace('[key]', ''); +} diff --git a/shared/deprecated_attributes.json b/shared/deprecated_attributes.json index 2b7b2a1..51a88b4 100644 --- a/shared/deprecated_attributes.json +++ b/shared/deprecated_attributes.json @@ -608,7 +608,7 @@ "example": "example.com", "deprecation": { "_status": null, - "replacement": "http.client_ip" + "replacement": "client.address" }, "alias": ["client.address"] }, @@ -671,11 +671,7 @@ "_status": "backfill", "replacement": "http.response.body.size" }, - "alias": [ - "http.response.body.size", - "http.response.header.content-length", - "http.response.header['content-length']" - ] + "alias": ["http.response.body.size", "http.response.header.content-length"] }, { "key": "http.response_transfer_size", diff --git a/test/attributes.test.ts b/test/attributes.test.ts index a517aef..e030988 100644 --- a/test/attributes.test.ts +++ b/test/attributes.test.ts @@ -5,6 +5,8 @@ import Ajv from 'ajv'; import { describe, expect, it } from 'vitest'; import schema from '../schemas/attribute.schema.json'; +import type { AttributeJson } from '../scripts/types'; +import { attributeKeyToFileName, fileNameToAttributeKey } from '../scripts/utils'; const traceFolders = path.resolve(__dirname, '../model/attributes'); @@ -15,7 +17,7 @@ describe('attribute json', async () => { for (const file of files) { const name = path.basename(file); describe(name, async () => { - const content = JSON.parse(await fs.promises.readFile(file, 'utf-8')); + const content: AttributeJson = JSON.parse(await fs.promises.readFile(file, 'utf-8')); it('should follow the attribute json schema', () => { const ajv = new Ajv(); @@ -35,6 +37,9 @@ describe('attribute json', async () => { return; } + if (!content.example) { + return; + } switch (content.type) { case 'integer': case 'double': @@ -43,21 +48,21 @@ describe('attribute json', async () => { case 'integer[]': case 'double[]': expect(Array.isArray(content.example)).toBe(true); - expect(content.example.every((e: number) => typeof e === 'number')).toBe(true); + expect((content.example as number[]).every((e: number) => typeof e === 'number')).toBe(true); break; case 'string': expect(typeof content.example).toBe('string'); break; case 'string[]': expect(Array.isArray(content.example)).toBe(true); - expect(content.example.every((e: string) => typeof e === 'string')).toBe(true); + expect((content.example as string[]).every((e: string) => typeof e === 'string')).toBe(true); break; case 'boolean': expect(typeof content.example).toBe('boolean'); break; case 'boolean[]': expect(Array.isArray(content.example)).toBe(true); - expect(content.example.every((e: boolean) => typeof e === 'boolean')).toBe(true); + expect((content.example as boolean[]).every((e: boolean) => typeof e === 'boolean')).toBe(true); break; default: throw new Error('Invalid type'); @@ -65,9 +70,60 @@ describe('attribute json', async () => { }); it('should follow the correct naming convention', () => { - expect(name.replaceAll('__', '.').replaceAll('[', '<').replaceAll(']', '>').replace('.json', '')).toMatch( - content.key, - ); + expect(fileNameToAttributeKey(name)).toMatch(content.key); + }); + + it('its replacement should exist', async () => { + if (!content.deprecation?.replacement) { + return; + } + const replacement = content.deprecation?.replacement; + const replacementFileName = attributeKeyToFileName(replacement); + let replacementFilePath: string; + + if (replacement.includes('.')) { + const namespace = replacement.split('.')[0] as string; + replacementFilePath = path.join(traceFolders, namespace, replacementFileName); + } else { + replacementFilePath = path.join(traceFolders, replacementFileName); + } + + const replacementExists = await fs.promises + .access(replacementFilePath, fs.constants.F_OK) + .then(() => true) + .catch(() => false); + expect(replacementExists); + }); + + it('all of its aliases should exist', async () => { + if (!content.alias || content.alias.length === 0) { + return; + } + + const missingAliases: string[] = []; + + for (const alias of content.alias) { + const aliasFileName = attributeKeyToFileName(alias); + let aliasFilePath: string; + + if (alias.includes('.')) { + const namespace = alias.split('.')[0] as string; + aliasFilePath = path.join(traceFolders, namespace, aliasFileName); + } else { + aliasFilePath = path.join(traceFolders, aliasFileName); + } + + const aliasExists = await fs.promises + .access(aliasFilePath, fs.constants.F_OK) + .then(() => true) + .catch(() => false); + + if (!aliasExists) { + missingAliases.push(alias); + } + } + + expect(missingAliases).toEqual([]); }); }); }