123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364 |
- import { SpanLinks } from '@jaegertracing/jaeger-ui-components/src/types/links';
- import React from 'react';
- import {
- DataFrame,
- DataLink,
- DataQuery,
- DataSourceInstanceSettings,
- DataSourceJsonData,
- dateTime,
- Field,
- KeyValue,
- LinkModel,
- mapInternalLinkToExplore,
- rangeUtil,
- SplitOpen,
- TimeRange,
- } from '@grafana/data';
- import { getTemplateSrv } from '@grafana/runtime';
- import { Icon } from '@grafana/ui';
- import { SpanLinkFunc, TraceSpan } from '@jaegertracing/jaeger-ui-components';
- import { TraceToLogsOptions } from 'app/core/components/TraceToLogs/TraceToLogsSettings';
- import { TraceToMetricsOptions } from 'app/core/components/TraceToMetrics/TraceToMetricsSettings';
- import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
- import { PromQuery } from 'app/plugins/datasource/prometheus/types';
- import { LokiQuery } from '../../../plugins/datasource/loki/types';
- import { getFieldLinksForExplore } from '../utils/links';
- /**
- * This is a factory for the link creator. It returns the function mainly so it can return undefined in which case
- * the trace view won't create any links and to capture the datasource and split function making it easier to memoize
- * with useMemo.
- */
- export function createSpanLinkFactory({
- splitOpenFn,
- traceToLogsOptions,
- traceToMetricsOptions,
- dataFrame,
- createFocusSpanLink,
- }: {
- splitOpenFn: SplitOpen;
- traceToLogsOptions?: TraceToLogsOptions;
- traceToMetricsOptions?: TraceToMetricsOptions;
- dataFrame?: DataFrame;
- createFocusSpanLink?: (traceId: string, spanId: string) => LinkModel<Field>;
- }): SpanLinkFunc | undefined {
- if (!dataFrame || dataFrame.fields.length === 1 || !dataFrame.fields.some((f) => Boolean(f.config.links?.length))) {
- // if the dataframe contains just a single blob of data (legacy format) or does not have any links configured,
- // let's try to use the old legacy path.
- return legacyCreateSpanLinkFactory(splitOpenFn, traceToLogsOptions, traceToMetricsOptions, createFocusSpanLink);
- } else {
- return function SpanLink(span: TraceSpan): SpanLinks | undefined {
- // We should be here only if there are some links in the dataframe
- const field = dataFrame.fields.find((f) => Boolean(f.config.links?.length))!;
- try {
- const links = getFieldLinksForExplore({
- field,
- rowIndex: span.dataFrameRowIndex!,
- splitOpenFn,
- range: getTimeRangeFromSpan(span),
- dataFrame,
- });
- return {
- logLinks: [
- {
- href: links[0].href,
- onClick: links[0].onClick,
- content: <Icon name="gf-logs" title="Explore the logs for this in split view" />,
- },
- ],
- };
- } catch (error) {
- // It's fairly easy to crash here for example if data source defines wrong interpolation in the data link
- console.error(error);
- return undefined;
- }
- };
- }
- }
- function legacyCreateSpanLinkFactory(
- splitOpenFn: SplitOpen,
- traceToLogsOptions?: TraceToLogsOptions,
- traceToMetricsOptions?: TraceToMetricsOptions,
- createFocusSpanLink?: (traceId: string, spanId: string) => LinkModel<Field>
- ) {
- let logsDataSourceSettings: DataSourceInstanceSettings<DataSourceJsonData> | undefined;
- if (traceToLogsOptions?.datasourceUid) {
- logsDataSourceSettings = getDatasourceSrv().getInstanceSettings(traceToLogsOptions.datasourceUid);
- }
- const isSplunkDS = logsDataSourceSettings?.type === 'grafana-splunk-datasource';
- let metricsDataSourceSettings: DataSourceInstanceSettings<DataSourceJsonData> | undefined;
- if (traceToMetricsOptions?.datasourceUid) {
- metricsDataSourceSettings = getDatasourceSrv().getInstanceSettings(traceToMetricsOptions.datasourceUid);
- }
- return function SpanLink(span: TraceSpan): SpanLinks {
- const links: SpanLinks = { traceLinks: [] };
- // This is reusing existing code from derived fields which may not be ideal match so some data is a bit faked at
- // 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;
- // Get logs link
- if (logsDataSourceSettings && traceToLogsOptions) {
- switch (logsDataSourceSettings?.type) {
- case 'loki':
- dataLink = getLinkForLoki(span, traceToLogsOptions, logsDataSourceSettings);
- break;
- case 'grafana-splunk-datasource':
- dataLink = getLinkForSplunk(span, traceToLogsOptions, logsDataSourceSettings);
- break;
- }
- if (dataLink) {
- const link = mapInternalLinkToExplore({
- link: dataLink,
- internalLink: dataLink.internal!,
- scopedVars: {},
- range: getTimeRangeFromSpan(
- span,
- {
- startMs: traceToLogsOptions.spanStartTimeShift
- ? rangeUtil.intervalToMs(traceToLogsOptions.spanStartTimeShift)
- : 0,
- endMs: traceToLogsOptions.spanEndTimeShift
- ? rangeUtil.intervalToMs(traceToLogsOptions.spanEndTimeShift)
- : 0,
- },
- isSplunkDS
- ),
- field: {} as Field,
- onClickFn: splitOpenFn,
- replaceVariables: getTemplateSrv().replace.bind(getTemplateSrv()),
- });
- links.logLinks = [
- {
- href: link.href,
- onClick: link.onClick,
- content: <Icon name="gf-logs" title="Explore the logs for this in split view" />,
- },
- ];
- }
- }
- // Get metrics links
- if (metricsDataSourceSettings && traceToMetricsOptions?.queries) {
- const defaultQuery = `histogram_quantile(0.5, sum(rate(tempo_spanmetrics_latency_bucket{operation="${span.operationName}"}[5m])) by (le))`;
- links.metricLinks = [];
- for (const query of traceToMetricsOptions.queries) {
- const dataLink: DataLink<PromQuery> = {
- title: metricsDataSourceSettings.name,
- url: '',
- internal: {
- datasourceUid: metricsDataSourceSettings.uid,
- datasourceName: metricsDataSourceSettings.name,
- query: {
- expr: query.query || defaultQuery,
- refId: 'A',
- },
- },
- };
- const link = mapInternalLinkToExplore({
- link: dataLink,
- internalLink: dataLink.internal!,
- scopedVars: {},
- range: getTimeRangeFromSpan(span, {
- startMs: 0,
- endMs: 0,
- }),
- field: {} as Field,
- onClickFn: splitOpenFn,
- replaceVariables: getTemplateSrv().replace.bind(getTemplateSrv()),
- });
- links.metricLinks.push({
- title: query?.name,
- href: link.href,
- onClick: link.onClick,
- content: <Icon name="chart-line" title="Explore metrics for this span" />,
- });
- }
- }
- // Get trace links
- if (span.references && createFocusSpanLink) {
- for (const reference of span.references) {
- // Ignore parent-child links
- if (reference.refType === 'CHILD_OF') {
- continue;
- }
- const link = createFocusSpanLink(reference.traceID, reference.spanID);
- links.traceLinks!.push({
- href: link.href,
- title: reference.span ? reference.span.operationName : 'View linked span',
- content: <Icon name="link" title="View linked span" />,
- onClick: link.onClick,
- });
- }
- }
- if (span.subsidiarilyReferencedBy && createFocusSpanLink) {
- for (const reference of span.subsidiarilyReferencedBy) {
- const link = createFocusSpanLink(reference.traceID, reference.spanID);
- links.traceLinks!.push({
- href: link.href,
- title: reference.span ? reference.span.operationName : 'View linked span',
- content: <Icon name="link" title="View linked span" />,
- onClick: link.onClick,
- });
- }
- }
- return links;
- };
- }
- /**
- * Default keys to use when there are no configured tags.
- */
- const defaultKeys = ['cluster', 'hostname', 'namespace', 'pod'];
- function getLinkForLoki(span: TraceSpan, options: TraceToLogsOptions, dataSourceSettings: DataSourceInstanceSettings) {
- 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 (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[]);
- // If no tags found, return undefined to prevent an invalid Loki query
- if (!tags.length) {
- return undefined;
- }
- let expr = `{${tags.join(', ')}}`;
- if (filterByTraceID && span.traceID) {
- expr += ` |="${span.traceID}"`;
- }
- if (filterBySpanID && span.spanID) {
- expr += ` |="${span.spanID}"`;
- }
- const dataLink: DataLink<LokiQuery> = {
- title: dataSourceSettings.name,
- url: '',
- internal: {
- datasourceUid: dataSourceSettings.uid,
- datasourceName: dataSourceSettings.name,
- query: {
- expr: expr,
- refId: '',
- },
- },
- };
- return dataLink;
- }
- function getLinkForSplunk(
- span: TraceSpan,
- options: TraceToLogsOptions,
- dataSourceSettings: DataSourceInstanceSettings
- ) {
- 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 (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[]);
- let query = '';
- if (tags.length > 0) {
- query += `${tags.join(' ')}`;
- }
- if (filterByTraceID && span.traceID) {
- query += ` "${span.traceID}"`;
- }
- if (filterBySpanID && span.spanID) {
- query += ` "${span.spanID}"`;
- }
- const dataLink: DataLink<DataQuery> = {
- title: dataSourceSettings.name,
- url: '',
- internal: {
- datasourceUid: dataSourceSettings.uid,
- datasourceName: dataSourceSettings.name,
- query: {
- query: query,
- refId: '',
- },
- },
- } as DataLink<DataQuery>;
- return dataLink;
- }
- /**
- * Gets a time range from the span.
- */
- function getTimeRangeFromSpan(
- span: TraceSpan,
- timeShift: { startMs: number; endMs: number } = { startMs: 0, endMs: 0 },
- isSplunkDS = false
- ): TimeRange {
- const adjustedStartTime = Math.floor(span.startTime / 1000 + timeShift.startMs);
- const from = dateTime(adjustedStartTime);
- const spanEndMs = (span.startTime + span.duration) / 1000;
- let adjustedEndTime = Math.floor(spanEndMs + timeShift.endMs);
- // Splunk requires a time interval of >= 1s, rather than >=1ms like Loki timerange in below elseif block
- if (isSplunkDS && adjustedEndTime - adjustedStartTime < 1000) {
- adjustedEndTime = adjustedStartTime + 1000;
- } else if (adjustedStartTime === adjustedEndTime) {
- // Because we can only pass milliseconds in the url we need to check if they equal.
- // We need end time to be later than start time
- adjustedEndTime++;
- }
- const to = dateTime(adjustedEndTime);
- // Beware that public/app/features/explore/state/main.ts SplitOpen fn uses the range from here. No matter what is in the url.
- return {
- from,
- to,
- raw: {
- from,
- to,
- },
- };
- }
|