Skip to content

fix: bugs with showing non otel spans (ex. clickhouse opentelemetry span logs) #789

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: v2
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions packages/api/src/models/source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export const Source = mongoose.model<ISource>(
spanKindExpression: String,
statusCodeExpression: String,
statusMessageExpression: String,
spanEventsValueExpression: String,

metricTables: {
type: {
Expand Down
4 changes: 2 additions & 2 deletions packages/app/src/components/DBRowDataPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
]
Expand Down
47 changes: 34 additions & 13 deletions packages/app/src/components/DBRowSidePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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 (
Expand Down Expand Up @@ -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,
Expand Down
22 changes: 20 additions & 2 deletions packages/app/src/components/SourceForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shows up very long and almost runs off the screen. Could we throw a maxwidth + text wrap on this element?

>
<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."
Expand Down
8 changes: 6 additions & 2 deletions packages/app/src/source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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 = {
Expand Down
2 changes: 1 addition & 1 deletion packages/common-utils/src/renderChartConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
};
Expand Down
1 change: 1 addition & 0 deletions packages/common-utils/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down