Skip to content

Commit 36ecbdc

Browse files
authored
feat(otlp): Parse JSON-looking attributes in span attributes panel (#95544)
Until EAP supports array storage, we are storing array attributes as JSON-encoded strings. Since we can't know all array attribute names ahead of time, scan through the attributes and look for any value that _seems_ like it'll be a JSON array, and render it using the JSON renderer if appropriate. Closes OPE-51. **e.g.,** <img width="849" height="214" alt="Screenshot 2025-07-15 at 10 11 53 AM" src="https://github.com/user-attachments/assets/0cf9eae3-e7c5-4c96-873e-c0e8a074cf96" />
1 parent 2bf850f commit 36ecbdc

File tree

3 files changed

+51
-0
lines changed

3 files changed

+51
-0
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import {looksLikeAJSONArray} from './looksLikeAJSONArray';
2+
3+
describe('looksLikeAJSONArray', () => {
4+
it.each([
5+
['[]', true],
6+
['[1, 2, 3]', true],
7+
['["hello", "world"]', true],
8+
['[{"key": "value"}]', true],
9+
[' [] ', true],
10+
['[Filtered]', false],
11+
['{}', false],
12+
['{"key": "value"}', false],
13+
['hello world', false],
14+
['[incomplete', false],
15+
['incomplete]', false],
16+
['', false],
17+
])('"%s" should return %s', (input, expected) => {
18+
expect(looksLikeAJSONArray(input)).toBe(expected);
19+
});
20+
});
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/**
2+
* Check if a string value looks like it could be a JSON-encoded array. This is
3+
useful for situations where we used JSON strings to store array values because
4+
array storage was not possible.
5+
*
6+
* This function does _not_ parse the string as JSON because in most cases we
7+
have to decode the string anyway to render it, and we don't want to decode
8+
twice. Instead, the renderer gracefully fails if the JSON is not valid.
9+
*/
10+
export function looksLikeAJSONArray(value: string) {
11+
const trimmedValue = value.trim();
12+
13+
// The string '[Filtered]' looks array-like, but it's actually a Relay special string
14+
if (trimmedValue === '[Filtered]') return false;
15+
return trimmedValue.startsWith('[') && trimmedValue.endsWith(']');
16+
}

static/app/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/attributes.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {trackAnalytics} from 'sentry/utils/analytics';
1414
import type {RenderFunctionBaggage} from 'sentry/utils/discover/fieldRenderers';
1515
import {FieldKey} from 'sentry/utils/fields';
1616
import {generateProfileFlamechartRoute} from 'sentry/utils/profiling/routes';
17+
import {looksLikeAJSONArray} from 'sentry/utils/string/looksLikeAJSONArray';
1718
import {useIsSentryEmployee} from 'sentry/utils/useIsSentryEmployee';
1819
import type {AttributesFieldRendererProps} from 'sentry/views/explore/components/traceItemAttributes/attributesTree';
1920
import {AttributesTree} from 'sentry/views/explore/components/traceItemAttributes/attributesTree';
@@ -187,10 +188,24 @@ export function Attributes({
187188
},
188189
};
189190

191+
// Some semantic attributes are known to contain JSON-encoded arrays or
192+
// JSON-encoded objects. Add a JSON renderer for those attributes.
190193
for (const attribute of JSON_ATTRIBUTES) {
191194
customRenderers[attribute] = jsonRenderer;
192195
}
193196

197+
// Some attributes (semantic or otherwise) look like they contain JSON-encoded
198+
// arrays. Use a JSON renderer for any value that looks suspiciously like it's
199+
// a JSON-encoded array. NOTE: This happens a lot because EAP doesn't support
200+
// array values, so SDKs often store array values as JSON-encoded strings.
201+
sortedAndFilteredAttributes.forEach(attribute => {
202+
if (Object.hasOwn(customRenderers, attribute.name)) return;
203+
if (attribute.type !== 'str') return;
204+
if (!looksLikeAJSONArray(attribute.value)) return;
205+
206+
customRenderers[attribute.name] = jsonRenderer;
207+
});
208+
194209
for (const attribute of TRUNCATED_TEXT_ATTRIBUTES) {
195210
customRenderers[attribute] = truncatedTextRenderer;
196211
}

0 commit comments

Comments
 (0)