diff --git a/packages/core/src/v3/utils/flattenAttributes.ts b/packages/core/src/v3/utils/flattenAttributes.ts index 545b0184e6..57e291a693 100644 --- a/packages/core/src/v3/utils/flattenAttributes.ts +++ b/packages/core/src/v3/utils/flattenAttributes.ts @@ -3,6 +3,10 @@ import { Attributes } from "@opentelemetry/api"; export const NULL_SENTINEL = "$@null(("; export const CIRCULAR_REFERENCE_SENTINEL = "$@circular(("; +function escapeKey(key: string): string { + return key.replace(/\./g, '\\.'); +} + export function flattenAttributes( obj: Record | Array | string | boolean | number | null | undefined, prefix?: string , @@ -53,7 +57,8 @@ export function flattenAttributes( for (const [key, value] of Object.entries(obj)) { - const newPrefix = `${prefix ? `${prefix}.` : ""}${Array.isArray(obj) ? `[${key}]` : key}`; + const escapedKey = escapeKey(key); + const newPrefix = `${prefix ? `${prefix}.` : ""}${Array.isArray(obj) ? `[${escapedKey}]` : escapedKey}`; if (Array.isArray(value)) { for (let i = 0; i < value.length; i++) { if (typeof value[i] === "object" && value[i] !== null) { @@ -86,6 +91,10 @@ function isRecord(value: unknown): value is Record { return value !== null && typeof value === "object" && !Array.isArray(value); } +function unescapeKey(key: string): string { + return key.replace(/\\\./g, '.'); +} + export function unflattenAttributes( obj: Attributes ): Record | string | number | boolean | null | undefined { @@ -109,24 +118,21 @@ export function unflattenAttributes( const result: Record = {}; for (const [key, value] of Object.entries(obj)) { - const parts = key.split(".").reduce( - (acc, part) => { - if (part.startsWith("[") && part.endsWith("]")) { - // Handle array indices more precisely - const match = part.match(/^\[(\d+)\]$/); - if (match && match[1]) { - acc.push(parseInt(match[1])); - } else { - // Remove brackets for non-numeric array keys - acc.push(part.slice(1, -1)); - } - } else { - acc.push(part); - } - return acc; - }, - [] as (string | number)[] - ); + const parts = key + .split('.') // Split by all dots + .reduce((acc, part, i, arr) => { + // If the previous part ends with an odd number of backslashes, + // the dot is escaped and should be part of the last segment. + if (i > 0 && arr[i - 1].match(/\\+$/)?.[0]?.length % 2 === 1) { + acc[acc.length - 1] += '.' + part; + } else { + acc.push(part); + } + return acc; + }, [] as (string | number)[]) + .map(unescapeKey); // Unescape keys + } + let current: any = result; for (let i = 0; i < parts.length - 1; i++) {