Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/otel-semconv-http.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hyperdx/app': patch
---

fix: Read current OpenTelemetry HTTP and RPC semantic conventions (with deprecated names as fallbacks) in the span side panel and trace waterfall: redesign the HTTP panel around a request line and details, add a gRPC/RPC call panel, and flag correlated log errors and warnings by SeverityNumber or case-insensitive SeverityText
34 changes: 7 additions & 27 deletions packages/app/src/LogSidePanelElements.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { ColumnDef, Row, Table } from '@tanstack/react-table';
import HyperJson from './components/HyperJson';
import { StacktraceFrame as StacktraceFrameCmp } from './components/StacktraceFrame';
import { TableCellButton } from './components/Table';
import type { HttpHeader } from './utils/httpHeaders';
import { UNDEFINED_WIDTH } from './tableUtils';
import type { StacktraceBreadcrumb, StacktraceFrame } from './types';
import { FormatTime } from './useFormatTime';
Expand Down Expand Up @@ -375,48 +376,27 @@ const useShowMoreRows = <T extends object>({
/**
* Request / Response Headers elements
*/
export const headerColumns: ColumnDef<[string, string]>[] = [
export const headerColumns: ColumnDef<HttpHeader>[] = [
{
accessorKey: '0',
accessorKey: 'name',
header: 'Header',
size: 260,
cell: ({ row }) => (
<div className="text-truncate" title={row.original[0]}>
{row.original[0]}
<div className="text-truncate" title={row.original.attributeKey}>
{row.original.name}
</div>
),
},
{
size: UNDEFINED_WIDTH,
accessorKey: '1',
accessorKey: 'value',
header: 'Value',
},
];

/**
* Network Subpanel
*/
export const networkColumns: ColumnDef<{
label: string;
value: string;
className?: string;
}>[] = [
{
accessorKey: 'label',
header: 'Label',
size: 260,
cell: ({ row }) => <span>{row.original.label}</span>,
},
{
size: UNDEFINED_WIDTH,
accessorKey: 'value',
header: 'Value',
cell: ({ row }) => (
<span className={row.original.className}>{row.original.value}</span>
),
},
];

export const NetworkBody = ({
body,
theme,
Expand Down Expand Up @@ -461,7 +441,7 @@ export const NetworkBody = ({
<>
{body != null && body != '' ? (
<pre
className="m-0 px-4 py-3"
className={parsedBody ? 'm-0' : 'm-0 px-4 py-3'}
style={{
wordBreak: 'break-all',
wordWrap: 'break-word',
Expand Down
19 changes: 19 additions & 0 deletions packages/app/src/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
formatNumber,
getAllMetricTables,
getColorFromCSSToken,
getLogSeverityLevel,
getMetricTableName,
isColumnInSelect,
mapKeyBy,
Expand Down Expand Up @@ -1535,6 +1536,24 @@ describe('orderByAfterRemovingSelectItem', () => {
});
});

describe('getLogSeverityLevel', () => {
it.each([
[12, 'error', undefined],
[13, undefined, 'warn'],
[16, 'error', 'warn'],
[17, 'info', 'error'],
['17', undefined, 'error'],
[0, 'ERROR', 'error'],
[undefined, 'Warning', 'warn'],
['', 'fatal', 'error'],
[undefined, 'info', undefined],
])('maps %p and %p to %p', (severityNumber, severityText, expected) => {
expect(getLogSeverityLevel({ severityNumber, severityText })).toBe(
expected,
);
});
});

describe('getColorFromCSSToken', () => {
afterEach(() => {
jest.restoreAllMocks();
Expand Down
33 changes: 10 additions & 23 deletions packages/app/src/components/DBRowOverviewPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { WithClause } from '@/hooks/useRowWhere';
import { getEventBody } from '@/source';
import { getHighlightedAttributesFromData } from '@/utils/highlightedAttributes';
import { resolveRowTimestampAnchor } from '@/utils/rowTimestamps';
import { getRequestPanelTitle } from '@/utils/rpcSpanAttributes';

import {
getJSONColumnNames,
Expand Down Expand Up @@ -134,25 +135,10 @@ export function RowOverviewPanel({
[generateSearchUrl],
);

const isHttpRequest = useMemo(() => {
const attributes =
eventAttributesExpr && dataAttributes?.[eventAttributesExpr];
return attributes?.['http.url'] != null;
}, [dataAttributes, eventAttributesExpr]);

const filteredEventAttributes = useMemo(() => {
if (!eventAttributesExpr) return dataAttributes;

const attributes = dataAttributes?.[eventAttributesExpr];
return isHttpRequest && attributes
? {
[eventAttributesExpr]: pickBy(
attributes,
(_, key) => !key.startsWith('http.'),
),
}
: dataAttributes;
}, [dataAttributes, isHttpRequest, eventAttributesExpr]);
const requestPanelTitle = useMemo(
() => getRequestPanelTitle(flattenedEventAttributes),
[flattenedEventAttributes],
);

const exceptionValues = useMemo(() => {
const parsedEvents =
Expand Down Expand Up @@ -266,17 +252,18 @@ export function RowOverviewPanel({
multiple
variant="noPadding"
>
{isHttpRequest && (
{requestPanelTitle != null && (
<Accordion.Item value="network">
<Accordion.Control>
<Text size="sm" ps={contentPx}>
HTTP Request
{requestPanelTitle}
</Text>
</Accordion.Control>
<Accordion.Panel>
<Box px={contentPx}>
<NetworkPropertySubpanel
eventAttributes={flattenedEventAttributes}
spanKind={firstRow?.__hdx_span_kind}
/>
</Box>
</Accordion.Panel>
Expand Down Expand Up @@ -323,7 +310,7 @@ export function RowOverviewPanel({
</Accordion.Item>
)}

{Object.keys(filteredEventAttributes).length > 0 && (
{Object.keys(dataAttributes).length > 0 && (
<Accordion.Item value="eventAttributes">
<Accordion.Control>
<Text size="sm" ps={contentPx}>
Expand All @@ -333,7 +320,7 @@ export function RowOverviewPanel({
<Accordion.Panel>
<Box px={contentPx}>
<DBRowJsonViewer
data={filteredEventAttributes}
data={dataAttributes}
jsonColumns={jsonColumns}
mapColumns={mapColumns}
/>
Expand Down
Loading
Loading