Tempo: Add Victoria Logs support for "traces to logs" (#105985)
* feat(trace-to-logs): add VictoriaLogs datasource support Signed-off-by: Yury Molodov <yurymolodov@gmail.com> * tempo: fix lint errors in createSpanLink.tsx Signed-off-by: Yury Molodov <yurymolodov@gmail.com> --------- Signed-off-by: Yury Molodov <yurymolodov@gmail.com>
This commit is contained in:
@@ -80,6 +80,7 @@ export function TraceToLogsSettings({ options, onOptionsChange }: Props) {
|
||||
'grafana-opensearch-datasource', // external
|
||||
'grafana-falconlogscale-datasource', // external
|
||||
'googlecloud-logging-datasource', // external
|
||||
'victoriametrics-logs-datasource', // external
|
||||
];
|
||||
|
||||
const traceToLogs = useMemo(
|
||||
|
||||
@@ -1515,6 +1515,91 @@ describe('createSpanLinkFactory', () => {
|
||||
expect(decodeURIComponent(links![0].href)).toContain('spanName=\\"operation\\"');
|
||||
});
|
||||
});
|
||||
|
||||
describe('should return victorialogs link', () => {
|
||||
const victoriaLogsUID = 'victoriaLogsUID';
|
||||
|
||||
beforeAll(() => {
|
||||
setDataSourceSrv({
|
||||
getInstanceSettings() {
|
||||
return {
|
||||
uid: victoriaLogsUID,
|
||||
name: 'VictoriaLogs',
|
||||
type: 'victoriametrics-logs-datasource',
|
||||
} as unknown as DataSourceInstanceSettings;
|
||||
},
|
||||
} as unknown as DataSourceSrv);
|
||||
|
||||
setLinkSrv(new LinkSrv());
|
||||
setTemplateSrv(new TemplateSrv());
|
||||
});
|
||||
|
||||
it('with default keys when tags not configured', () => {
|
||||
const createLink = setupSpanLinkFactory({}, victoriaLogsUID);
|
||||
const links = createLink!(createTraceSpan());
|
||||
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Logs);
|
||||
expect(linkDef!.href).toBe(
|
||||
`/explore?left=${encodeURIComponent(
|
||||
'{"range":{"from":"1602637200000","to":"1602637201000"},"datasource":"victoriaLogsUID","queries":[{"expr":"cluster:=\\"cluster1\\" AND hostname:=\\"hostname1\\" AND service_namespace:=\\"namespace1\\"","refId":""}]}'
|
||||
)}`
|
||||
);
|
||||
});
|
||||
|
||||
it('formats query correctly if filterByTraceID and filterBySpanID is true', () => {
|
||||
const createLink = setupSpanLinkFactory(
|
||||
{
|
||||
datasourceUid: victoriaLogsUID,
|
||||
filterByTraceID: true,
|
||||
filterBySpanID: true,
|
||||
},
|
||||
victoriaLogsUID
|
||||
);
|
||||
|
||||
const links = createLink!(createTraceSpan());
|
||||
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Logs);
|
||||
expect(linkDef!.href).toBe(
|
||||
`/explore?left=${encodeURIComponent(
|
||||
'{"range":{"from":"1602637200000","to":"1602637201000"},"datasource":"victoriaLogsUID","queries":[{"expr":"span_id:=\\"6605c7b08e715d6c\\" AND trace_id:=\\"7946b05c2e2e4e5a\\" AND cluster:=\\"cluster1\\" AND hostname:=\\"hostname1\\" AND service_namespace:=\\"namespace1\\"","refId":""}]}'
|
||||
)}`
|
||||
);
|
||||
});
|
||||
|
||||
it('should format multiple tags correctly', () => {
|
||||
const createLink = setupSpanLinkFactory(
|
||||
{
|
||||
tags: [{ key: 'ip' }, { key: 'hostname' }],
|
||||
},
|
||||
victoriaLogsUID
|
||||
);
|
||||
|
||||
const links = createLink!(
|
||||
createTraceSpan({
|
||||
process: {
|
||||
serviceName: 'service',
|
||||
tags: [
|
||||
{ key: 'hostname', value: 'hostname1' },
|
||||
{ key: 'ip', value: '192.168.0.1' },
|
||||
],
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const linkDef = links?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef?.type).toBe(SpanLinkType.Logs);
|
||||
expect(linkDef!.href).toBe(
|
||||
`/explore?left=${encodeURIComponent(
|
||||
'{"range":{"from":"1602637200000","to":"1602637201000"},"datasource":"victoriaLogsUID","queries":[{"expr":"hostname:=\\"hostname1\\" AND ip:=\\"192.168.0.1\\"","refId":""}]}'
|
||||
)}`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('dataFrame links', () => {
|
||||
|
||||
@@ -196,6 +196,13 @@ function legacyCreateSpanLinkFactory(
|
||||
case 'googlecloud-logging-datasource':
|
||||
tags = getFormattedTags(span, tagsToUse, { joinBy: ' AND ' });
|
||||
query = getQueryForGoogleCloudLogging(span, traceToLogsOptions, tags, customQuery);
|
||||
break;
|
||||
case 'victoriametrics-logs-datasource':
|
||||
// Build tag selector using strict equality (":=") required by LogsQL
|
||||
// See https://docs.victoriametrics.com/victorialogs/logsql/#exact-filter
|
||||
tags = getFormattedTags(span, tagsToUse, { labelValueSign: ':=', joinBy: ' AND ' });
|
||||
query = getQueryForVictoriaLogs(span, traceToLogsOptions, tags, customQuery);
|
||||
break;
|
||||
}
|
||||
|
||||
// query can be false in case the simple UI tag mapping is used but none of them are present in the span.
|
||||
@@ -568,6 +575,45 @@ function getQueryForFalconLogScale(span: TraceSpan, options: TraceToLogsOptionsV
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a LogsQL expression for victoria‑metrics‑logs‑datasource.
|
||||
* Uses := for exact‑match filters and joins parts with AND.
|
||||
* See https://docs.victoriametrics.com/victorialogs/logsql/#exact-filter
|
||||
*/
|
||||
function getQueryForVictoriaLogs(span: TraceSpan, options: TraceToLogsOptionsV2, tags: string, customQuery?: string) {
|
||||
const { filterByTraceID, filterBySpanID } = options;
|
||||
|
||||
// Custom user query has priority
|
||||
if (customQuery) {
|
||||
return {
|
||||
expr: customQuery,
|
||||
refId: '',
|
||||
};
|
||||
}
|
||||
|
||||
const parts: string[] = [];
|
||||
|
||||
if (filterBySpanID && span.spanID) {
|
||||
parts.push('span_id:="${__span.spanId}"');
|
||||
}
|
||||
if (filterByTraceID && span.traceID) {
|
||||
parts.push('trace_id:="${__span.traceId}"');
|
||||
}
|
||||
if (tags) {
|
||||
parts.push('${__tags}');
|
||||
}
|
||||
|
||||
// Nothing to match against – do not create the link
|
||||
if (!parts.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
expr: parts.join(' AND '),
|
||||
refId: '',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a string representing all the tags already formatted for use in the query. The tags are filtered so that
|
||||
* only intersection of tags that exist in a span and tags that you want are serialized into the string.
|
||||
|
||||
Reference in New Issue
Block a user