mirror of
https://github.com/hyperdxio/hyperdx
synced 2026-04-21 13:37:15 +00:00
fix: bugs with showing non otel spans (ex. clickhouse opentelemetry span logs) (#789)
<img width="1645" alt="image" src="https://github.com/user-attachments/assets/2f7eb93f-9648-4c98-8bfd-a2d0f65be9d5" /> fixing a few bugs that prevented us from properly rendering trace view for `system.opentelemetry_span_log` fix HDX-1676 Co-authored-by: Warren <5959690+wrn14897@users.noreply.github.com>
This commit is contained in:
parent
1674ab8672
commit
931d7387d9
8 changed files with 72 additions and 20 deletions
7
.changeset/sour-gifts-thank.md
Normal file
7
.changeset/sour-gifts-thank.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
"@hyperdx/common-utils": patch
|
||||
"@hyperdx/api": patch
|
||||
"@hyperdx/app": patch
|
||||
---
|
||||
|
||||
fix: bugs with showing non otel spans (ex. clickhouse opentelemetry span logs)
|
||||
|
|
@ -63,6 +63,7 @@ export const Source = mongoose.model<ISource>(
|
|||
spanKindExpression: String,
|
||||
statusCodeExpression: String,
|
||||
statusMessageExpression: String,
|
||||
spanEventsValueExpression: String,
|
||||
|
||||
metricTables: {
|
||||
type: {
|
||||
|
|
|
|||
|
|
@ -82,10 +82,10 @@ export function useRowData({
|
|||
},
|
||||
]
|
||||
: []),
|
||||
...(source.kind === SourceKind.Trace
|
||||
...(source.kind === SourceKind.Trace && source.spanEventsValueExpression
|
||||
? [
|
||||
{
|
||||
valueExpression: `Events.Attributes[indexOf(Events.Name, 'exception')]`,
|
||||
valueExpression: `${source.spanEventsValueExpression}.Attributes[indexOf(${source.spanEventsValueExpression}.Name, 'exception')]`,
|
||||
alias: '__hdx_events_exception_attributes',
|
||||
},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -94,9 +94,21 @@ export default function DBRowSidePanel({
|
|||
Infrastructure = 'infrastructure',
|
||||
}
|
||||
|
||||
const hasOverviewPanel = useMemo(() => {
|
||||
if (
|
||||
source.resourceAttributesExpression ||
|
||||
source.eventAttributesExpression
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, [source.eventAttributesExpression, source.resourceAttributesExpression]);
|
||||
|
||||
const defaultTab = hasOverviewPanel ? Tab.Overview : Tab.Parsed;
|
||||
|
||||
const [queryTab, setQueryTab] = useQueryState(
|
||||
'tab',
|
||||
parseAsStringEnum<Tab>(Object.values(Tab)).withDefault(Tab.Overview),
|
||||
parseAsStringEnum<Tab>(Object.values(Tab)).withDefault(defaultTab),
|
||||
);
|
||||
|
||||
const initialWidth = 80;
|
||||
|
|
@ -114,7 +126,7 @@ export default function DBRowSidePanel({
|
|||
// Keep track of sub-drawers so we can disable closing this root drawer
|
||||
const [subDrawerOpen, setSubDrawerOpen] = useState(false);
|
||||
|
||||
const [stateTab, setStateTab] = useState<Tab>(Tab.Overview);
|
||||
const [stateTab, setStateTab] = useState<Tab>(defaultTab);
|
||||
// Nested panels can't share the query param or else they'll conflict, so we'll use local state for nested panels
|
||||
// We'll need to handle this properly eventually...
|
||||
const tab = isNestedPanel ? stateTab : queryTab;
|
||||
|
|
@ -211,15 +223,20 @@ export default function DBRowSidePanel({
|
|||
});
|
||||
|
||||
const hasK8sContext = useMemo(() => {
|
||||
if (!source?.resourceAttributesExpression || !normalizedRow) {
|
||||
try {
|
||||
if (!source?.resourceAttributesExpression || !normalizedRow) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
normalizedRow[source.resourceAttributesExpression]?.['k8s.pod.uid'] !=
|
||||
null ||
|
||||
normalizedRow[source.resourceAttributesExpression]?.['k8s.node.name'] !=
|
||||
null
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
normalizedRow[source.resourceAttributesExpression]['k8s.pod.uid'] !=
|
||||
null ||
|
||||
normalizedRow[source.resourceAttributesExpression]['k8s.node.name'] !=
|
||||
null
|
||||
);
|
||||
}, [source, normalizedRow]);
|
||||
|
||||
return (
|
||||
|
|
@ -264,10 +281,14 @@ export default function DBRowSidePanel({
|
|||
<TabBar
|
||||
className="fs-8 mt-2"
|
||||
items={[
|
||||
{
|
||||
text: 'Overview',
|
||||
value: Tab.Overview,
|
||||
},
|
||||
...(hasOverviewPanel
|
||||
? [
|
||||
{
|
||||
text: 'Overview',
|
||||
value: Tab.Overview,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
text: 'Column Values',
|
||||
value: Tab.Parsed,
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ function FormRow({
|
|||
...(!helpText ? { opacity: 0, pointerEvents: 'none' } : {}),
|
||||
}}
|
||||
>
|
||||
<Tooltip label={helpText} color="dark" c="white">
|
||||
<Tooltip label={helpText} color="dark" c="white" multiline maw={600}>
|
||||
<i className="bi bi-question-circle cursor-pointer" />
|
||||
</Tooltip>
|
||||
</Text>
|
||||
|
|
@ -413,7 +413,10 @@ export function TraceTableModelForm({
|
|||
rules={{ required: 'Table is required' }}
|
||||
/>
|
||||
</FormRow>
|
||||
<FormRow label={'Timestamp Column'}>
|
||||
<FormRow
|
||||
label={'Timestamp Column'}
|
||||
helpText="DateTime column or expression defines the start of the span"
|
||||
>
|
||||
<SQLInlineEditorControlled
|
||||
tableConnections={{
|
||||
databaseName,
|
||||
|
|
@ -619,6 +622,21 @@ export function TraceTableModelForm({
|
|||
placeholder="SpanAttributes"
|
||||
/>
|
||||
</FormRow>
|
||||
<FormRow
|
||||
label={'Span Events Expression'}
|
||||
helpText="Expression to extract span events. Used to capture events associated with spans. Expected to be Nested ( Timestamp DateTime64(9), Name LowCardinality(String), Attributes Map(LowCardinality(String), String)"
|
||||
>
|
||||
<SQLInlineEditorControlled
|
||||
tableConnections={{
|
||||
databaseName,
|
||||
tableName,
|
||||
connectionId,
|
||||
}}
|
||||
control={control}
|
||||
name="spanEventsValueExpression"
|
||||
placeholder="Events"
|
||||
/>
|
||||
</FormRow>
|
||||
<FormRow
|
||||
label={'Implicit Column Expression'}
|
||||
helpText="Column used for full text search if no property is specified in a Lucene-based search. Typically the message body of a log."
|
||||
|
|
|
|||
|
|
@ -246,6 +246,9 @@ export async function inferTableSourceConfig({
|
|||
'StatusMessage',
|
||||
]);
|
||||
|
||||
// Check if SpanEvents column is available
|
||||
const hasSpanEvents = columns.some(col => col.name === 'Events');
|
||||
|
||||
const timestampColumns = filterColumnMetaByType(columns, [JSDataType.Date]);
|
||||
const primaryKeyTimestampColumn = timestampColumns?.find(c =>
|
||||
keys.find(
|
||||
|
|
@ -299,17 +302,18 @@ export async function inferTableSourceConfig({
|
|||
traceIdExpression: 'TraceId',
|
||||
statusCodeExpression: 'StatusCode',
|
||||
statusMessageExpression: 'StatusMessage',
|
||||
...(hasSpanEvents ? { spanEventsValueExpression: 'Events' } : {}),
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
|
||||
export function getDurationMsExpression(source: TSource) {
|
||||
return `${source.durationExpression}/1e${(source.durationPrecision ?? 9) - 3}`;
|
||||
return `(${source.durationExpression})/1e${(source.durationPrecision ?? 9) - 3}`;
|
||||
}
|
||||
|
||||
export function getDurationSecondsExpression(source: TSource) {
|
||||
return `${source.durationExpression}/1e${source.durationPrecision ?? 9}`;
|
||||
return `(${source.durationExpression})/1e${source.durationPrecision ?? 9}`;
|
||||
}
|
||||
|
||||
const ReqMetricTableColumns = {
|
||||
|
|
|
|||
|
|
@ -251,7 +251,7 @@ const fastifySQL = ({
|
|||
|
||||
return parser.sqlify(ast);
|
||||
} catch (e) {
|
||||
console.error('[renderWhereExpression]feat: Failed to parse SQL AST', e);
|
||||
console.debug('[renderWhereExpression]feat: Failed to parse SQL AST', e);
|
||||
return rawSQL;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -510,6 +510,7 @@ export const SourceSchema = z.object({
|
|||
durationPrecision: z.number().min(0).max(9).optional(),
|
||||
parentSpanIdExpression: z.string().optional(),
|
||||
spanNameExpression: z.string().optional(),
|
||||
spanEventsValueExpression: z.string().optional(),
|
||||
|
||||
spanKindExpression: z.string().optional(),
|
||||
statusCodeExpression: z.string().optional(),
|
||||
|
|
|
|||
Loading…
Reference in a new issue