Tracing: Add option to map tag names to log label names in trace to logs settings (#45178)

* Add mapped tags input to allow renaming tags in trace to logs settings

* Use mappedTags in createSpanLink

* Update traceToLogs docs

* Show 'add kv' button if no tags

* Update docs

* Default mappedTags to tag values
This commit is contained in:
Connor Lindsey 2022-02-22 15:17:45 -07:00 committed by GitHub
parent 6165377eb2
commit af2d19b02e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 260 additions and 26 deletions

View File

@ -32,6 +32,7 @@ This is a configuration for the [trace to logs feature]({{< relref "../explore/t
- **Data source -** Target data source. - **Data source -** Target data source.
- **Tags -** The tags that will be used in the Loki query. Default is `'cluster', 'hostname', 'namespace', 'pod'`. - **Tags -** The tags that will be used in the Loki query. Default is `'cluster', 'hostname', 'namespace', 'pod'`.
- **Map tag names -** When enabled, allows configuring how Jaeger tag names map to Loki label names. For example, map `service.name` to `service`.
- **Span start time shift -** Shift in the start time for the Loki query based on the span start time. In order to extend to the past, you need to use a negative value. Use time interval units like 5s, 1m, 3h. The default is 0. - **Span start time shift -** Shift in the start time for the Loki query based on the span start time. In order to extend to the past, you need to use a negative value. Use time interval units like 5s, 1m, 3h. The default is 0.
- **Span end time shift -** Shift in the end time for the Loki query based on the span end time. Time units can be used here, for example, 5s, 1m, 3h. The default is 0. - **Span end time shift -** Shift in the end time for the Loki query based on the span end time. Time units can be used here, for example, 5s, 1m, 3h. The default is 0.
- **Filter by Trace ID -** Toggle to append the trace ID to the Loki query. - **Filter by Trace ID -** Toggle to append the trace ID to the Loki query.
@ -147,12 +148,15 @@ datasources:
tracesToLogs: tracesToLogs:
# Field with internal link pointing to a Loki data source in Grafana. # Field with internal link pointing to a Loki data source in Grafana.
# datasourceUid value must match the `datasourceUid` value of the Loki data source. # datasourceUid value must match the `datasourceUid` value of the Loki data source.
datasourceUid: loki datasourceUid: 'loki'
tags: tags: ['job', 'instance', 'pod', 'namespace']
- cluster mappedTags: [{ key: 'service.name', value: 'service' }]
- hostname mapTagNamesEnabled: false
- namespace spanStartTimeShift: '1h'
- pod spanEndTimeShift: '1h'
filterByTraceID: false
filterBySpanID: false
lokiSearch: true
secureJsonData: secureJsonData:
basicAuthPassword: my_password basicAuthPassword: my_password
``` ```

View File

@ -31,6 +31,7 @@ This is a configuration for the [trace to logs feature]({{< relref "../explore/t
- **Data source -** Target data source. - **Data source -** Target data source.
- **Tags -** The tags that will be used in the Loki query. Default is `'cluster', 'hostname', 'namespace', 'pod'`. - **Tags -** The tags that will be used in the Loki query. Default is `'cluster', 'hostname', 'namespace', 'pod'`.
- **Map tag names -** When enabled, allows configuring how Tempo tag names map to Loki label names. For example, map `service.name` to `service`.
- **Span start time shift -** A shift in the start time for the Loki query based on the start time for the span. To extend the time to the past, use a negative value. You can use time units, for example, 5s, 1m, 3h. The default is 0. - **Span start time shift -** A shift in the start time for the Loki query based on the start time for the span. To extend the time to the past, use a negative value. You can use time units, for example, 5s, 1m, 3h. The default is 0.
- **Span end time shift -** Shift in the end time for the Loki query based on the span end time. Time units can be used here, for example, 5s, 1m, 3h. The default is 0. - **Span end time shift -** Shift in the end time for the Loki query based on the span end time. Time units can be used here, for example, 5s, 1m, 3h. The default is 0.
- **Filter by Trace ID -** Toggle to append the trace ID to the Loki query. - **Filter by Trace ID -** Toggle to append the trace ID to the Loki query.
@ -167,6 +168,8 @@ datasources:
tracesToLogs: tracesToLogs:
datasourceUid: 'loki' datasourceUid: 'loki'
tags: ['job', 'instance', 'pod', 'namespace'] tags: ['job', 'instance', 'pod', 'namespace']
mappedTags: [{ key: 'service.name', value: 'service' }]
mapTagNamesEnabled: false
spanStartTimeShift: '1h' spanStartTimeShift: '1h'
spanEndTimeShift: '1h' spanEndTimeShift: '1h'
filterByTraceID: false filterByTraceID: false

View File

@ -32,6 +32,7 @@ This is a configuration for the [trace to logs feature]({{< relref "../explore/t
- **Data source -** Target data source. - **Data source -** Target data source.
- **Tags -** The tags that will be used in the Loki query. Default is `'cluster', 'hostname', 'namespace', 'pod'`. - **Tags -** The tags that will be used in the Loki query. Default is `'cluster', 'hostname', 'namespace', 'pod'`.
- **Map tag names -** When enabled, allows configuring how Zipkin tag names map to Loki label names. For example, map `service.name` to `service`.
- **Span start time shift -** Shift in the start time for the Loki query based on the span start time. In order to extend to the past, you need to use a negative value. Use time interval units like 5s, 1m, 3h. The default is 0. - **Span start time shift -** Shift in the start time for the Loki query based on the span start time. In order to extend to the past, you need to use a negative value. Use time interval units like 5s, 1m, 3h. The default is 0.
- **Span end time shift -** Shift in the end time for the Loki query based on the span end time. Time units can be used here, for example, 5s, 1m, 3h. The default is 0. - **Span end time shift -** Shift in the end time for the Loki query based on the span end time. Time units can be used here, for example, 5s, 1m, 3h. The default is 0.
- **Filter by Trace ID -** Toggle to append the trace ID to the Loki query. - **Filter by Trace ID -** Toggle to append the trace ID to the Loki query.

View File

@ -0,0 +1,110 @@
import { css } from '@emotion/css';
import { GrafanaTheme, KeyValue } from '@grafana/data';
import { SegmentInput, useStyles, InlineLabel, Icon } from '@grafana/ui';
import React from 'react';
const EQ_WIDTH = 3; // = 24px in inline label
interface Props {
values: Array<KeyValue<string>>;
onChange: (values: Array<KeyValue<string>>) => void;
id?: string;
keyPlaceholder?: string;
valuePlaceholder?: string;
}
const KeyValueInput = ({
values,
onChange,
id,
keyPlaceholder = 'Key',
valuePlaceholder = 'Value (optional)',
}: Props) => {
const styles = useStyles(getStyles);
return (
<div className={styles.wrapper}>
{values.length ? (
values.map((value, idx) => (
<div className={styles.pair} key={idx}>
<SegmentInput
id={`${id}-key-${idx}`}
placeholder={keyPlaceholder}
value={value.key}
onChange={(e) => {
onChange(
values.map((v, i) => {
if (i === idx) {
v.key = String(e);
}
return v;
})
);
}}
/>
<InlineLabel aria-label="equals" className={styles.operator} width={EQ_WIDTH}>
=
</InlineLabel>
<SegmentInput
id={`${id}-value-${idx}`}
placeholder={valuePlaceholder}
value={value.value}
onChange={(e) => {
onChange(
values.map((v, i) => {
if (i === idx) {
v.value = String(e);
}
return v;
})
);
}}
/>
<button
onClick={() => onChange([...values.slice(0, idx), ...values.slice(idx + 1)])}
className="gf-form-label query-part"
aria-label="Remove tag"
>
<Icon name="times" />
</button>
{idx === values.length - 1 ? (
<button
onClick={() => onChange([...values, { key: '', value: '' }])}
className="gf-form-label query-part"
aria-label="Add tag"
>
<Icon name="plus" />
</button>
) : null}
</div>
))
) : (
<button
onClick={() => onChange([...values, { key: '', value: '' }])}
className="gf-form-label query-part"
aria-label="Add tag"
>
<Icon name="plus" />
</button>
)}
</div>
);
};
export default KeyValueInput;
const getStyles = (theme: GrafanaTheme) => ({
wrapper: css`
display: flex;
flex-direction: column;
gap: ${theme.spacing.xs} 0;
`,
pair: css`
display: flex;
justify-content: start;
align-items: center;
`,
operator: css`
color: ${theme.palette.orange};
`,
});

View File

@ -3,15 +3,19 @@ import {
DataSourceJsonData, DataSourceJsonData,
DataSourcePluginOptionsEditorProps, DataSourcePluginOptionsEditorProps,
GrafanaTheme, GrafanaTheme,
KeyValue,
updateDatasourcePluginJsonDataOption, updateDatasourcePluginJsonDataOption,
} from '@grafana/data'; } from '@grafana/data';
import { DataSourcePicker } from '@grafana/runtime'; import { DataSourcePicker } from '@grafana/runtime';
import { InlineField, InlineFieldRow, Input, TagsInput, useStyles, InlineSwitch } from '@grafana/ui'; import { InlineField, InlineFieldRow, Input, TagsInput, useStyles, InlineSwitch } from '@grafana/ui';
import React from 'react'; import React from 'react';
import KeyValueInput from './KeyValueInput';
export interface TraceToLogsOptions { export interface TraceToLogsOptions {
datasourceUid?: string; datasourceUid?: string;
tags?: string[]; tags?: string[];
mappedTags?: Array<KeyValue<string>>;
mapTagNamesEnabled?: boolean;
spanStartTimeShift?: string; spanStartTimeShift?: string;
spanEndTimeShift?: string; spanEndTimeShift?: string;
filterByTraceID?: boolean; filterByTraceID?: boolean;
@ -54,19 +58,64 @@ export function TraceToLogsSettings({ options, onOptionsChange }: Props) {
</InlineField> </InlineField>
</InlineFieldRow> </InlineFieldRow>
{options.jsonData.tracesToLogs?.mapTagNamesEnabled ? (
<InlineFieldRow>
<InlineField
tooltip="Tags that will be used in the Loki query. Default tags: 'cluster', 'hostname', 'namespace', 'pod'"
label="Tags"
labelWidth={26}
>
<KeyValueInput
keyPlaceholder="Tag"
values={
options.jsonData.tracesToLogs?.mappedTags ??
options.jsonData.tracesToLogs?.tags?.map((tag) => ({ key: tag })) ??
[]
}
onChange={(v) =>
updateDatasourcePluginJsonDataOption({ onOptionsChange, options }, 'tracesToLogs', {
...options.jsonData.tracesToLogs,
mappedTags: v,
})
}
/>
</InlineField>
</InlineFieldRow>
) : (
<InlineFieldRow>
<InlineField
tooltip="Tags that will be used in the Loki query. Default tags: 'cluster', 'hostname', 'namespace', 'pod'"
label="Tags"
labelWidth={26}
>
<TagsInput
tags={options.jsonData.tracesToLogs?.tags}
width={40}
onChange={(tags) =>
updateDatasourcePluginJsonDataOption({ onOptionsChange, options }, 'tracesToLogs', {
...options.jsonData.tracesToLogs,
tags: tags,
})
}
/>
</InlineField>
</InlineFieldRow>
)}
<InlineFieldRow> <InlineFieldRow>
<InlineField <InlineField
tooltip="Tags that will be used in the Loki query. Default tags: 'cluster', 'hostname', 'namespace', 'pod'" label="Map tag names"
label="Tags"
labelWidth={26} labelWidth={26}
grow
tooltip="Map trace tag names to log label names. Ex: k8s.pod.name -> pod"
> >
<TagsInput <InlineSwitch
tags={options.jsonData.tracesToLogs?.tags} id="mapTagNames"
width={40} value={options.jsonData.tracesToLogs?.mapTagNamesEnabled ?? false}
onChange={(tags) => onChange={(event: React.SyntheticEvent<HTMLInputElement>) =>
updateDatasourcePluginJsonDataOption({ onOptionsChange, options }, 'tracesToLogs', { updateDatasourcePluginJsonDataOption({ onOptionsChange, options }, 'tracesToLogs', {
datasourceUid: options.jsonData.tracesToLogs?.datasourceUid, ...options.jsonData.tracesToLogs,
tags: tags, mapTagNamesEnabled: event.currentTarget.checked,
}) })
} }
/> />
@ -156,6 +205,7 @@ export function TraceToLogsSettings({ options, onOptionsChange }: Props) {
/> />
</InlineField> </InlineField>
</InlineFieldRow> </InlineFieldRow>
<InlineFieldRow> <InlineFieldRow>
<InlineField label="Loki Search" labelWidth={26} grow tooltip="Use this logs data source to search for traces."> <InlineField label="Loki Search" labelWidth={26} grow tooltip="Use this logs data source to search for traces.">
<InlineSwitch <InlineSwitch

View File

@ -19,7 +19,7 @@ import {
transformTraceData, transformTraceData,
TTraceTimeline, TTraceTimeline,
} from '@jaegertracing/jaeger-ui-components'; } from '@jaegertracing/jaeger-ui-components';
import { TraceToLogsData } from 'app/core/components/TraceToLogsSettings'; import { TraceToLogsData } from 'app/core/components/TraceToLogs/TraceToLogsSettings';
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
import { getTimeZone } from 'app/features/profile/state/selectors'; import { getTimeZone } from 'app/features/profile/state/selectors';
import { StoreState } from 'app/types'; import { StoreState } from 'app/types';

View File

@ -2,7 +2,7 @@ import { DataSourceInstanceSettings, MutableDataFrame } from '@grafana/data';
import { setDataSourceSrv, setTemplateSrv } from '@grafana/runtime'; import { setDataSourceSrv, setTemplateSrv } from '@grafana/runtime';
import { createSpanLinkFactory } from './createSpanLink'; import { createSpanLinkFactory } from './createSpanLink';
import { TraceSpan } from '@jaegertracing/jaeger-ui-components'; import { TraceSpan } from '@jaegertracing/jaeger-ui-components';
import { TraceToLogsOptions } from '../../../core/components/TraceToLogsSettings'; import { TraceToLogsOptions } from '../../../core/components/TraceToLogs/TraceToLogsSettings';
import { LinkSrv, setLinkSrv } from '../../panel/panellinks/link_srv'; import { LinkSrv, setLinkSrv } from '../../panel/panellinks/link_srv';
import { TemplateSrv } from '../../templating/template_srv'; import { TemplateSrv } from '../../templating/template_srv';
@ -141,6 +141,60 @@ describe('createSpanLinkFactory', () => {
expect(linkDef!.href).toBe('testSpanId'); expect(linkDef!.href).toBe('testSpanId');
}); });
it('handles renamed tags', () => {
const createLink = setupSpanLinkFactory({
mapTagNamesEnabled: true,
mappedTags: [
{ key: 'service.name', value: 'service' },
{ key: 'k8s.pod.name', value: 'pod' },
],
});
expect(createLink).toBeDefined();
const linkDef = createLink!(
createTraceSpan({
process: {
serviceName: 'service',
tags: [
{ key: 'service.name', value: 'serviceName' },
{ key: 'k8s.pod.name', value: 'podName' },
],
},
})
);
expect(linkDef!.href).toBe(
`/explore?left=${encodeURIComponent(
'{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"loki1","queries":[{"expr":"{service=\\"serviceName\\", pod=\\"podName\\"}","refId":""}],"panelsState":{}}'
)}`
);
});
it('handles incomplete renamed tags', () => {
const createLink = setupSpanLinkFactory({
mapTagNamesEnabled: true,
mappedTags: [
{ key: 'service.name', value: '' },
{ key: 'k8s.pod.name', value: 'pod' },
],
});
expect(createLink).toBeDefined();
const linkDef = createLink!(
createTraceSpan({
process: {
serviceName: 'service',
tags: [
{ key: 'service.name', value: 'serviceName' },
{ key: 'k8s.pod.name', value: 'podName' },
],
},
})
);
expect(linkDef!.href).toBe(
`/explore?left=${encodeURIComponent(
'{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"loki1","queries":[{"expr":"{service.name=\\"serviceName\\", pod=\\"podName\\"}","refId":""}],"panelsState":{}}'
)}`
);
});
}); });
}); });

View File

@ -3,6 +3,7 @@ import {
DataLink, DataLink,
dateTime, dateTime,
Field, Field,
KeyValue,
mapInternalLinkToExplore, mapInternalLinkToExplore,
rangeUtil, rangeUtil,
SplitOpen, SplitOpen,
@ -11,7 +12,7 @@ import {
import { getTemplateSrv } from '@grafana/runtime'; import { getTemplateSrv } from '@grafana/runtime';
import { Icon } from '@grafana/ui'; import { Icon } from '@grafana/ui';
import { SpanLinkDef, SpanLinkFunc, TraceSpan } from '@jaegertracing/jaeger-ui-components'; import { SpanLinkDef, SpanLinkFunc, TraceSpan } from '@jaegertracing/jaeger-ui-components';
import { TraceToLogsOptions } from 'app/core/components/TraceToLogsSettings'; import { TraceToLogsOptions } from 'app/core/components/TraceToLogs/TraceToLogsSettings';
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
import React from 'react'; import React from 'react';
import { LokiQuery } from '../../../plugins/datasource/loki/types'; import { LokiQuery } from '../../../plugins/datasource/loki/types';
@ -122,11 +123,22 @@ function legacyCreateSpanLinkFactory(splitOpenFn: SplitOpen, traceToLogsOptions?
const defaultKeys = ['cluster', 'hostname', 'namespace', 'pod']; const defaultKeys = ['cluster', 'hostname', 'namespace', 'pod'];
function getLokiQueryFromSpan(span: TraceSpan, options: TraceToLogsOptions): string { function getLokiQueryFromSpan(span: TraceSpan, options: TraceToLogsOptions): string {
const { tags: keys, filterByTraceID, filterBySpanID } = options; const { tags: keys, filterByTraceID, filterBySpanID, mapTagNamesEnabled, mappedTags } = options;
const keysToCheck = keys?.length ? keys : defaultKeys;
// In order, try to use mapped tags -> tags -> default tags
const keysToCheck = mapTagNamesEnabled && mappedTags?.length ? mappedTags : keys?.length ? keys : defaultKeys;
// Build tag portion of query
const tags = [...span.process.tags, ...span.tags].reduce((acc, tag) => { const tags = [...span.process.tags, ...span.tags].reduce((acc, tag) => {
if (keysToCheck.includes(tag.key)) { if (mapTagNamesEnabled) {
acc.push(`${tag.key}="${tag.value}"`); const keyValue = (keysToCheck as KeyValue[]).find((keyValue: KeyValue) => keyValue.key === tag.key);
if (keyValue) {
acc.push(`${keyValue.value ? keyValue.value : keyValue.key}="${tag.value}"`);
}
} else {
if ((keysToCheck as string[]).includes(tag.key)) {
acc.push(`${tag.key}="${tag.value}"`);
}
} }
return acc; return acc;
}, [] as string[]); }, [] as string[]);

View File

@ -1,7 +1,7 @@
import { DataSourcePluginOptionsEditorProps } from '@grafana/data'; import { DataSourcePluginOptionsEditorProps } from '@grafana/data';
import { DataSourceHttpSettings } from '@grafana/ui'; import { DataSourceHttpSettings } from '@grafana/ui';
import { NodeGraphSettings } from 'app/core/components/NodeGraphSettings'; import { NodeGraphSettings } from 'app/core/components/NodeGraphSettings';
import { TraceToLogsSettings } from 'app/core/components/TraceToLogsSettings'; import { TraceToLogsSettings } from 'app/core/components/TraceToLogs/TraceToLogsSettings';
import React from 'react'; import React from 'react';
export type Props = DataSourcePluginOptionsEditorProps; export type Props = DataSourcePluginOptionsEditorProps;

View File

@ -12,7 +12,7 @@ import {
Themeable2, Themeable2,
withTheme2, withTheme2,
} from '@grafana/ui'; } from '@grafana/ui';
import { TraceToLogsOptions } from 'app/core/components/TraceToLogsSettings'; import { TraceToLogsOptions } from 'app/core/components/TraceToLogs/TraceToLogsSettings';
import React from 'react'; import React from 'react';
import { LokiQueryField } from '../../loki/components/LokiQueryField'; import { LokiQueryField } from '../../loki/components/LokiQueryField';
import { LokiQuery } from '../../loki/types'; import { LokiQuery } from '../../loki/types';

View File

@ -1,6 +1,6 @@
import { DataSourcePluginOptionsEditorProps } from '@grafana/data'; import { DataSourcePluginOptionsEditorProps } from '@grafana/data';
import { DataSourceHttpSettings } from '@grafana/ui'; import { DataSourceHttpSettings } from '@grafana/ui';
import { TraceToLogsSettings } from 'app/core/components/TraceToLogsSettings'; import { TraceToLogsSettings } from 'app/core/components/TraceToLogs/TraceToLogsSettings';
import React from 'react'; import React from 'react';
import { ServiceGraphSettings } from './ServiceGraphSettings'; import { ServiceGraphSettings } from './ServiceGraphSettings';
import { config } from '@grafana/runtime'; import { config } from '@grafana/runtime';

View File

@ -10,7 +10,7 @@ import {
isValidGoDuration, isValidGoDuration,
LoadingState, LoadingState,
} from '@grafana/data'; } from '@grafana/data';
import { TraceToLogsOptions } from 'app/core/components/TraceToLogsSettings'; import { TraceToLogsOptions } from 'app/core/components/TraceToLogs/TraceToLogsSettings';
import { config, BackendSrvRequest, DataSourceWithBackend, getBackendSrv } from '@grafana/runtime'; import { config, BackendSrvRequest, DataSourceWithBackend, getBackendSrv } from '@grafana/runtime';
import { serializeParams } from 'app/core/utils/fetch'; import { serializeParams } from 'app/core/utils/fetch';
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';

View File

@ -1,7 +1,7 @@
import { DataSourcePluginOptionsEditorProps } from '@grafana/data'; import { DataSourcePluginOptionsEditorProps } from '@grafana/data';
import { DataSourceHttpSettings } from '@grafana/ui'; import { DataSourceHttpSettings } from '@grafana/ui';
import { NodeGraphSettings } from 'app/core/components/NodeGraphSettings'; import { NodeGraphSettings } from 'app/core/components/NodeGraphSettings';
import { TraceToLogsSettings } from 'app/core/components/TraceToLogsSettings'; import { TraceToLogsSettings } from 'app/core/components/TraceToLogs/TraceToLogsSettings';
import React from 'react'; import React from 'react';
export type Props = DataSourcePluginOptionsEditorProps; export type Props = DataSourcePluginOptionsEditorProps;