mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Elasticsearch: Add trace to logs functionality (#58063)
* Elasticsearch: Implement trace to logs * Fix tests
This commit is contained in:
parent
eb84358aa7
commit
a83dee6031
@ -3968,17 +3968,6 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "6"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "7"]
|
||||
],
|
||||
"public/app/features/explore/TraceView/createSpanLink.test.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "2"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "3"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "4"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "5"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "6"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "7"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "8"]
|
||||
],
|
||||
"public/app/features/explore/TraceView/createSpanLink.tsx:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "1"],
|
||||
@ -3989,8 +3978,7 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Do not use any type assertions.", "6"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "7"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "8"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "9"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "10"]
|
||||
[0, 0, 0, "Do not use any type assertions.", "9"]
|
||||
],
|
||||
"public/app/features/explore/Wrapper.test.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
|
@ -34,6 +34,7 @@ interface Props extends DataSourcePluginOptionsEditorProps<TraceToLogsData> {}
|
||||
|
||||
export function TraceToLogsSettings({ options, onOptionsChange }: Props) {
|
||||
const styles = useStyles2(getStyles);
|
||||
const supportedDataSourceTypes = ['loki', 'grafana-splunk-datasource', 'elasticsearch'];
|
||||
|
||||
return (
|
||||
<div className={css({ width: '100%' })}>
|
||||
@ -47,10 +48,7 @@ export function TraceToLogsSettings({ options, onOptionsChange }: Props) {
|
||||
<InlineField tooltip="The data source the trace is going to navigate to" label="Data source" labelWidth={26}>
|
||||
<DataSourcePicker
|
||||
inputId="trace-to-logs-data-source-picker"
|
||||
filter={(ds) => {
|
||||
// Trace to logs only supports loki and splunk at the moment
|
||||
return ds.type === 'loki' || ds.type === 'grafana-splunk-datasource';
|
||||
}}
|
||||
filter={(ds) => supportedDataSourceTypes.includes(ds.type)}
|
||||
current={options.jsonData.tracesToLogs?.datasourceUid}
|
||||
noDefault={true}
|
||||
width={40}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { DataSourceInstanceSettings, MutableDataFrame } from '@grafana/data';
|
||||
import { setDataSourceSrv, setTemplateSrv } from '@grafana/runtime';
|
||||
import { DataSourceInstanceSettings, LinkModel, MutableDataFrame } from '@grafana/data';
|
||||
import { DataSourceSrv, setDataSourceSrv, setTemplateSrv } from '@grafana/runtime';
|
||||
import { TraceSpan } from '@jaegertracing/jaeger-ui-components';
|
||||
import { TraceToMetricsOptions } from 'app/core/components/TraceToMetrics/TraceToMetricsSettings';
|
||||
import { DatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||
@ -23,10 +23,10 @@ describe('createSpanLinkFactory', () => {
|
||||
describe('should return loki link', () => {
|
||||
beforeAll(() => {
|
||||
setDataSourceSrv({
|
||||
getInstanceSettings(uid: string): DataSourceInstanceSettings | undefined {
|
||||
return { uid: 'loki1_uid', name: 'loki1', type: 'loki' } as any;
|
||||
getInstanceSettings() {
|
||||
return { uid: 'loki1_uid', name: 'loki1', type: 'loki' } as unknown as DataSourceInstanceSettings;
|
||||
},
|
||||
} as any);
|
||||
} as unknown as DataSourceSrv);
|
||||
|
||||
setLinkSrv(new LinkSrv());
|
||||
setTemplateSrv(new TemplateSrv());
|
||||
@ -246,10 +246,14 @@ describe('createSpanLinkFactory', () => {
|
||||
|
||||
beforeAll(() => {
|
||||
setDataSourceSrv({
|
||||
getInstanceSettings(uid: string): DataSourceInstanceSettings | undefined {
|
||||
return { uid: splunkUID, name: 'Splunk 8', type: 'grafana-splunk-datasource' } as any;
|
||||
getInstanceSettings() {
|
||||
return {
|
||||
uid: splunkUID,
|
||||
name: 'Splunk 8',
|
||||
type: 'grafana-splunk-datasource',
|
||||
} as unknown as DataSourceInstanceSettings;
|
||||
},
|
||||
} as any);
|
||||
} as unknown as DataSourceSrv);
|
||||
|
||||
setLinkSrv(new LinkSrv());
|
||||
setTemplateSrv(new TemplateSrv());
|
||||
@ -385,10 +389,10 @@ describe('createSpanLinkFactory', () => {
|
||||
describe('should return metric link', () => {
|
||||
beforeAll(() => {
|
||||
setDataSourceSrv({
|
||||
getInstanceSettings(uid: string): DataSourceInstanceSettings | undefined {
|
||||
return { uid: 'prom1Uid', name: 'prom1', type: 'prometheus' } as any;
|
||||
getInstanceSettings() {
|
||||
return { uid: 'prom1Uid', name: 'prom1', type: 'prometheus' } as unknown as DataSourceInstanceSettings;
|
||||
},
|
||||
} as any);
|
||||
} as unknown as DatasourceSrv);
|
||||
|
||||
setLinkSrv(new LinkSrv());
|
||||
setTemplateSrv(new TemplateSrv());
|
||||
@ -565,7 +569,7 @@ describe('createSpanLinkFactory', () => {
|
||||
refType: 'FOLLOWS_FROM',
|
||||
spanID: 'span1',
|
||||
traceID: 'traceID',
|
||||
span: { operationName: 'SpanName' } as any,
|
||||
span: { operationName: 'SpanName' } as TraceSpan,
|
||||
},
|
||||
],
|
||||
subsidiarilyReferencedBy: [{ refType: 'FOLLOWS_FROM', spanID: 'span3', traceID: 'traceID2' }],
|
||||
@ -589,6 +593,166 @@ describe('createSpanLinkFactory', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('elasticsearch link', () => {
|
||||
const elasticsearchUID = 'elasticsearchUID';
|
||||
|
||||
beforeAll(() => {
|
||||
setDataSourceSrv({
|
||||
getInstanceSettings() {
|
||||
return {
|
||||
uid: elasticsearchUID,
|
||||
name: 'Elasticsearch',
|
||||
type: 'elasticsearch',
|
||||
} as unknown as DataSourceInstanceSettings;
|
||||
},
|
||||
} as unknown as DataSourceSrv);
|
||||
|
||||
setLinkSrv(new LinkSrv());
|
||||
setTemplateSrv(new TemplateSrv());
|
||||
});
|
||||
|
||||
it('creates link with correct simple query', () => {
|
||||
const createLink = setupSpanLinkFactory({
|
||||
datasourceUid: elasticsearchUID,
|
||||
});
|
||||
const links = createLink!(createTraceSpan());
|
||||
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef!.href).toContain(
|
||||
encodeURIComponent(
|
||||
`datasource":"${elasticsearchUID}","queries":[{"query":"cluster:\\"cluster1\\" AND hostname:\\"hostname1\\"","refId":"","metrics":[{"id":"1","type":"logs"}]}]`
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('automatically timeshifts the time range by one second in a query', () => {
|
||||
const createLink = setupSpanLinkFactory({
|
||||
datasourceUid: elasticsearchUID,
|
||||
});
|
||||
const links = createLink!(createTraceSpan());
|
||||
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef!.href).toContain(
|
||||
`${encodeURIComponent('{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"}')}`
|
||||
);
|
||||
expect(linkDef!.href).not.toContain(
|
||||
`${encodeURIComponent('{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:00.000Z"}')}`
|
||||
);
|
||||
});
|
||||
|
||||
it('formats query correctly if filterByTraceID and or filterBySpanID is true', () => {
|
||||
const createLink = setupSpanLinkFactory(
|
||||
{
|
||||
datasourceUid: elasticsearchUID,
|
||||
filterByTraceID: true,
|
||||
filterBySpanID: true,
|
||||
},
|
||||
elasticsearchUID
|
||||
);
|
||||
|
||||
expect(createLink).toBeDefined();
|
||||
const links = createLink!(createTraceSpan());
|
||||
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef!.href).toBe(
|
||||
`/explore?left=${encodeURIComponent(
|
||||
`{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"${elasticsearchUID}","queries":[{"query":"\\"6605c7b08e715d6c\\" AND \\"7946b05c2e2e4e5a\\" AND cluster:\\"cluster1\\" AND hostname:\\"hostname1\\"","refId":"","metrics":[{"id":"1","type":"logs"}]}],"panelsState":{}}`
|
||||
)}`
|
||||
);
|
||||
});
|
||||
|
||||
it('should format one tag correctly', () => {
|
||||
const createLink = setupSpanLinkFactory(
|
||||
{
|
||||
tags: ['ip'],
|
||||
},
|
||||
elasticsearchUID
|
||||
);
|
||||
expect(createLink).toBeDefined();
|
||||
const links = createLink!(
|
||||
createTraceSpan({
|
||||
process: {
|
||||
serviceName: 'service',
|
||||
tags: [{ key: 'ip', value: '192.168.0.1' }],
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef!.href).toBe(
|
||||
`/explore?left=${encodeURIComponent(
|
||||
`{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"${elasticsearchUID}","queries":[{"query":"ip:\\"192.168.0.1\\"","refId":"","metrics":[{"id":"1","type":"logs"}]}],"panelsState":{}}`
|
||||
)}`
|
||||
);
|
||||
});
|
||||
|
||||
it('should format multiple tags correctly', () => {
|
||||
const createLink = setupSpanLinkFactory(
|
||||
{
|
||||
tags: ['ip', 'hostname'],
|
||||
},
|
||||
elasticsearchUID
|
||||
);
|
||||
expect(createLink).toBeDefined();
|
||||
const links = createLink!(
|
||||
createTraceSpan({
|
||||
process: {
|
||||
serviceName: 'service',
|
||||
tags: [
|
||||
{ key: 'hostname', value: 'hostname1' },
|
||||
{ key: 'ip', value: '192.168.0.1' },
|
||||
],
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef!.href).toBe(
|
||||
`/explore?left=${encodeURIComponent(
|
||||
`{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"${elasticsearchUID}","queries":[{"query":"hostname:\\"hostname1\\" AND ip:\\"192.168.0.1\\"","refId":"","metrics":[{"id":"1","type":"logs"}]}],"panelsState":{}}`
|
||||
)}`
|
||||
);
|
||||
});
|
||||
|
||||
it('handles renamed tags', () => {
|
||||
const createLink = setupSpanLinkFactory(
|
||||
{
|
||||
mapTagNamesEnabled: true,
|
||||
mappedTags: [
|
||||
{ key: 'service.name', value: 'service' },
|
||||
{ key: 'k8s.pod.name', value: 'pod' },
|
||||
],
|
||||
},
|
||||
elasticsearchUID
|
||||
);
|
||||
expect(createLink).toBeDefined();
|
||||
const links = createLink!(
|
||||
createTraceSpan({
|
||||
process: {
|
||||
serviceName: 'service',
|
||||
tags: [
|
||||
{ key: 'service.name', value: 'serviceName' },
|
||||
{ key: 'k8s.pod.name', value: 'podName' },
|
||||
],
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const linkDef = links?.logLinks?.[0];
|
||||
expect(linkDef).toBeDefined();
|
||||
expect(linkDef!.href).toBe(
|
||||
`/explore?left=${encodeURIComponent(
|
||||
`{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"${elasticsearchUID}","queries":[{"query":"service:\\"serviceName\\" AND pod:\\"podName\\"","refId":"","metrics":[{"id":"1","type":"logs"}]}],"panelsState":{}}`
|
||||
)}`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function setupSpanLinkFactory(options: Partial<TraceToLogsOptions> = {}, datasourceUid = 'lokiUid') {
|
||||
@ -602,12 +766,12 @@ function setupSpanLinkFactory(options: Partial<TraceToLogsOptions> = {}, datasou
|
||||
createFocusSpanLink: (traceId, spanId) => {
|
||||
return {
|
||||
href: `${traceId}-${spanId}`,
|
||||
} as any;
|
||||
} as unknown as LinkModel;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function createTraceSpan(overrides: Partial<TraceSpan> = {}): TraceSpan {
|
||||
function createTraceSpan(overrides: Partial<TraceSpan> = {}) {
|
||||
return {
|
||||
spanID: '6605c7b08e715d6c',
|
||||
traceID: '7946b05c2e2e4e5a',
|
||||
@ -643,5 +807,5 @@ function createTraceSpan(overrides: Partial<TraceSpan> = {}): TraceSpan {
|
||||
],
|
||||
},
|
||||
...overrides,
|
||||
} as any;
|
||||
} as TraceSpan;
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import { SpanLinkFunc, TraceSpan } from '@jaegertracing/jaeger-ui-components';
|
||||
import { TraceToLogsOptions } from 'app/core/components/TraceToLogs/TraceToLogsSettings';
|
||||
import { TraceToMetricQuery, TraceToMetricsOptions } from 'app/core/components/TraceToMetrics/TraceToMetricsSettings';
|
||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||
import { ElasticsearchQuery } from 'app/plugins/datasource/elasticsearch/types';
|
||||
import { PromQuery } from 'app/plugins/datasource/prometheus/types';
|
||||
|
||||
import { LokiQuery } from '../../../plugins/datasource/loki/types';
|
||||
@ -103,7 +104,7 @@ function legacyCreateSpanLinkFactory(
|
||||
// the moment. Issue is that the trace itself isn't clearly mapped to dataFrame (right now it's just a json blob
|
||||
// inside a single field) so the dataLinks as config of that dataFrame abstraction breaks down a bit and we do
|
||||
// it manually here instead of leaving it for the data source to supply the config.
|
||||
let dataLink: DataLink<LokiQuery | DataQuery> | undefined = {} as DataLink<LokiQuery | DataQuery> | undefined;
|
||||
let dataLink: DataLink | undefined;
|
||||
|
||||
// Get logs link
|
||||
if (logsDataSourceSettings && traceToLogsOptions) {
|
||||
@ -114,6 +115,8 @@ function legacyCreateSpanLinkFactory(
|
||||
case 'grafana-splunk-datasource':
|
||||
dataLink = getLinkForSplunk(span, traceToLogsOptions, logsDataSourceSettings);
|
||||
break;
|
||||
case 'elasticsearch':
|
||||
dataLink = getLinkForElasticsearch(span, traceToLogsOptions, logsDataSourceSettings);
|
||||
}
|
||||
|
||||
if (dataLink) {
|
||||
@ -280,6 +283,61 @@ function getLinkForLoki(span: TraceSpan, options: TraceToLogsOptions, dataSource
|
||||
return dataLink;
|
||||
}
|
||||
|
||||
function getLinkForElasticsearch(
|
||||
span: TraceSpan,
|
||||
options: TraceToLogsOptions,
|
||||
dataSourceSettings: DataSourceInstanceSettings
|
||||
) {
|
||||
const { tags: keys, filterByTraceID, filterBySpanID, mapTagNamesEnabled, mappedTags } = options;
|
||||
const tags = [...span.process.tags, ...span.tags].reduce((acc: string[], tag) => {
|
||||
if (mapTagNamesEnabled && mappedTags?.length) {
|
||||
const keysToCheck = mappedTags;
|
||||
const keyValue = keysToCheck.find((keyValue) => keyValue.key === tag.key);
|
||||
if (keyValue) {
|
||||
acc.push(`${keyValue.value ? keyValue.value : keyValue.key}:"${tag.value}"`);
|
||||
}
|
||||
} else {
|
||||
const keysToCheck = keys?.length ? keys : defaultKeys;
|
||||
if (keysToCheck.includes(tag.key)) {
|
||||
acc.push(`${tag.key}:"${tag.value}"`);
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
let query = '';
|
||||
if (tags.length > 0) {
|
||||
query += `${tags.join(' AND ')}`;
|
||||
}
|
||||
if (filterByTraceID && span.traceID) {
|
||||
query = `"${span.traceID}" AND ` + query;
|
||||
}
|
||||
if (filterBySpanID && span.spanID) {
|
||||
query = `"${span.spanID}" AND ` + query;
|
||||
}
|
||||
|
||||
const dataLink: DataLink<ElasticsearchQuery> = {
|
||||
title: dataSourceSettings.name,
|
||||
url: '',
|
||||
internal: {
|
||||
datasourceUid: dataSourceSettings.uid,
|
||||
datasourceName: dataSourceSettings.name,
|
||||
query: {
|
||||
query: query,
|
||||
refId: '',
|
||||
metrics: [
|
||||
{
|
||||
id: '1',
|
||||
type: 'logs',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return dataLink;
|
||||
}
|
||||
|
||||
function getLinkForSplunk(
|
||||
span: TraceSpan,
|
||||
options: TraceToLogsOptions,
|
||||
|
Loading…
Reference in New Issue
Block a user