mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Tempo: Represent OTLP Span Intrinsics correctly (#69394)
* Span intrinsics * Update intrinsics and add to span details * Remove intrinsics section * Update tests * Update status code text * Self review * Move previously intrinsic values to span * Remove few methods
This commit is contained in:
parent
e9d42a6395
commit
00ec9fceb9
@ -4642,11 +4642,9 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Do not use any type assertions.", "4"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "5"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "6"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "7"],
|
||||
[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, "Unexpected any. Specify a different type.", "11"]
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "9"]
|
||||
],
|
||||
"public/app/plugins/datasource/tempo/testResponse.ts:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"],
|
||||
|
@ -152,7 +152,7 @@ You can choose one of three options:
|
||||
| ------------ | -------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **None** | Adds nothing to the span bar row. |
|
||||
| **Duration** | _(Default)_ Displays the span duration on the span bar row. |
|
||||
| **Tag** | Displays the span tag on the span bar row. You must also specify which tag key to use to get the tag value, such as `span.kind`. |
|
||||
| **Tag** | Displays the span tag on the span bar row. You must also specify which tag key to use to get the tag value, such as `component`. |
|
||||
|
||||
### Provision the data source
|
||||
|
||||
|
@ -185,7 +185,7 @@ You can choose one of three options:
|
||||
| ------------ | -------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **None** | Adds nothing to the span bar row. |
|
||||
| **Duration** | _(Default)_ Displays the span duration on the span bar row. |
|
||||
| **Tag** | Displays the span tag on the span bar row. You must also specify which tag key to use to get the tag value, such as `span.kind`. |
|
||||
| **Tag** | Displays the span tag on the span bar row. You must also specify which tag key to use to get the tag value, such as `component`. |
|
||||
|
||||
### Provision the data source
|
||||
|
||||
|
@ -150,7 +150,7 @@ You can choose one of three options:
|
||||
| ------------ | -------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **None** | Adds nothing to the span bar row. |
|
||||
| **Duration** | _(Default)_ Displays the span duration on the span bar row. |
|
||||
| **Tag** | Displays the span tag on the span bar row. You must also specify which tag key to use to get the tag value, such as `span.kind`. |
|
||||
| **Tag** | Displays the span tag on the span bar row. You must also specify which tag key to use to get the tag value, such as `component`. |
|
||||
|
||||
### Provision the data source
|
||||
|
||||
|
@ -40,6 +40,12 @@ export interface TraceSpanRow {
|
||||
references?: TraceSpanReference[];
|
||||
// Note: To mark spen as having error add tag error: true
|
||||
tags?: TraceKeyValuePair[];
|
||||
kind?: string;
|
||||
statusCode?: number;
|
||||
statusMessage?: string;
|
||||
instrumentationLibraryName?: string;
|
||||
instrumentationLibraryVersion?: string;
|
||||
traceState?: string;
|
||||
warnings?: string[];
|
||||
stackTraces?: string[];
|
||||
|
||||
|
@ -46,6 +46,12 @@ func TraceToFrame(td pdata.Traces) (*data.Frame, error) {
|
||||
data.NewField("parentSpanID", nil, []string{}),
|
||||
data.NewField("operationName", nil, []string{}),
|
||||
data.NewField("serviceName", nil, []string{}),
|
||||
data.NewField("kind", nil, []string{}),
|
||||
data.NewField("statusCode", nil, []int64{}),
|
||||
data.NewField("statusMessage", nil, []string{}),
|
||||
data.NewField("instrumentationLibraryName", nil, []string{}),
|
||||
data.NewField("instrumentationLibraryVersion", nil, []string{}),
|
||||
data.NewField("traceState", nil, []string{}),
|
||||
data.NewField("serviceTags", nil, []json.RawMessage{}),
|
||||
data.NewField("startTime", nil, []float64{}),
|
||||
data.NewField("duration", nil, []float64{}),
|
||||
@ -119,12 +125,20 @@ func spanToSpanRow(span pdata.Span, libraryTags pdata.InstrumentationLibrary, re
|
||||
startTime := float64(span.StartTimestamp()) / 1_000_000
|
||||
serviceName, serviceTags := resourceToProcess(resource)
|
||||
|
||||
status := span.Status()
|
||||
statusCode := int64(status.Code())
|
||||
statusMessage := status.Message()
|
||||
|
||||
libraryName := libraryTags.Name()
|
||||
libraryVersion := libraryTags.Version()
|
||||
traceState := getTraceState(span.TraceState())
|
||||
|
||||
serviceTagsJson, err := json.Marshal(serviceTags)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal service tags: %w", err)
|
||||
}
|
||||
|
||||
spanTags, err := json.Marshal(getSpanTags(span, libraryTags))
|
||||
spanTags, err := json.Marshal(getSpanTags(span))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal span tags: %w", err)
|
||||
}
|
||||
@ -147,6 +161,12 @@ func spanToSpanRow(span pdata.Span, libraryTags pdata.InstrumentationLibrary, re
|
||||
parentSpanID,
|
||||
span.Name(),
|
||||
serviceName,
|
||||
getSpanKind(span.Kind()),
|
||||
statusCode,
|
||||
statusMessage,
|
||||
libraryName,
|
||||
libraryVersion,
|
||||
traceState,
|
||||
json.RawMessage(serviceTagsJson),
|
||||
startTime,
|
||||
float64(span.EndTimestamp()-span.StartTimestamp()) / 1_000_000,
|
||||
@ -192,56 +212,16 @@ func getAttributeVal(attr pdata.AttributeValue) interface{} {
|
||||
}
|
||||
}
|
||||
|
||||
func getSpanTags(span pdata.Span, instrumentationLibrary pdata.InstrumentationLibrary) []*KeyValue {
|
||||
func getSpanTags(span pdata.Span) []*KeyValue {
|
||||
var tags []*KeyValue
|
||||
|
||||
libraryTags := getTagsFromInstrumentationLibrary(instrumentationLibrary)
|
||||
if libraryTags != nil {
|
||||
tags = append(tags, libraryTags...)
|
||||
}
|
||||
span.Attributes().Range(func(key string, attr pdata.AttributeValue) bool {
|
||||
tags = append(tags, &KeyValue{Key: key, Value: getAttributeVal(attr)})
|
||||
return true
|
||||
})
|
||||
|
||||
status := span.Status()
|
||||
possibleNilTags := []*KeyValue{
|
||||
getTagFromSpanKind(span.Kind()),
|
||||
getTagFromStatusCode(status.Code()),
|
||||
getErrorTagFromStatusCode(status.Code()),
|
||||
getTagFromStatusMsg(status.Message()),
|
||||
getTagFromTraceState(span.TraceState()),
|
||||
}
|
||||
|
||||
for _, tag := range possibleNilTags {
|
||||
if tag != nil {
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
}
|
||||
return tags
|
||||
}
|
||||
|
||||
func getTagsFromInstrumentationLibrary(il pdata.InstrumentationLibrary) []*KeyValue {
|
||||
var keyValues []*KeyValue
|
||||
if ilName := il.Name(); ilName != "" {
|
||||
kv := &KeyValue{
|
||||
Key: conventions.InstrumentationLibraryName,
|
||||
Value: ilName,
|
||||
}
|
||||
keyValues = append(keyValues, kv)
|
||||
}
|
||||
if ilVersion := il.Version(); ilVersion != "" {
|
||||
kv := &KeyValue{
|
||||
Key: conventions.InstrumentationLibraryVersion,
|
||||
Value: ilVersion,
|
||||
}
|
||||
keyValues = append(keyValues, kv)
|
||||
}
|
||||
|
||||
return keyValues
|
||||
}
|
||||
|
||||
func getTagFromSpanKind(spanKind pdata.SpanKind) *KeyValue {
|
||||
func getSpanKind(spanKind pdata.SpanKind) string {
|
||||
var tagStr string
|
||||
switch spanKind {
|
||||
case pdata.SpanKindClient:
|
||||
@ -255,50 +235,17 @@ func getTagFromSpanKind(spanKind pdata.SpanKind) *KeyValue {
|
||||
case pdata.SpanKindInternal:
|
||||
tagStr = string(tracetranslator.OpenTracingSpanKindInternal)
|
||||
default:
|
||||
return nil
|
||||
return ""
|
||||
}
|
||||
|
||||
return &KeyValue{
|
||||
Key: tracetranslator.TagSpanKind,
|
||||
Value: tagStr,
|
||||
}
|
||||
return tagStr
|
||||
}
|
||||
|
||||
func getTagFromStatusCode(statusCode pdata.StatusCode) *KeyValue {
|
||||
return &KeyValue{
|
||||
Key: tracetranslator.TagStatusCode,
|
||||
Value: int64(statusCode),
|
||||
}
|
||||
}
|
||||
|
||||
func getErrorTagFromStatusCode(statusCode pdata.StatusCode) *KeyValue {
|
||||
if statusCode == pdata.StatusCodeError {
|
||||
return &KeyValue{
|
||||
Key: tracetranslator.TagError,
|
||||
Value: true,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getTagFromStatusMsg(statusMsg string) *KeyValue {
|
||||
if statusMsg == "" {
|
||||
return nil
|
||||
}
|
||||
return &KeyValue{
|
||||
Key: tracetranslator.TagStatusMsg,
|
||||
Value: statusMsg,
|
||||
}
|
||||
}
|
||||
|
||||
func getTagFromTraceState(traceState pdata.TraceState) *KeyValue {
|
||||
func getTraceState(traceState pdata.TraceState) string {
|
||||
if traceState != pdata.TraceStateEmpty {
|
||||
return &KeyValue{
|
||||
Key: tracetranslator.TagW3CTraceState,
|
||||
Value: string(traceState),
|
||||
}
|
||||
return string(traceState)
|
||||
}
|
||||
return nil
|
||||
return ""
|
||||
}
|
||||
|
||||
func spanEventsToLogs(events pdata.SpanEventSlice) []*TraceLog {
|
||||
|
@ -40,7 +40,7 @@ func TestTraceToFrame(t *testing.T) {
|
||||
require.Equal(t, 1616072924070.497, root["startTime"])
|
||||
require.Equal(t, 8.421, root["duration"])
|
||||
require.Equal(t, json.RawMessage("null"), root["logs"])
|
||||
require.Equal(t, json.RawMessage("[{\"value\":\"const\",\"key\":\"sampler.type\"},{\"value\":true,\"key\":\"sampler.param\"},{\"value\":200,\"key\":\"http.status_code\"},{\"value\":\"GET\",\"key\":\"http.method\"},{\"value\":\"/loki/api/v1/query_range?direction=BACKWARD\\u0026limit=1000\\u0026query=%7Bcompose_project%3D%22devenv%22%7D%20%7C%3D%22traceID%22\\u0026start=1616070921000000000\\u0026end=1616072722000000000\\u0026step=2\",\"key\":\"http.url\"},{\"value\":\"net/http\",\"key\":\"component\"},{\"value\":\"server\",\"key\":\"span.kind\"},{\"value\":0,\"key\":\"status.code\"}]"), root["tags"])
|
||||
require.Equal(t, json.RawMessage("[{\"value\":\"const\",\"key\":\"sampler.type\"},{\"value\":true,\"key\":\"sampler.param\"},{\"value\":200,\"key\":\"http.status_code\"},{\"value\":\"GET\",\"key\":\"http.method\"},{\"value\":\"/loki/api/v1/query_range?direction=BACKWARD\\u0026limit=1000\\u0026query=%7Bcompose_project%3D%22devenv%22%7D%20%7C%3D%22traceID%22\\u0026start=1616070921000000000\\u0026end=1616072722000000000\\u0026step=2\",\"key\":\"http.url\"},{\"value\":\"net/http\",\"key\":\"component\"}]"), root["tags"])
|
||||
|
||||
span := bFrame.FindRowWithValue("spanID", "7198307df9748606")
|
||||
|
||||
@ -50,7 +50,6 @@ func TestTraceToFrame(t *testing.T) {
|
||||
require.Equal(t, 1616072924072.852, span["startTime"])
|
||||
require.Equal(t, 0.094, span["duration"])
|
||||
require.Equal(t, json.RawMessage("[{\"timestamp\":1616072924072.856,\"fields\":[{\"value\":1,\"key\":\"chunks requested\"}]},{\"timestamp\":1616072924072.9448,\"fields\":[{\"value\":1,\"key\":\"chunks fetched\"}]}]"), span["logs"])
|
||||
require.Equal(t, json.RawMessage("[{\"value\":0,\"key\":\"status.code\"}]"), span["tags"])
|
||||
})
|
||||
|
||||
t.Run("should transform correct traceID", func(t *testing.T) {
|
||||
@ -141,6 +140,12 @@ var fields = []string{
|
||||
"parentSpanID",
|
||||
"operationName",
|
||||
"serviceName",
|
||||
"kind",
|
||||
"statusCode",
|
||||
"statusMessage",
|
||||
"instrumentationLibraryName",
|
||||
"instrumentationLibraryVersion",
|
||||
"traceState",
|
||||
"serviceTags",
|
||||
"startTime",
|
||||
"duration",
|
||||
|
@ -15,6 +15,12 @@ const trace: Trace = {
|
||||
spanID: '1ed38015486087ca',
|
||||
operationName: 'Span0',
|
||||
tags: [{ key: 'TagKey0', type: 'string', value: 'TagValue0' }],
|
||||
kind: 'server',
|
||||
statusCode: 2,
|
||||
statusMessage: 'message',
|
||||
instrumentationLibraryName: 'name',
|
||||
instrumentationLibraryVersion: 'version',
|
||||
traceState: 'state',
|
||||
process: {
|
||||
serviceName: 'Service0',
|
||||
tags: [{ key: 'ProcessKey0', type: 'string', value: 'ProcessValue0' }],
|
||||
@ -123,6 +129,7 @@ describe('SpanFilters', () => {
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('TagKey0')).toBeInTheDocument();
|
||||
expect(screen.getByText('TagKey1')).toBeInTheDocument();
|
||||
expect(screen.getByText('kind')).toBeInTheDocument();
|
||||
expect(screen.getByText('ProcessKey0')).toBeInTheDocument();
|
||||
expect(screen.getByText('ProcessKey1')).toBeInTheDocument();
|
||||
expect(screen.getByText('LogKey0')).toBeInTheDocument();
|
||||
@ -164,8 +171,14 @@ describe('SpanFilters', () => {
|
||||
expect(container?.childNodes[1].textContent).toBe('ProcessKey1');
|
||||
expect(container?.childNodes[2].textContent).toBe('TagKey0');
|
||||
expect(container?.childNodes[3].textContent).toBe('TagKey1');
|
||||
expect(container?.childNodes[4].textContent).toBe('LogKey0');
|
||||
expect(container?.childNodes[5].textContent).toBe('LogKey1');
|
||||
expect(container?.childNodes[4].textContent).toBe('kind');
|
||||
expect(container?.childNodes[5].textContent).toBe('library.name');
|
||||
expect(container?.childNodes[6].textContent).toBe('library.version');
|
||||
expect(container?.childNodes[7].textContent).toBe('status');
|
||||
expect(container?.childNodes[8].textContent).toBe('status.message');
|
||||
expect(container?.childNodes[9].textContent).toBe('trace.state');
|
||||
expect(container?.childNodes[10].textContent).toBe('LogKey0');
|
||||
expect(container?.childNodes[11].textContent).toBe('LogKey1');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -13,6 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
import { css } from '@emotion/css';
|
||||
import { SpanStatusCode } from '@opentelemetry/api';
|
||||
import { uniq } from 'lodash';
|
||||
import React, { useState, useEffect, memo, useCallback } from 'react';
|
||||
|
||||
@ -31,6 +32,7 @@ import {
|
||||
} from '@grafana/ui';
|
||||
|
||||
import { defaultFilters, randomId, SearchProps, Tag } from '../../../useSearch';
|
||||
import { KIND, LIBRARY_NAME, LIBRARY_VERSION, STATUS, STATUS_MESSAGE, TRACE_STATE } from '../../constants/span';
|
||||
import { Trace } from '../../types';
|
||||
import NewTracePageSearchBar from '../NewTracePageSearchBar';
|
||||
|
||||
@ -119,6 +121,25 @@ export const SpanFilters = memo((props: SpanFilterProps) => {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (span.kind) {
|
||||
keys.push(KIND);
|
||||
}
|
||||
if (span.statusCode !== undefined) {
|
||||
keys.push(STATUS);
|
||||
}
|
||||
if (span.statusMessage) {
|
||||
keys.push(STATUS_MESSAGE);
|
||||
}
|
||||
if (span.instrumentationLibraryName) {
|
||||
keys.push(LIBRARY_NAME);
|
||||
}
|
||||
if (span.instrumentationLibraryVersion) {
|
||||
keys.push(LIBRARY_VERSION);
|
||||
}
|
||||
if (span.traceState) {
|
||||
keys.push(TRACE_STATE);
|
||||
}
|
||||
});
|
||||
keys = uniq(keys).sort();
|
||||
logKeys = uniq(logKeys).sort();
|
||||
@ -147,6 +168,41 @@ export const SpanFilters = memo((props: SpanFilterProps) => {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
switch (key) {
|
||||
case KIND:
|
||||
if (span.kind) {
|
||||
values.push(span.kind);
|
||||
}
|
||||
break;
|
||||
case STATUS:
|
||||
if (span.statusCode !== undefined) {
|
||||
values.push(SpanStatusCode[span.statusCode].toLowerCase());
|
||||
}
|
||||
break;
|
||||
case STATUS_MESSAGE:
|
||||
if (span.statusMessage) {
|
||||
values.push(span.statusMessage);
|
||||
}
|
||||
break;
|
||||
case LIBRARY_NAME:
|
||||
if (span.instrumentationLibraryName) {
|
||||
values.push(span.instrumentationLibraryName);
|
||||
}
|
||||
break;
|
||||
case LIBRARY_VERSION:
|
||||
if (span.instrumentationLibraryVersion) {
|
||||
values.push(span.instrumentationLibraryVersion);
|
||||
}
|
||||
break;
|
||||
case TRACE_STATE:
|
||||
if (span.traceState) {
|
||||
values.push(span.traceState);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return uniq(values).sort().map(toOption);
|
||||
|
@ -558,13 +558,13 @@ export class UnthemedSpanBarRow extends React.PureComponent<SpanBarRowProps> {
|
||||
const tag = span.tags?.find((tag: TraceKeyValuePair) => {
|
||||
return tag.key === tagKey;
|
||||
});
|
||||
const process = span.process?.tags?.find((process: TraceKeyValuePair) => {
|
||||
return process.key === tagKey;
|
||||
});
|
||||
|
||||
if (tag) {
|
||||
return `(${tag.value})`;
|
||||
}
|
||||
|
||||
const process = span.process?.tags?.find((process: TraceKeyValuePair) => {
|
||||
return process.key === tagKey;
|
||||
});
|
||||
if (process) {
|
||||
return `(${process.value})`;
|
||||
}
|
||||
|
@ -47,6 +47,14 @@ describe('<SpanDetail>', () => {
|
||||
createFocusSpanLink: jest.fn().mockReturnValue({}),
|
||||
topOfViewRefType: 'Explore',
|
||||
};
|
||||
|
||||
span.kind = 'test-kind';
|
||||
span.statusCode = 2;
|
||||
span.statusMessage = 'test-message';
|
||||
span.instrumentationLibraryName = 'test-name';
|
||||
span.instrumentationLibraryVersion = 'test-version';
|
||||
span.traceState = 'test-state';
|
||||
|
||||
span.logs = [
|
||||
{
|
||||
timestamp: 10,
|
||||
@ -125,11 +133,22 @@ describe('<SpanDetail>', () => {
|
||||
expect(screen.getByRole('heading', { name: span.operationName })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('lists the service name, duration and start time', () => {
|
||||
it('lists the service name, duration, start time and kind', () => {
|
||||
render(<SpanDetail {...(props as unknown as SpanDetailProps)} />);
|
||||
expect(screen.getByText('Duration:')).toBeInTheDocument();
|
||||
expect(screen.getByText('Service:')).toBeInTheDocument();
|
||||
expect(screen.getByText('Start Time:')).toBeInTheDocument();
|
||||
expect(screen.getByText('Kind:')).toBeInTheDocument();
|
||||
expect(screen.getByText('test-kind')).toBeInTheDocument();
|
||||
expect(screen.getByText('Status:')).toBeInTheDocument();
|
||||
expect(screen.getByText('Status Message:')).toBeInTheDocument();
|
||||
expect(screen.getByText('test-message')).toBeInTheDocument();
|
||||
expect(screen.getByText('Library Name:')).toBeInTheDocument();
|
||||
expect(screen.getByText('test-name')).toBeInTheDocument();
|
||||
expect(screen.getByText('Library Version:')).toBeInTheDocument();
|
||||
expect(screen.getByText('test-version')).toBeInTheDocument();
|
||||
expect(screen.getByText('Trace State:')).toBeInTheDocument();
|
||||
expect(screen.getByText('test-state')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('start time shows the absolute time', () => {
|
||||
|
@ -13,6 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
import { css } from '@emotion/css';
|
||||
import { SpanStatusCode } from '@opentelemetry/api';
|
||||
import cx from 'classnames';
|
||||
import React from 'react';
|
||||
|
||||
@ -23,6 +24,7 @@ import { Button, DataLinkButton, Icon, TextArea, useStyles2 } from '@grafana/ui'
|
||||
import { autoColor } from '../../Theme';
|
||||
import { Divider } from '../../common/Divider';
|
||||
import LabeledList from '../../common/LabeledList';
|
||||
import { KIND, LIBRARY_NAME, LIBRARY_VERSION, STATUS, STATUS_MESSAGE, TRACE_STATE } from '../../constants/span';
|
||||
import { SpanLinkFunc, TNil } from '../../types';
|
||||
import { SpanLinkType } from '../../types/links';
|
||||
import { TraceKeyValuePair, TraceLink, TraceLog, TraceSpan, TraceSpanReference } from '../../types/trace';
|
||||
@ -167,7 +169,7 @@ export default function SpanDetail(props: SpanDetailProps) {
|
||||
stackTraces,
|
||||
} = span;
|
||||
const { timeZone } = props;
|
||||
const overviewItems = [
|
||||
let overviewItems = [
|
||||
{
|
||||
key: 'svc',
|
||||
label: 'Service:',
|
||||
@ -193,6 +195,50 @@ export default function SpanDetail(props: SpanDetailProps) {
|
||||
]
|
||||
: []),
|
||||
];
|
||||
|
||||
if (span.kind) {
|
||||
overviewItems.push({
|
||||
key: KIND,
|
||||
label: 'Kind:',
|
||||
value: span.kind,
|
||||
});
|
||||
}
|
||||
if (span.statusCode !== undefined) {
|
||||
overviewItems.push({
|
||||
key: STATUS,
|
||||
label: 'Status:',
|
||||
value: SpanStatusCode[span.statusCode].toLowerCase(),
|
||||
});
|
||||
}
|
||||
if (span.statusMessage) {
|
||||
overviewItems.push({
|
||||
key: STATUS_MESSAGE,
|
||||
label: 'Status Message:',
|
||||
value: span.statusMessage,
|
||||
});
|
||||
}
|
||||
if (span.instrumentationLibraryName) {
|
||||
overviewItems.push({
|
||||
key: LIBRARY_NAME,
|
||||
label: 'Library Name:',
|
||||
value: span.instrumentationLibraryName,
|
||||
});
|
||||
}
|
||||
if (span.instrumentationLibraryVersion) {
|
||||
overviewItems.push({
|
||||
key: LIBRARY_VERSION,
|
||||
label: 'Library Version:',
|
||||
value: span.instrumentationLibraryVersion,
|
||||
});
|
||||
}
|
||||
if (span.traceState) {
|
||||
overviewItems.push({
|
||||
key: TRACE_STATE,
|
||||
label: 'Trace State:',
|
||||
value: span.traceState,
|
||||
});
|
||||
}
|
||||
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
let logLinkButton: JSX.Element | undefined = undefined;
|
||||
|
@ -57,11 +57,24 @@ describe('TraceTimelineViewer/utils', () => {
|
||||
});
|
||||
|
||||
describe('spanHasTag() and variants', () => {
|
||||
it('returns true iff the key/value pair is found', () => {
|
||||
it('returns true if client span', () => {
|
||||
const span = traceGenerator.span;
|
||||
span.kind = 'client';
|
||||
expect(isServerSpan(span)).toBe(false);
|
||||
expect(isClientSpan(span)).toBe(true);
|
||||
span.kind = 'server';
|
||||
expect(isServerSpan(span)).toBe(true);
|
||||
expect(isClientSpan(span)).toBe(false);
|
||||
span.statusCode = 0;
|
||||
expect(isErrorSpan(span)).toBe(false);
|
||||
span.statusCode = 2;
|
||||
expect(isErrorSpan(span)).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true if the key/value pair is found', () => {
|
||||
const span = traceGenerator.span;
|
||||
span.tags = [{ key: 'span.kind', value: 'server' }];
|
||||
expect(spanHasTag('span.kind', 'client', span)).toBe(false);
|
||||
expect(spanHasTag('span.kind', 'client', span)).toBe(false);
|
||||
expect(spanHasTag('span.kind', 'server', span)).toBe(true);
|
||||
});
|
||||
|
||||
@ -77,41 +90,56 @@ describe('TraceTimelineViewer/utils', () => {
|
||||
it(msg, () => {
|
||||
const span = { tags: traceGenerator.tags() } as TraceSpan;
|
||||
expect(testCase.fn(span)).toBe(false);
|
||||
span.tags.push(testCase);
|
||||
span.tags!.push(testCase);
|
||||
expect(testCase.fn(span)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('spanContainsErredSpan()', () => {
|
||||
// Using a string to generate the test spans. Each line results in a span. The
|
||||
// left number indicates whether or not the generated span has a descendant
|
||||
// with an error tag (the expectation). The length of the line indicates the
|
||||
// depth of the span (i.e. further right is higher depth). The right number
|
||||
// indicates whether or not the span has an error tag.
|
||||
const config = `
|
||||
1 0
|
||||
1 0
|
||||
0 1
|
||||
0 0
|
||||
1 0
|
||||
1 1
|
||||
0 1
|
||||
0 0
|
||||
1 0
|
||||
0 1
|
||||
0 0
|
||||
`
|
||||
.trim()
|
||||
.split('\n')
|
||||
.map((s) => s.trim());
|
||||
// Get the expectation, str -> number -> bool
|
||||
const expectations = config.map((s) => Boolean(Number(s[0])));
|
||||
|
||||
it('returns true only when a descendant has an error value', () => {
|
||||
const spans = config.map((line) => ({
|
||||
depth: line.length,
|
||||
statusCode: +line.slice(-1) ? 2 : 0,
|
||||
})) as TraceSpan[];
|
||||
|
||||
expectations.forEach((target, i) => {
|
||||
// include the index in the expect condition to know which span failed
|
||||
// (if there is a failure, that is)
|
||||
const result = [i, spanContainsErredSpan(spans, i)];
|
||||
expect(result).toEqual([i, target]);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns true only when a descendant has an error tag', () => {
|
||||
const errorTag = { key: 'error', type: 'bool', value: true };
|
||||
const getTags = (withError: number) =>
|
||||
withError ? traceGenerator.tags().concat(errorTag) : traceGenerator.tags();
|
||||
|
||||
// Using a string to generate the test spans. Each line results in a span. The
|
||||
// left number indicates whether or not the generated span has a descendant
|
||||
// with an error tag (the expectation). The length of the line indicates the
|
||||
// depth of the span (i.e. further right is higher depth). The right number
|
||||
// indicates whether or not the span has an error tag.
|
||||
const config = `
|
||||
1 0
|
||||
1 0
|
||||
0 1
|
||||
0 0
|
||||
1 0
|
||||
1 1
|
||||
0 1
|
||||
0 0
|
||||
1 0
|
||||
0 1
|
||||
0 0
|
||||
`
|
||||
.trim()
|
||||
.split('\n')
|
||||
.map((s) => s.trim());
|
||||
// Get the expectation, str -> number -> bool
|
||||
const expectations = config.map((s) => Boolean(Number(s[0])));
|
||||
const spans = config.map((line) => ({
|
||||
depth: line.length,
|
||||
tags: getTags(+line.slice(-1)),
|
||||
@ -126,6 +154,36 @@ describe('TraceTimelineViewer/utils', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('findServerChildSpan() for OTEL', () => {
|
||||
let spans: TraceSpan[];
|
||||
|
||||
beforeEach(() => {
|
||||
spans = [
|
||||
{ depth: 0, kind: 'client' },
|
||||
{ depth: 1 },
|
||||
{ depth: 1, kind: 'server' },
|
||||
{ depth: 1, kind: 'third-kind' },
|
||||
{ depth: 1, kind: 'server' },
|
||||
] as TraceSpan[];
|
||||
});
|
||||
|
||||
it('returns falsy if the frist span is not a client', () => {
|
||||
expect(findServerChildSpan(spans.slice(1))).toBeFalsy();
|
||||
});
|
||||
|
||||
it('returns the first server span', () => {
|
||||
const span = findServerChildSpan(spans);
|
||||
expect(span).toBe(spans[2]);
|
||||
});
|
||||
|
||||
it('bails when a non-child-depth span is encountered', () => {
|
||||
spans[1].depth++;
|
||||
expect(findServerChildSpan(spans)).toBeFalsy();
|
||||
spans[1].depth = spans[0].depth;
|
||||
expect(findServerChildSpan(spans)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('findServerChildSpan()', () => {
|
||||
let spans: TraceSpan[];
|
||||
|
||||
|
@ -51,11 +51,10 @@ export function createViewedBoundsFunc(viewRange: { min: number; max: number; vi
|
||||
/**
|
||||
* Returns `true` if the `span` has a tag matching `key` = `value`.
|
||||
*
|
||||
* @param {string} key The tag key to match on.
|
||||
* @param {any} value The tag value to match.
|
||||
* @param {{tags}} span An object with a `tags` property of { key, value }
|
||||
* items.
|
||||
* @returns {boolean} True if a match was found.
|
||||
* @param {string} key The tag key to match on.
|
||||
* @param {any} value The tag value to match.
|
||||
* @param {{tag}} span An object with a `tag` property of { key, value } items.
|
||||
* @returns {boolean} True if a match was found.
|
||||
*/
|
||||
export function spanHasTag(key: string, value: unknown, span: TraceSpan) {
|
||||
if (!Array.isArray(span.tags) || !span.tags.length) {
|
||||
@ -64,12 +63,17 @@ export function spanHasTag(key: string, value: unknown, span: TraceSpan) {
|
||||
return span.tags.some((tag) => tag.key === key && tag.value === value);
|
||||
}
|
||||
|
||||
export const isClientSpan = spanHasTag.bind(null, 'span.kind', 'client');
|
||||
export const isServerSpan = spanHasTag.bind(null, 'span.kind', 'server');
|
||||
const isClientOtel = (span: TraceSpan) => span.kind === 'client';
|
||||
const isClient = spanHasTag.bind(null, 'span.kind', 'client');
|
||||
export const isClientSpan = (span: TraceSpan) => isClientOtel(span) || isClient(span);
|
||||
const isServerOtel = (span: TraceSpan) => span.kind === 'server';
|
||||
const isServer = spanHasTag.bind(null, 'span.kind', 'server');
|
||||
export const isServerSpan = (span: TraceSpan) => isServerOtel(span) || isServer(span);
|
||||
|
||||
const isErrorOtel = (span: TraceSpan) => span.statusCode === 2;
|
||||
const isErrorBool = spanHasTag.bind(null, 'error', true);
|
||||
const isErrorStr = spanHasTag.bind(null, 'error', 'true');
|
||||
export const isErrorSpan = (span: TraceSpan) => isErrorBool(span) || isErrorStr(span);
|
||||
export const isErrorSpan = (span: TraceSpan) => isErrorOtel(span) || isErrorBool(span) || isErrorStr(span);
|
||||
|
||||
/**
|
||||
* Returns `true` if at least one of the descendants of the `parentSpanIndex`
|
||||
@ -112,7 +116,11 @@ export function findServerChildSpan(spans: TraceSpan[]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
export const isKindClient = (span: TraceSpan): Boolean =>
|
||||
span.tags.some(({ key, value }) => key === 'span.kind' && value === 'client');
|
||||
export const isKindClient = (span: TraceSpan): Boolean => {
|
||||
if (span.kind) {
|
||||
return span.kind === 'client';
|
||||
}
|
||||
return span.tags.some(({ key, value }) => key === 'span.kind' && value === 'client');
|
||||
};
|
||||
|
||||
export { formatDuration } from '../utils/date';
|
||||
|
@ -52,7 +52,7 @@ const getStyles = (divider: boolean) => (theme: GrafanaTheme2) => {
|
||||
`,
|
||||
LabeledListValue: css`
|
||||
label: LabeledListValue;
|
||||
margin-right: 0.55rem;
|
||||
${!divider && `margin-right: 0.55rem;`}
|
||||
`,
|
||||
};
|
||||
};
|
||||
|
@ -0,0 +1,6 @@
|
||||
export const KIND = 'kind';
|
||||
export const STATUS = 'status';
|
||||
export const STATUS_MESSAGE = 'status.message';
|
||||
export const LIBRARY_NAME = 'library.name';
|
||||
export const LIBRARY_VERSION = 'library.version';
|
||||
export const TRACE_STATE = 'trace.state';
|
@ -47,7 +47,7 @@ describe('deduplicateTags()', () => {
|
||||
{ key: 'a.ip', value: '8.8.8.8' },
|
||||
]);
|
||||
|
||||
expect(tagsInfo.tags).toEqual([
|
||||
expect(tagsInfo.dedupedTags).toEqual([
|
||||
{ key: 'b.ip', value: '8.8.4.4' },
|
||||
{ key: 'b.ip', value: '8.8.8.8' },
|
||||
{ key: 'a.ip', value: '8.8.8.8' },
|
||||
|
@ -24,9 +24,9 @@ import { getConfigValue } from '../utils/config/get-config';
|
||||
import { getTraceName } from './trace-viewer';
|
||||
|
||||
// exported for tests
|
||||
export function deduplicateTags(spanTags: TraceKeyValuePair[]) {
|
||||
export function deduplicateTags(tags: TraceKeyValuePair[]) {
|
||||
const warningsHash: Map<string, string> = new Map<string, string>();
|
||||
const tags: TraceKeyValuePair[] = spanTags.reduce<TraceKeyValuePair[]>((uniqueTags, tag) => {
|
||||
const dedupedTags: TraceKeyValuePair[] = tags.reduce<TraceKeyValuePair[]>((uniqueTags, tag) => {
|
||||
if (!uniqueTags.some((t) => t.key === tag.key && t.value === tag.value)) {
|
||||
uniqueTags.push(tag);
|
||||
} else {
|
||||
@ -35,12 +35,12 @@ export function deduplicateTags(spanTags: TraceKeyValuePair[]) {
|
||||
return uniqueTags;
|
||||
}, []);
|
||||
const warnings = Array.from(warningsHash.values());
|
||||
return { tags, warnings };
|
||||
return { dedupedTags, warnings };
|
||||
}
|
||||
|
||||
// exported for tests
|
||||
export function orderTags(spanTags: TraceKeyValuePair[], topPrefixes?: string[]) {
|
||||
const orderedTags: TraceKeyValuePair[] = spanTags?.slice() ?? [];
|
||||
export function orderTags(tags: TraceKeyValuePair[], topPrefixes?: string[]) {
|
||||
const orderedTags: TraceKeyValuePair[] = tags?.slice() ?? [];
|
||||
const tp = (topPrefixes || []).map((p: string) => p.toLowerCase());
|
||||
|
||||
orderedTags.sort((a, b) => {
|
||||
@ -156,7 +156,7 @@ export default function transformTraceData(data: TraceResponse | undefined): Tra
|
||||
span.tags = span.tags || [];
|
||||
span.references = span.references || [];
|
||||
const tagsInfo = deduplicateTags(span.tags);
|
||||
span.tags = orderTags(tagsInfo.tags, getConfigValue('topTagPrefixes'));
|
||||
span.tags = orderTags(tagsInfo.dedupedTags, getConfigValue('topTagPrefixes'));
|
||||
span.warnings = span.warnings.concat(tagsInfo.warnings);
|
||||
span.references.forEach((ref, index) => {
|
||||
const refSpan = spanMap.get(ref.spanID);
|
||||
|
@ -57,6 +57,12 @@ export type TraceSpanData = {
|
||||
duration: number;
|
||||
logs: TraceLog[];
|
||||
tags?: TraceKeyValuePair[];
|
||||
kind?: string;
|
||||
statusCode?: number;
|
||||
statusMessage?: string;
|
||||
instrumentationLibraryName?: string;
|
||||
instrumentationLibraryVersion?: string;
|
||||
traceState?: string;
|
||||
references?: TraceSpanReference[];
|
||||
warnings?: string[] | null;
|
||||
stackTraces?: string[];
|
||||
|
@ -24,6 +24,12 @@ describe('filterSpans', () => {
|
||||
spanID: spanID0,
|
||||
operationName: 'operationName0',
|
||||
duration: 3050,
|
||||
kind: 'kind0',
|
||||
statusCode: 0,
|
||||
statusMessage: 'statusMessage0',
|
||||
instrumentationLibraryName: 'libraryName',
|
||||
instrumentationLibraryVersion: 'libraryVersion0',
|
||||
traceState: 'traceState0',
|
||||
process: {
|
||||
serviceName: 'serviceName0',
|
||||
tags: [
|
||||
@ -69,6 +75,12 @@ describe('filterSpans', () => {
|
||||
spanID: spanID2,
|
||||
operationName: 'operationName2',
|
||||
duration: 5000,
|
||||
kind: 'kind2',
|
||||
statusCode: 2,
|
||||
statusMessage: 'statusMessage2',
|
||||
instrumentationLibraryName: 'libraryName',
|
||||
instrumentationLibraryVersion: 'libraryVersion2',
|
||||
traceState: 'traceState2',
|
||||
process: {
|
||||
serviceName: 'serviceName2',
|
||||
tags: [
|
||||
@ -178,6 +190,117 @@ describe('filterSpans', () => {
|
||||
).toEqual(new Set([spanID0]));
|
||||
});
|
||||
|
||||
it('should return spans whose kind, statusCode, statusMessage, libraryName, libraryVersion or traceState match a filter', () => {
|
||||
expect(
|
||||
filterSpansNewTraceViewHeader({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'kind' }] }, spans)
|
||||
).toEqual(new Set([spanID0, spanID2]));
|
||||
expect(
|
||||
filterSpansNewTraceViewHeader(
|
||||
{ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'kind', value: 'kind0' }] },
|
||||
spans
|
||||
)
|
||||
).toEqual(new Set([spanID0]));
|
||||
expect(
|
||||
filterSpansNewTraceViewHeader(
|
||||
{ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'kind', operator: '!=', value: 'kind0' }] },
|
||||
spans
|
||||
)
|
||||
).toEqual(new Set([spanID2]));
|
||||
expect(
|
||||
filterSpansNewTraceViewHeader({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'status' }] }, spans)
|
||||
).toEqual(new Set([spanID0, spanID2]));
|
||||
expect(
|
||||
filterSpansNewTraceViewHeader(
|
||||
{ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'status', value: 'unset' }] },
|
||||
spans
|
||||
)
|
||||
).toEqual(new Set([spanID0]));
|
||||
expect(
|
||||
filterSpansNewTraceViewHeader(
|
||||
{ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'status', operator: '!=', value: 'unset' }] },
|
||||
spans
|
||||
)
|
||||
).toEqual(new Set([spanID2]));
|
||||
expect(
|
||||
filterSpansNewTraceViewHeader(
|
||||
{ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'status.message' }] },
|
||||
spans
|
||||
)
|
||||
).toEqual(new Set([spanID0, spanID2]));
|
||||
expect(
|
||||
filterSpansNewTraceViewHeader(
|
||||
{ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'status.message', value: 'statusMessage0' }] },
|
||||
spans
|
||||
)
|
||||
).toEqual(new Set([spanID0]));
|
||||
expect(
|
||||
filterSpansNewTraceViewHeader(
|
||||
{
|
||||
...defaultFilters,
|
||||
tags: [{ ...defaultTagFilter, key: 'status.message', operator: '!=', value: 'statusMessage0' }],
|
||||
},
|
||||
spans
|
||||
)
|
||||
).toEqual(new Set([spanID2]));
|
||||
expect(
|
||||
filterSpansNewTraceViewHeader({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'library.name' }] }, spans)
|
||||
).toEqual(new Set([spanID0, spanID2]));
|
||||
expect(
|
||||
filterSpansNewTraceViewHeader(
|
||||
{ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'library.name', value: 'libraryName' }] },
|
||||
spans
|
||||
)
|
||||
).toEqual(new Set([spanID0, spanID2]));
|
||||
expect(
|
||||
filterSpansNewTraceViewHeader(
|
||||
{
|
||||
...defaultFilters,
|
||||
tags: [{ ...defaultTagFilter, key: 'library.name', operator: '!=', value: 'libraryName' }],
|
||||
},
|
||||
spans
|
||||
)
|
||||
).toEqual(new Set([]));
|
||||
expect(
|
||||
filterSpansNewTraceViewHeader(
|
||||
{ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'library.version' }] },
|
||||
spans
|
||||
)
|
||||
).toEqual(new Set([spanID0, spanID2]));
|
||||
expect(
|
||||
filterSpansNewTraceViewHeader(
|
||||
{ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'library.version', value: 'libraryVersion0' }] },
|
||||
spans
|
||||
)
|
||||
).toEqual(new Set([spanID0]));
|
||||
expect(
|
||||
filterSpansNewTraceViewHeader(
|
||||
{
|
||||
...defaultFilters,
|
||||
tags: [{ ...defaultTagFilter, key: 'library.version', operator: '!=', value: 'libraryVersion0' }],
|
||||
},
|
||||
spans
|
||||
)
|
||||
).toEqual(new Set([spanID2]));
|
||||
expect(
|
||||
filterSpansNewTraceViewHeader({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'trace.state' }] }, spans)
|
||||
).toEqual(new Set([spanID0, spanID2]));
|
||||
expect(
|
||||
filterSpansNewTraceViewHeader(
|
||||
{ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'trace.state', value: 'traceState0' }] },
|
||||
spans
|
||||
)
|
||||
).toEqual(new Set([spanID0]));
|
||||
expect(
|
||||
filterSpansNewTraceViewHeader(
|
||||
{
|
||||
...defaultFilters,
|
||||
tags: [{ ...defaultTagFilter, key: 'trace.state', operator: '!=', value: 'traceState0' }],
|
||||
},
|
||||
spans
|
||||
)
|
||||
).toEqual(new Set([spanID2]));
|
||||
});
|
||||
|
||||
it('should return spans whose process.tags kv.key match a filter', () => {
|
||||
expect(
|
||||
filterSpansNewTraceViewHeader(
|
||||
@ -533,6 +656,15 @@ describe('filterSpans', () => {
|
||||
expect(filterSpans('tagValue1 -tagKey1', spans)).toEqual(new Set([spanID2]));
|
||||
});
|
||||
|
||||
it('should return spans whose kind, statusCode, statusMessage, libraryName, libraryVersion or traceState value match a filter', () => {
|
||||
expect(filterSpans('kind0', spans)).toEqual(new Set([spanID0]));
|
||||
expect(filterSpans('error', spans)).toEqual(new Set([spanID2]));
|
||||
expect(filterSpans('statusMessage0', spans)).toEqual(new Set([spanID0]));
|
||||
expect(filterSpans('libraryName', spans)).toEqual(new Set([spanID0, spanID2]));
|
||||
expect(filterSpans('libraryVersion2', spans)).toEqual(new Set([spanID2]));
|
||||
expect(filterSpans('traceState0', spans)).toEqual(new Set([spanID0]));
|
||||
});
|
||||
|
||||
it('should return spans whose logs have a field whose kv.key match a filter', () => {
|
||||
expect(filterSpans('logFieldKey1', spans)).toEqual(new Set([spanID0, spanID2]));
|
||||
expect(filterSpans('logFieldKey0', spans)).toEqual(new Set([spanID0]));
|
||||
|
@ -12,7 +12,10 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { SpanStatusCode } from '@opentelemetry/api';
|
||||
|
||||
import { SearchProps, Tag } from '../../useSearch';
|
||||
import { KIND, LIBRARY_NAME, LIBRARY_VERSION, STATUS, STATUS_MESSAGE, TRACE_STATE } from '../constants/span';
|
||||
import { TNil, TraceKeyValuePair, TraceSpan } from '../types';
|
||||
|
||||
// filter spans where all filters added need to be true for each individual span that is returned
|
||||
@ -56,21 +59,36 @@ const getTagMatches = (spans: TraceSpan[], tags: Tag[]) => {
|
||||
// match against every tag filter
|
||||
return tags.every((tag: Tag) => {
|
||||
if (tag.key && tag.value) {
|
||||
if (span.tags.some((kv) => checkKeyAndValueForMatch(tag, kv))) {
|
||||
return getReturnValue(tag.operator, true);
|
||||
} else if (span.process.tags.some((kv) => checkKeyAndValueForMatch(tag, kv))) {
|
||||
return getReturnValue(tag.operator, true);
|
||||
} else if (span.logs.some((log) => log.fields.some((kv) => checkKeyAndValueForMatch(tag, kv)))) {
|
||||
if (
|
||||
span.tags.some((kv) => checkKeyAndValueForMatch(tag, kv)) ||
|
||||
span.process.tags.some((kv) => checkKeyAndValueForMatch(tag, kv)) ||
|
||||
(span.logs && span.logs.some((log) => log.fields.some((kv) => checkKeyAndValueForMatch(tag, kv)))) ||
|
||||
(span.kind && tag.key === KIND && tag.value === span.kind) ||
|
||||
(span.statusCode !== undefined &&
|
||||
tag.key === STATUS &&
|
||||
tag.value === SpanStatusCode[span.statusCode].toLowerCase()) ||
|
||||
(span.statusMessage && tag.key === STATUS_MESSAGE && tag.value === span.statusMessage) ||
|
||||
(span.instrumentationLibraryName &&
|
||||
tag.key === LIBRARY_NAME &&
|
||||
tag.value === span.instrumentationLibraryName) ||
|
||||
(span.instrumentationLibraryVersion &&
|
||||
tag.key === LIBRARY_VERSION &&
|
||||
tag.value === span.instrumentationLibraryVersion) ||
|
||||
(span.traceState && tag.key === TRACE_STATE && tag.value === span.traceState)
|
||||
) {
|
||||
return getReturnValue(tag.operator, true);
|
||||
}
|
||||
} else if (tag.key) {
|
||||
if (span.tags.some((kv) => checkKeyForMatch(tag.key!, kv.key))) {
|
||||
return getReturnValue(tag.operator, true);
|
||||
} else if (span.process.tags.some((kv) => checkKeyForMatch(tag.key!, kv.key))) {
|
||||
return getReturnValue(tag.operator, true);
|
||||
} else if (
|
||||
span.logs &&
|
||||
span.logs.some((log) => log.fields.some((kv) => checkKeyForMatch(tag.key!, kv.key)))
|
||||
if (
|
||||
span.tags.some((kv) => checkKeyForMatch(tag.key!, kv.key)) ||
|
||||
span.process.tags.some((kv) => checkKeyForMatch(tag.key!, kv.key)) ||
|
||||
(span.logs && span.logs.some((log) => log.fields.some((kv) => checkKeyForMatch(tag.key!, kv.key)))) ||
|
||||
(span.kind && tag.key === KIND) ||
|
||||
(span.statusCode !== undefined && tag.key === STATUS) ||
|
||||
(span.statusMessage && tag.key === STATUS_MESSAGE) ||
|
||||
(span.instrumentationLibraryName && tag.key === LIBRARY_NAME) ||
|
||||
(span.instrumentationLibraryVersion && tag.key === LIBRARY_VERSION) ||
|
||||
(span.traceState && tag.key === TRACE_STATE)
|
||||
) {
|
||||
return getReturnValue(tag.operator, true);
|
||||
}
|
||||
@ -194,6 +212,12 @@ export function filterSpans(textFilter: string, spans: TraceSpan[] | TNil) {
|
||||
isTextInFilters(includeFilters, span.operationName) ||
|
||||
isTextInFilters(includeFilters, span.process.serviceName) ||
|
||||
isTextInKeyValues(span.tags) ||
|
||||
(span.kind && isTextInFilters(includeFilters, span.kind)) ||
|
||||
(span.statusCode !== undefined && isTextInFilters(includeFilters, SpanStatusCode[span.statusCode])) ||
|
||||
(span.statusMessage && isTextInFilters(includeFilters, span.statusMessage)) ||
|
||||
(span.instrumentationLibraryName && isTextInFilters(includeFilters, span.instrumentationLibraryName)) ||
|
||||
(span.instrumentationLibraryVersion && isTextInFilters(includeFilters, span.instrumentationLibraryVersion)) ||
|
||||
(span.traceState && isTextInFilters(includeFilters, span.traceState)) ||
|
||||
(span.logs !== null && span.logs.some((log) => isTextInKeyValues(log.fields))) ||
|
||||
isTextInKeyValues(span.process.tags) ||
|
||||
includeFilters.some((filter) => filter === span.spanID);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { SpanStatus, SpanStatusCode } from '@opentelemetry/api';
|
||||
import { SpanStatus } from '@opentelemetry/api';
|
||||
import { collectorTypes } from '@opentelemetry/exporter-collector';
|
||||
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
|
||||
|
||||
@ -161,53 +161,27 @@ function resourceToProcess(resource: collectorTypes.opentelemetryProto.resource.
|
||||
return { serviceName, serviceTags };
|
||||
}
|
||||
|
||||
function getSpanTags(
|
||||
span: collectorTypes.opentelemetryProto.trace.v1.Span,
|
||||
instrumentationLibrary?: collectorTypes.opentelemetryProto.common.v1.InstrumentationLibrary
|
||||
): TraceKeyValuePair[] {
|
||||
function getSpanTags(span: collectorTypes.opentelemetryProto.trace.v1.Span): TraceKeyValuePair[] {
|
||||
const spanTags: TraceKeyValuePair[] = [];
|
||||
|
||||
if (instrumentationLibrary) {
|
||||
if (instrumentationLibrary.name) {
|
||||
spanTags.push({ key: 'otel.library.name', value: instrumentationLibrary.name });
|
||||
}
|
||||
if (instrumentationLibrary.version) {
|
||||
spanTags.push({ key: 'otel.library.version', value: instrumentationLibrary.version });
|
||||
}
|
||||
}
|
||||
|
||||
if (span.attributes) {
|
||||
for (const attribute of span.attributes) {
|
||||
spanTags.push({ key: attribute.key, value: getAttributeValue(attribute.value) });
|
||||
}
|
||||
}
|
||||
|
||||
if (span.status) {
|
||||
if (span.status.code && (span.status.code as any) !== SpanStatusCode.UNSET) {
|
||||
spanTags.push({
|
||||
key: 'otel.status_code',
|
||||
value: SpanStatusCode[span.status.code],
|
||||
});
|
||||
if (span.status.message) {
|
||||
spanTags.push({ key: 'otel.status_description', value: span.status.message });
|
||||
}
|
||||
}
|
||||
if (span.status.code === SpanStatusCode.ERROR) {
|
||||
spanTags.push({ key: 'error', value: true });
|
||||
}
|
||||
}
|
||||
|
||||
if (span.kind !== undefined) {
|
||||
const split = span.kind.toString().toLowerCase().split('_');
|
||||
spanTags.push({
|
||||
key: 'span.kind',
|
||||
value: split.length ? split[split.length - 1] : span.kind.toString(),
|
||||
});
|
||||
}
|
||||
|
||||
return spanTags;
|
||||
}
|
||||
|
||||
function getSpanKind(span: collectorTypes.opentelemetryProto.trace.v1.Span) {
|
||||
let kind = undefined;
|
||||
if (span.kind) {
|
||||
const split = span.kind.toString().toLowerCase().split('_');
|
||||
kind = split.length ? split[split.length - 1] : span.kind.toString();
|
||||
}
|
||||
return kind;
|
||||
}
|
||||
|
||||
function getReferences(span: collectorTypes.opentelemetryProto.trace.v1.Span) {
|
||||
const references: TraceSpanReference[] = [];
|
||||
if (span.links) {
|
||||
@ -254,6 +228,12 @@ export function transformFromOTLP(
|
||||
{ name: 'parentSpanID', type: FieldType.string },
|
||||
{ name: 'operationName', type: FieldType.string },
|
||||
{ name: 'serviceName', type: FieldType.string },
|
||||
{ name: 'kind', type: FieldType.string },
|
||||
{ name: 'statusCode', type: FieldType.number },
|
||||
{ name: 'statusMessage', type: FieldType.string },
|
||||
{ name: 'instrumentationLibraryName', type: FieldType.string },
|
||||
{ name: 'instrumentationLibraryVersion', type: FieldType.string },
|
||||
{ name: 'traceState', type: FieldType.string },
|
||||
{ name: 'serviceTags', type: FieldType.other },
|
||||
{ name: 'startTime', type: FieldType.number },
|
||||
{ name: 'duration', type: FieldType.number },
|
||||
@ -279,10 +259,16 @@ export function transformFromOTLP(
|
||||
parentSpanID: span.parentSpanId || '',
|
||||
operationName: span.name || '',
|
||||
serviceName,
|
||||
kind: getSpanKind(span),
|
||||
statusCode: span.status?.code,
|
||||
statusMessage: span.status?.message,
|
||||
instrumentationLibraryName: librarySpan.instrumentationLibrary?.name,
|
||||
instrumentationLibraryVersion: librarySpan.instrumentationLibrary?.version,
|
||||
traceState: span.traceState,
|
||||
serviceTags,
|
||||
startTime: span.startTimeUnixNano! / 1000000,
|
||||
duration: (span.endTimeUnixNano! - span.startTimeUnixNano!) / 1000000,
|
||||
tags: getSpanTags(span, librarySpan.instrumentationLibrary),
|
||||
tags: getSpanTags(span),
|
||||
logs: getLogs(span),
|
||||
references: getReferences(span),
|
||||
} as TraceSpanRow);
|
||||
@ -343,11 +329,10 @@ export function transformToOTLP(data: MutableDataFrame): {
|
||||
|
||||
// Populate instrumentation library if it exists
|
||||
if (!result.batches[batchIndex].instrumentationLibrarySpans[0].instrumentationLibrary) {
|
||||
let libraryName = span.tags.find((t: TraceKeyValuePair) => t.key === 'otel.library.name')?.value;
|
||||
if (libraryName) {
|
||||
if (span.instrumentationLibraryName) {
|
||||
result.batches[batchIndex].instrumentationLibrarySpans[0].instrumentationLibrary = {
|
||||
name: libraryName,
|
||||
version: span.tags.find((t: TraceKeyValuePair) => t.key === 'otel.library.version')?.value,
|
||||
name: span.instrumentationLibraryName,
|
||||
version: span.instrumentationLibraryVersion ? span.instrumentationLibraryVersion : '',
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -356,16 +341,16 @@ export function transformToOTLP(data: MutableDataFrame): {
|
||||
traceId: span.traceID.padStart(32, '0'),
|
||||
spanId: span.spanID,
|
||||
parentSpanId: span.parentSpanID || '',
|
||||
traceState: '',
|
||||
traceState: span.traceState || '',
|
||||
name: span.operationName,
|
||||
kind: getOTLPSpanKind(span.tags) as any,
|
||||
kind: getOTLPSpanKind(span.kind) as any,
|
||||
startTimeUnixNano: span.startTime * 1000000,
|
||||
endTimeUnixNano: (span.startTime + span.duration) * 1000000,
|
||||
attributes: tagsToAttributes(span.tags),
|
||||
attributes: span.tags ? tagsToAttributes(span.tags) : [],
|
||||
droppedAttributesCount: 0,
|
||||
droppedEventsCount: 0,
|
||||
droppedLinksCount: 0,
|
||||
status: getOTLPStatus(span.tags),
|
||||
status: getOTLPStatus(span),
|
||||
events: getOTLPEvents(span.logs),
|
||||
links: getOTLPReferences(span.references),
|
||||
});
|
||||
@ -374,24 +359,27 @@ export function transformToOTLP(data: MutableDataFrame): {
|
||||
return result;
|
||||
}
|
||||
|
||||
function getOTLPSpanKind(tags: TraceKeyValuePair[]): string | undefined {
|
||||
function getOTLPSpanKind(kind: string): string | undefined {
|
||||
let spanKind = undefined;
|
||||
const spanKindTagValue = tags.find((t) => t.key === 'span.kind')?.value;
|
||||
switch (spanKindTagValue) {
|
||||
case 'server':
|
||||
spanKind = 'SPAN_KIND_SERVER';
|
||||
break;
|
||||
case 'client':
|
||||
spanKind = 'SPAN_KIND_CLIENT';
|
||||
break;
|
||||
case 'producer':
|
||||
spanKind = 'SPAN_KIND_PRODUCER';
|
||||
break;
|
||||
case 'consumer':
|
||||
spanKind = 'SPAN_KIND_CONSUMER';
|
||||
break;
|
||||
if (kind) {
|
||||
switch (kind) {
|
||||
case 'server':
|
||||
spanKind = 'SPAN_KIND_SERVER';
|
||||
break;
|
||||
case 'client':
|
||||
spanKind = 'SPAN_KIND_CLIENT';
|
||||
break;
|
||||
case 'producer':
|
||||
spanKind = 'SPAN_KIND_PRODUCER';
|
||||
break;
|
||||
case 'consumer':
|
||||
spanKind = 'SPAN_KIND_CONSUMER';
|
||||
break;
|
||||
case 'internal':
|
||||
spanKind = 'SPAN_KIND_INTERNAL';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return spanKind;
|
||||
}
|
||||
|
||||
@ -399,21 +387,10 @@ function getOTLPSpanKind(tags: TraceKeyValuePair[]): string | undefined {
|
||||
* Converts key-value tags to OTLP attributes and removes tags added by Grafana
|
||||
*/
|
||||
function tagsToAttributes(tags: TraceKeyValuePair[]): collectorTypes.opentelemetryProto.common.v1.KeyValue[] {
|
||||
return tags
|
||||
.filter(
|
||||
(t) =>
|
||||
![
|
||||
'span.kind',
|
||||
'otel.library.name',
|
||||
'otel.libary.version',
|
||||
'otel.status_description',
|
||||
'otel.status_code',
|
||||
].includes(t.key)
|
||||
)
|
||||
.reduce<collectorTypes.opentelemetryProto.common.v1.KeyValue[]>(
|
||||
(attributes, tag) => [...attributes, { key: tag.key, value: toAttributeValue(tag) }],
|
||||
[]
|
||||
);
|
||||
return tags.reduce<collectorTypes.opentelemetryProto.common.v1.KeyValue[]>(
|
||||
(attributes, tag) => [...attributes, { key: tag.key, value: toAttributeValue(tag) }],
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -443,16 +420,14 @@ function toAttributeValue(tag: TraceKeyValuePair): collectorTypes.opentelemetryP
|
||||
return { stringValue: tag.value };
|
||||
}
|
||||
|
||||
function getOTLPStatus(tags: TraceKeyValuePair[]): SpanStatus | undefined {
|
||||
function getOTLPStatus(span: TraceSpanRow): SpanStatus | undefined {
|
||||
let status = undefined;
|
||||
const statusCodeTag = tags.find((t) => t.key === 'otel.status_code');
|
||||
if (statusCodeTag) {
|
||||
if (span.statusCode !== undefined) {
|
||||
status = {
|
||||
code: statusCodeTag.value,
|
||||
message: tags.find((t) => t.key === 'otel_status_description')?.value,
|
||||
code: span.statusCode,
|
||||
message: span.statusMessage ? span.statusMessage : '',
|
||||
};
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
@ -1835,6 +1835,42 @@ export const otlpDataFrameFromResponse = new MutableDataFrame({
|
||||
config: {},
|
||||
values: ['db'],
|
||||
},
|
||||
{
|
||||
name: 'kind',
|
||||
type: 'string',
|
||||
config: {},
|
||||
values: ['client'],
|
||||
},
|
||||
{
|
||||
name: 'statusCode',
|
||||
type: 'number',
|
||||
config: {},
|
||||
values: [2],
|
||||
},
|
||||
{
|
||||
name: 'statusMessage',
|
||||
type: 'string',
|
||||
config: {},
|
||||
values: ['message'],
|
||||
},
|
||||
{
|
||||
name: 'instrumentationLibraryName',
|
||||
type: 'string',
|
||||
config: {},
|
||||
values: ['libraryName'],
|
||||
},
|
||||
{
|
||||
name: 'instrumentationLibraryVersion',
|
||||
type: 'string',
|
||||
config: {},
|
||||
values: ['libraryVersion'],
|
||||
},
|
||||
{
|
||||
name: 'traceState',
|
||||
type: 'string',
|
||||
config: {},
|
||||
values: ['traceState'],
|
||||
},
|
||||
{
|
||||
name: 'serviceTags',
|
||||
type: 'other',
|
||||
@ -1930,10 +1966,6 @@ export const otlpDataFrameFromResponse = new MutableDataFrame({
|
||||
key: 'component',
|
||||
value: 'net/http',
|
||||
},
|
||||
{
|
||||
key: 'span.kind',
|
||||
value: 'client',
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
@ -1994,6 +2026,60 @@ export const otlpDataFrameToResponse = new MutableDataFrame({
|
||||
displayName: 'serviceName',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'kind',
|
||||
type: 'string',
|
||||
config: {},
|
||||
values: ['client'],
|
||||
state: {
|
||||
displayName: 'kind',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'statusCode',
|
||||
type: 'number',
|
||||
config: {},
|
||||
values: [2],
|
||||
state: {
|
||||
displayName: 'statusCode',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'statusMessage',
|
||||
type: 'string',
|
||||
config: {},
|
||||
values: ['message'],
|
||||
state: {
|
||||
displayName: 'statusMessage',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'instrumentationLibraryName',
|
||||
type: 'string',
|
||||
config: {},
|
||||
values: ['libraryName'],
|
||||
state: {
|
||||
displayName: 'instrumentationLibraryName',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'instrumentationLibraryVersion',
|
||||
type: 'string',
|
||||
config: {},
|
||||
values: ['libraryVersion'],
|
||||
state: {
|
||||
displayName: 'instrumentationLibraryVersion',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'traceState',
|
||||
type: 'string',
|
||||
config: {},
|
||||
values: ['traceState'],
|
||||
state: {
|
||||
displayName: 'traceState',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'serviceTags',
|
||||
type: 'other',
|
||||
@ -2079,10 +2165,6 @@ export const otlpDataFrameToResponse = new MutableDataFrame({
|
||||
key: 'component',
|
||||
value: 'net/http',
|
||||
},
|
||||
{
|
||||
key: 'span.kind',
|
||||
value: 'client',
|
||||
},
|
||||
],
|
||||
],
|
||||
state: {
|
||||
@ -2135,6 +2217,10 @@ export const otlpResponse = {
|
||||
},
|
||||
instrumentationLibrarySpans: [
|
||||
{
|
||||
instrumentationLibrary: {
|
||||
name: 'libraryName',
|
||||
version: 'libraryVersion',
|
||||
},
|
||||
spans: [
|
||||
{
|
||||
traceId: '000000000000000060ba2abb44f13eae',
|
||||
@ -2142,6 +2228,11 @@ export const otlpResponse = {
|
||||
parentSpanId: '398f0f21a3db99ae',
|
||||
name: 'HTTP GET - root',
|
||||
kind: 'SPAN_KIND_CLIENT',
|
||||
status: {
|
||||
code: 2,
|
||||
message: 'message',
|
||||
},
|
||||
traceState: 'traceState',
|
||||
startTimeUnixNano: 1627471657255809000,
|
||||
endTimeUnixNano: 1627471657256268000,
|
||||
attributes: [
|
||||
|
Loading…
Reference in New Issue
Block a user