mirror of
https://github.com/grafana/grafana.git
synced 2025-01-05 21:53:45 -06:00
Zipkin: add ability to upload trace json (#37483)
* Zipkin: upload json * Change back mock json
This commit is contained in:
parent
0563bc68fc
commit
44c7e65fac
@ -32,7 +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'`.
|
||||
- **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. Time units can be used here, for example, 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.
|
||||
|
||||
![Trace to logs settings](/static/img/docs/explore/trace-to-logs-settings-8.png 'Screenshot of the trace to logs settings')
|
||||
@ -57,6 +57,36 @@ Use the trace selector to pick particular trace from all traces logged in the ti
|
||||
|
||||
Zipkin annotations are shown in the trace view as logs with annotation value shown under annotation key.
|
||||
|
||||
## Upload JSON trace file
|
||||
|
||||
You can upload a JSON file that contains a single trace to visualize it.
|
||||
|
||||
{{< figure src="/static/img/docs/explore/zipkin-upload-json.png" class="docs-image--no-shadow" caption="Screenshot of the Zipkin data source in explore with upload selected" >}}
|
||||
|
||||
Here is an example JSON:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"traceId": "efe9cb8857f68c8f",
|
||||
"parentId": "efe9cb8857f68c8f",
|
||||
"id": "8608dc6ce5cafe8e",
|
||||
"kind": "SERVER",
|
||||
"name": "get /api",
|
||||
"timestamp": 1627975249601797,
|
||||
"duration": 23457,
|
||||
"localEndpoint": { "serviceName": "backend", "ipv4": "127.0.0.1", "port": 9000 },
|
||||
"tags": {
|
||||
"http.method": "GET",
|
||||
"http.path": "/api",
|
||||
"jaxrs.resource.class": "Resource",
|
||||
"jaxrs.resource.method": "printDate"
|
||||
},
|
||||
"shared": true
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Linking Trace ID from logs
|
||||
|
||||
You can link to Zipkin trace from logs in Loki by configuring a derived field with internal link. See [Loki documentation]({{< relref "loki#derived-fields" >}}) for details.
|
||||
|
@ -1,9 +1,10 @@
|
||||
import React from 'react';
|
||||
import { QueryField, useLoadOptions, useServices } from './QueryField';
|
||||
import { ZipkinDatasource, ZipkinQuery } from './datasource';
|
||||
import { shallow } from 'enzyme';
|
||||
import { ButtonCascader, CascaderOption } from '@grafana/ui';
|
||||
import { renderHook, act } from '@testing-library/react-hooks';
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
import { ZipkinDatasource } from './datasource';
|
||||
import { QueryField, useLoadOptions, useServices } from './QueryField';
|
||||
import { ZipkinQuery } from './types';
|
||||
|
||||
describe('QueryField', () => {
|
||||
it('renders properly', () => {
|
||||
|
@ -1,21 +1,31 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { ExploreQueryFieldProps } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { ButtonCascader, CascaderOption } from '@grafana/ui';
|
||||
import {
|
||||
ButtonCascader,
|
||||
CascaderOption,
|
||||
FileDropzone,
|
||||
InlineField,
|
||||
InlineFieldRow,
|
||||
RadioButtonGroup,
|
||||
useTheme2,
|
||||
} from '@grafana/ui';
|
||||
import { notifyApp } from 'app/core/actions';
|
||||
import { createErrorNotification } from 'app/core/copy/appNotification';
|
||||
import { dispatch } from 'app/store/store';
|
||||
import { fromPairs } from 'lodash';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { useAsyncFn, useMount, useMountedState } from 'react-use';
|
||||
import { AsyncState } from 'react-use/lib/useAsyncFn';
|
||||
import { apiPrefix } from './constants';
|
||||
import { ZipkinDatasource, ZipkinQuery } from './datasource';
|
||||
import { ZipkinSpan } from './types';
|
||||
import { dispatch } from 'app/store/store';
|
||||
import { notifyApp } from 'app/core/actions';
|
||||
import { createErrorNotification } from 'app/core/copy/appNotification';
|
||||
import { ZipkinDatasource } from './datasource';
|
||||
import { ZipkinQuery, ZipkinQueryType, ZipkinSpan } from './types';
|
||||
|
||||
type Props = ExploreQueryFieldProps<ZipkinDatasource, ZipkinQuery>;
|
||||
|
||||
export const QueryField = ({ query, onChange, onRunQuery, datasource }: Props) => {
|
||||
const serviceOptions = useServices(datasource);
|
||||
const theme = useTheme2();
|
||||
const { onLoadOptions, allOptions } = useLoadOptions(datasource);
|
||||
|
||||
const onSelectTrace = useCallback(
|
||||
@ -33,29 +43,59 @@ export const QueryField = ({ query, onChange, onRunQuery, datasource }: Props) =
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="gf-form-inline gf-form-inline--nowrap">
|
||||
<div className="gf-form flex-shrink-0">
|
||||
<ButtonCascader options={cascaderOptions} onChange={onSelectTrace} loadData={onLoadOptions}>
|
||||
Traces
|
||||
</ButtonCascader>
|
||||
<InlineFieldRow>
|
||||
<InlineField label="Query type">
|
||||
<RadioButtonGroup<ZipkinQueryType>
|
||||
options={[
|
||||
{ value: 'traceID', label: 'TraceID' },
|
||||
{ value: 'upload', label: 'JSON file' },
|
||||
]}
|
||||
value={query.queryType || 'traceID'}
|
||||
onChange={(v) =>
|
||||
onChange({
|
||||
...query,
|
||||
queryType: v,
|
||||
})
|
||||
}
|
||||
size="md"
|
||||
/>
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
{query.queryType === 'upload' ? (
|
||||
<div className={css({ padding: theme.spacing(2) })}>
|
||||
<FileDropzone
|
||||
options={{ multiple: false }}
|
||||
onLoad={(result) => {
|
||||
datasource.uploadedJson = result;
|
||||
onRunQuery();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="gf-form gf-form--grow flex-shrink-1">
|
||||
<div className="slate-query-field__wrapper">
|
||||
<div className="slate-query-field" aria-label={selectors.components.QueryField.container}>
|
||||
<input
|
||||
style={{ width: '100%' }}
|
||||
value={query.query || ''}
|
||||
onChange={(e) =>
|
||||
onChange({
|
||||
...query,
|
||||
query: e.currentTarget.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<div className="gf-form-inline gf-form-inline--nowrap">
|
||||
<div className="gf-form flex-shrink-0">
|
||||
<ButtonCascader options={cascaderOptions} onChange={onSelectTrace} loadData={onLoadOptions}>
|
||||
Traces
|
||||
</ButtonCascader>
|
||||
</div>
|
||||
<div className="gf-form gf-form--grow flex-shrink-1">
|
||||
<div className="slate-query-field__wrapper">
|
||||
<div className="slate-query-field" aria-label={selectors.components.QueryField.container}>
|
||||
<input
|
||||
style={{ width: '100%' }}
|
||||
value={query.query || ''}
|
||||
onChange={(e) =>
|
||||
onChange({
|
||||
...query,
|
||||
query: e.currentTarget.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { DataSourceInstanceSettings } from '@grafana/data';
|
||||
import { DataSourceInstanceSettings, FieldType } from '@grafana/data';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { of } from 'rxjs';
|
||||
import { createFetchResponse } from 'test/helpers/createFetchResponse';
|
||||
import { ZipkinDatasource } from './datasource';
|
||||
import mockJson from './mockJsonResponse.json';
|
||||
import { traceFrameFields, zipkinResponse } from './utils/testData';
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
@ -26,6 +27,20 @@ describe('ZipkinDatasource', () => {
|
||||
expect(val[0].data[0].fields).toMatchObject(traceFrameFields);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle json file upload', async () => {
|
||||
const ds = new ZipkinDatasource(defaultSettings);
|
||||
ds.uploadedJson = JSON.stringify(mockJson);
|
||||
const response = await ds
|
||||
.query({
|
||||
targets: [{ queryType: 'upload', refId: 'A' }],
|
||||
} as any)
|
||||
.toPromise();
|
||||
const field = response.data[0].fields[0];
|
||||
expect(field.name).toBe('traceID');
|
||||
expect(field.type).toBe(FieldType.string);
|
||||
expect(field.values.length).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('metadataRequest', () => {
|
||||
|
@ -1,5 +1,4 @@
|
||||
import {
|
||||
DataQuery,
|
||||
DataQueryRequest,
|
||||
DataQueryResponse,
|
||||
DataSourceApi,
|
||||
@ -12,28 +11,32 @@ import { Observable, of } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { serializeParams } from '../../../core/utils/fetch';
|
||||
import { apiPrefix } from './constants';
|
||||
import { ZipkinSpan } from './types';
|
||||
import { transformResponse } from './utils/transforms';
|
||||
import { ZipkinQuery, ZipkinSpan } from './types';
|
||||
import { createGraphFrames } from './utils/graphTransform';
|
||||
|
||||
export interface ZipkinQuery extends DataQuery {
|
||||
query: string;
|
||||
}
|
||||
import { transformResponse } from './utils/transforms';
|
||||
|
||||
export class ZipkinDatasource extends DataSourceApi<ZipkinQuery> {
|
||||
uploadedJson: string | ArrayBuffer | null = null;
|
||||
constructor(private instanceSettings: DataSourceInstanceSettings) {
|
||||
super(instanceSettings);
|
||||
}
|
||||
|
||||
query(options: DataQueryRequest<ZipkinQuery>): Observable<DataQueryResponse> {
|
||||
const traceId = options.targets[0]?.query;
|
||||
if (traceId) {
|
||||
return this.request<ZipkinSpan[]>(`${apiPrefix}/trace/${encodeURIComponent(traceId)}`).pipe(
|
||||
const target = options.targets[0];
|
||||
if (target.queryType === 'upload') {
|
||||
if (!this.uploadedJson) {
|
||||
return of({ data: [] });
|
||||
}
|
||||
const traceData = JSON.parse(this.uploadedJson as string);
|
||||
return of(responseToDataQueryResponse({ data: traceData }));
|
||||
}
|
||||
|
||||
if (target.query) {
|
||||
return this.request<ZipkinSpan[]>(`${apiPrefix}/trace/${encodeURIComponent(target.query)}`).pipe(
|
||||
map(responseToDataQueryResponse)
|
||||
);
|
||||
} else {
|
||||
return of(emptyDataQueryResponse);
|
||||
}
|
||||
return of(emptyDataQueryResponse);
|
||||
}
|
||||
|
||||
async metadataRequest(url: string, params?: Record<string, any>): Promise<any> {
|
||||
|
45
public/app/plugins/datasource/zipkin/mockJsonResponse.json
Normal file
45
public/app/plugins/datasource/zipkin/mockJsonResponse.json
Normal file
@ -0,0 +1,45 @@
|
||||
[
|
||||
{
|
||||
"traceId": "efe9cb8857f68c8f",
|
||||
"parentId": "efe9cb8857f68c8f",
|
||||
"id": "8608dc6ce5cafe8e",
|
||||
"kind": "SERVER",
|
||||
"name": "get /api",
|
||||
"timestamp": 1627975249601797,
|
||||
"duration": 23457,
|
||||
"localEndpoint": { "serviceName": "backend", "ipv4": "127.0.0.1", "port": 9000 },
|
||||
"tags": {
|
||||
"http.method": "GET",
|
||||
"http.path": "/api",
|
||||
"jaxrs.resource.class": "Resource",
|
||||
"jaxrs.resource.method": "printDate"
|
||||
},
|
||||
"shared": true
|
||||
},
|
||||
{
|
||||
"traceId": "efe9cb8857f68c8f",
|
||||
"parentId": "efe9cb8857f68c8f",
|
||||
"id": "8608dc6ce5cafe8e",
|
||||
"kind": "CLIENT",
|
||||
"name": "get",
|
||||
"timestamp": 1627975249491667,
|
||||
"duration": 148265,
|
||||
"localEndpoint": { "serviceName": "frontend", "ipv4": "127.0.0.1", "port": 8081 },
|
||||
"tags": { "http.method": "GET", "http.path": "/api" }
|
||||
},
|
||||
{
|
||||
"traceId": "efe9cb8857f68c8f",
|
||||
"id": "efe9cb8857f68c8f",
|
||||
"kind": "SERVER",
|
||||
"name": "get",
|
||||
"timestamp": 1627975249258448,
|
||||
"duration": 413469,
|
||||
"localEndpoint": { "serviceName": "frontend", "ipv4": "127.0.0.1", "port": 8081 },
|
||||
"tags": {
|
||||
"http.method": "GET",
|
||||
"http.path": "/",
|
||||
"jaxrs.resource.class": "Resource",
|
||||
"jaxrs.resource.method": "callBackend"
|
||||
}
|
||||
}
|
||||
]
|
@ -1,3 +1,5 @@
|
||||
import { DataQuery } from '@grafana/data';
|
||||
|
||||
export type ZipkinSpan = {
|
||||
traceId: string;
|
||||
parentId?: string;
|
||||
@ -23,3 +25,9 @@ export type ZipkinAnnotation = {
|
||||
timestamp: number;
|
||||
value: string;
|
||||
};
|
||||
export type ZipkinQueryType = 'traceID' | 'upload';
|
||||
|
||||
export interface ZipkinQuery extends DataQuery {
|
||||
query: string;
|
||||
queryType?: ZipkinQueryType;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user