mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
6165377eb2
commit
af2d19b02e
@ -32,6 +32,7 @@ This is a configuration for the [trace to logs feature]({{< relref "../explore/t
|
||||
|
||||
- **Data source -** Target data source.
|
||||
- **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 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.
|
||||
@ -147,12 +148,15 @@ datasources:
|
||||
tracesToLogs:
|
||||
# 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: loki
|
||||
tags:
|
||||
- cluster
|
||||
- hostname
|
||||
- namespace
|
||||
- pod
|
||||
datasourceUid: 'loki'
|
||||
tags: ['job', 'instance', 'pod', 'namespace']
|
||||
mappedTags: [{ key: 'service.name', value: 'service' }]
|
||||
mapTagNamesEnabled: false
|
||||
spanStartTimeShift: '1h'
|
||||
spanEndTimeShift: '1h'
|
||||
filterByTraceID: false
|
||||
filterBySpanID: false
|
||||
lokiSearch: true
|
||||
secureJsonData:
|
||||
basicAuthPassword: my_password
|
||||
```
|
||||
|
@ -31,6 +31,7 @@ This is a configuration for the [trace to logs feature]({{< relref "../explore/t
|
||||
|
||||
- **Data source -** Target data source.
|
||||
- **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 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.
|
||||
@ -167,6 +168,8 @@ datasources:
|
||||
tracesToLogs:
|
||||
datasourceUid: 'loki'
|
||||
tags: ['job', 'instance', 'pod', 'namespace']
|
||||
mappedTags: [{ key: 'service.name', value: 'service' }]
|
||||
mapTagNamesEnabled: false
|
||||
spanStartTimeShift: '1h'
|
||||
spanEndTimeShift: '1h'
|
||||
filterByTraceID: false
|
||||
|
@ -32,6 +32,7 @@ This is a configuration for the [trace to logs feature]({{< relref "../explore/t
|
||||
|
||||
- **Data source -** Target data source.
|
||||
- **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 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.
|
||||
|
110
public/app/core/components/TraceToLogs/KeyValueInput.tsx
Normal file
110
public/app/core/components/TraceToLogs/KeyValueInput.tsx
Normal 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};
|
||||
`,
|
||||
});
|
@ -3,15 +3,19 @@ import {
|
||||
DataSourceJsonData,
|
||||
DataSourcePluginOptionsEditorProps,
|
||||
GrafanaTheme,
|
||||
KeyValue,
|
||||
updateDatasourcePluginJsonDataOption,
|
||||
} from '@grafana/data';
|
||||
import { DataSourcePicker } from '@grafana/runtime';
|
||||
import { InlineField, InlineFieldRow, Input, TagsInput, useStyles, InlineSwitch } from '@grafana/ui';
|
||||
import React from 'react';
|
||||
import KeyValueInput from './KeyValueInput';
|
||||
|
||||
export interface TraceToLogsOptions {
|
||||
datasourceUid?: string;
|
||||
tags?: string[];
|
||||
mappedTags?: Array<KeyValue<string>>;
|
||||
mapTagNamesEnabled?: boolean;
|
||||
spanStartTimeShift?: string;
|
||||
spanEndTimeShift?: string;
|
||||
filterByTraceID?: boolean;
|
||||
@ -54,19 +58,64 @@ export function TraceToLogsSettings({ options, onOptionsChange }: Props) {
|
||||
</InlineField>
|
||||
</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>
|
||||
<InlineField
|
||||
tooltip="Tags that will be used in the Loki query. Default tags: 'cluster', 'hostname', 'namespace', 'pod'"
|
||||
label="Tags"
|
||||
label="Map tag names"
|
||||
labelWidth={26}
|
||||
grow
|
||||
tooltip="Map trace tag names to log label names. Ex: k8s.pod.name -> pod"
|
||||
>
|
||||
<TagsInput
|
||||
tags={options.jsonData.tracesToLogs?.tags}
|
||||
width={40}
|
||||
onChange={(tags) =>
|
||||
<InlineSwitch
|
||||
id="mapTagNames"
|
||||
value={options.jsonData.tracesToLogs?.mapTagNamesEnabled ?? false}
|
||||
onChange={(event: React.SyntheticEvent<HTMLInputElement>) =>
|
||||
updateDatasourcePluginJsonDataOption({ onOptionsChange, options }, 'tracesToLogs', {
|
||||
datasourceUid: options.jsonData.tracesToLogs?.datasourceUid,
|
||||
tags: tags,
|
||||
...options.jsonData.tracesToLogs,
|
||||
mapTagNamesEnabled: event.currentTarget.checked,
|
||||
})
|
||||
}
|
||||
/>
|
||||
@ -156,6 +205,7 @@ export function TraceToLogsSettings({ options, onOptionsChange }: Props) {
|
||||
/>
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
|
||||
<InlineFieldRow>
|
||||
<InlineField label="Loki Search" labelWidth={26} grow tooltip="Use this logs data source to search for traces.">
|
||||
<InlineSwitch
|
@ -19,7 +19,7 @@ import {
|
||||
transformTraceData,
|
||||
TTraceTimeline,
|
||||
} 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 { getTimeZone } from 'app/features/profile/state/selectors';
|
||||
import { StoreState } from 'app/types';
|
||||
|
@ -2,7 +2,7 @@ import { DataSourceInstanceSettings, MutableDataFrame } from '@grafana/data';
|
||||
import { setDataSourceSrv, setTemplateSrv } from '@grafana/runtime';
|
||||
import { createSpanLinkFactory } from './createSpanLink';
|
||||
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 { TemplateSrv } from '../../templating/template_srv';
|
||||
|
||||
@ -141,6 +141,60 @@ describe('createSpanLinkFactory', () => {
|
||||
|
||||
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":{}}'
|
||||
)}`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -3,6 +3,7 @@ import {
|
||||
DataLink,
|
||||
dateTime,
|
||||
Field,
|
||||
KeyValue,
|
||||
mapInternalLinkToExplore,
|
||||
rangeUtil,
|
||||
SplitOpen,
|
||||
@ -11,7 +12,7 @@ import {
|
||||
import { getTemplateSrv } from '@grafana/runtime';
|
||||
import { Icon } from '@grafana/ui';
|
||||
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 React from 'react';
|
||||
import { LokiQuery } from '../../../plugins/datasource/loki/types';
|
||||
@ -122,11 +123,22 @@ function legacyCreateSpanLinkFactory(splitOpenFn: SplitOpen, traceToLogsOptions?
|
||||
const defaultKeys = ['cluster', 'hostname', 'namespace', 'pod'];
|
||||
|
||||
function getLokiQueryFromSpan(span: TraceSpan, options: TraceToLogsOptions): string {
|
||||
const { tags: keys, filterByTraceID, filterBySpanID } = options;
|
||||
const keysToCheck = keys?.length ? keys : defaultKeys;
|
||||
const { tags: keys, filterByTraceID, filterBySpanID, mapTagNamesEnabled, mappedTags } = options;
|
||||
|
||||
// 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) => {
|
||||
if (keysToCheck.includes(tag.key)) {
|
||||
acc.push(`${tag.key}="${tag.value}"`);
|
||||
if (mapTagNamesEnabled) {
|
||||
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;
|
||||
}, [] as string[]);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { DataSourcePluginOptionsEditorProps } from '@grafana/data';
|
||||
import { DataSourceHttpSettings } from '@grafana/ui';
|
||||
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';
|
||||
|
||||
export type Props = DataSourcePluginOptionsEditorProps;
|
||||
|
@ -12,7 +12,7 @@ import {
|
||||
Themeable2,
|
||||
withTheme2,
|
||||
} from '@grafana/ui';
|
||||
import { TraceToLogsOptions } from 'app/core/components/TraceToLogsSettings';
|
||||
import { TraceToLogsOptions } from 'app/core/components/TraceToLogs/TraceToLogsSettings';
|
||||
import React from 'react';
|
||||
import { LokiQueryField } from '../../loki/components/LokiQueryField';
|
||||
import { LokiQuery } from '../../loki/types';
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { DataSourcePluginOptionsEditorProps } from '@grafana/data';
|
||||
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 { ServiceGraphSettings } from './ServiceGraphSettings';
|
||||
import { config } from '@grafana/runtime';
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
isValidGoDuration,
|
||||
LoadingState,
|
||||
} 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 { serializeParams } from 'app/core/utils/fetch';
|
||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { DataSourcePluginOptionsEditorProps } from '@grafana/data';
|
||||
import { DataSourceHttpSettings } from '@grafana/ui';
|
||||
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';
|
||||
|
||||
export type Props = DataSourcePluginOptionsEditorProps;
|
||||
|
Loading…
Reference in New Issue
Block a user