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>
pull/108026/head
Yury Molodov 1 week ago committed by GitHub
parent ce73e5126a
commit f94722e1e3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      packages/grafana-o11y-ds-frontend/src/TraceToLogs/TraceToLogsSettings.tsx
  2. 85
      public/app/features/explore/TraceView/createSpanLink.test.ts
  3. 46
      public/app/features/explore/TraceView/createSpanLink.tsx

@ -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 victoriametricslogsdatasource.
* Uses := for exactmatch 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.

Loading…
Cancel
Save