diff --git a/.betterer.results b/.betterer.results
index bafa487b8df..2f9307b0639 100644
--- a/.betterer.results
+++ b/.betterer.results
@@ -4096,7 +4096,9 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings. Wrap text with ", "4"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "5"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "6"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "7"]
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "7"],
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "8"],
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "9"]
],
"public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianReferences.tsx:5381": [
[0, 0, 0, "Styles should be written using objects.", "0"],
diff --git a/packages/grafana-data/src/types/trace.ts b/packages/grafana-data/src/types/trace.ts
index 27b16a9f73c..8346f07dfb9 100644
--- a/packages/grafana-data/src/types/trace.ts
+++ b/packages/grafana-data/src/types/trace.ts
@@ -13,6 +13,7 @@ export type TraceLog = {
// Millisecond epoch time
timestamp: number;
fields: TraceKeyValuePair[];
+ name?: string;
};
export type TraceSpanReference = {
diff --git a/pkg/tsdb/tempo/trace_transform.go b/pkg/tsdb/tempo/trace_transform.go
index 0c3bf0321c2..a7d0f3c2dea 100644
--- a/pkg/tsdb/tempo/trace_transform.go
+++ b/pkg/tsdb/tempo/trace_transform.go
@@ -22,6 +22,7 @@ type TraceLog struct {
// Millisecond epoch time
Timestamp float64 `json:"timestamp"`
Fields []*KeyValue `json:"fields"`
+ Name string `json:"name,omitempty"`
}
type TraceReference struct {
@@ -260,12 +261,6 @@ func spanEventsToLogs(events ptrace.SpanEventSlice) []*TraceLog {
for i := 0; i < events.Len(); i++ {
event := events.At(i)
fields := make([]*KeyValue, 0, event.Attributes().Len()+1)
- if event.Name() != "" {
- fields = append(fields, &KeyValue{
- Key: TagMessage,
- Value: event.Name(),
- })
- }
event.Attributes().Range(func(key string, attr pcommon.Value) bool {
fields = append(fields, &KeyValue{Key: key, Value: getAttributeVal(attr)})
return true
@@ -273,6 +268,7 @@ func spanEventsToLogs(events ptrace.SpanEventSlice) []*TraceLog {
logs = append(logs, &TraceLog{
Timestamp: float64(event.Timestamp()) / 1_000_000,
Fields: fields,
+ Name: event.Name(),
})
}
diff --git a/pkg/tsdb/tempo/trace_transform_test.go b/pkg/tsdb/tempo/trace_transform_test.go
index 8b901c6c41c..8253220655a 100644
--- a/pkg/tsdb/tempo/trace_transform_test.go
+++ b/pkg/tsdb/tempo/trace_transform_test.go
@@ -9,6 +9,7 @@ import (
"go.opentelemetry.io/collector/pdata/ptrace"
"github.com/grafana/grafana-plugin-sdk-go/data"
+ "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -57,7 +58,30 @@ func TestTraceToFrame(t *testing.T) {
require.Equal(t, json.RawMessage("[{\"value\":\"loki-all\",\"key\":\"service.name\"},{\"value\":\"Jaeger-Go-2.25.0\",\"key\":\"opencensus.exporterversion\"},{\"value\":\"4d019a031941\",\"key\":\"host.hostname\"},{\"value\":\"172.18.0.6\",\"key\":\"ip\"},{\"value\":\"4b19ace06df8e4de\",\"key\":\"client-uuid\"}]"), span["serviceTags"])
require.Equal(t, 1616072924072.852, span["startTime"])
require.Equal(t, 0.094, span["duration"])
- require.Equal(t, "[{\"timestamp\":1616072924072.856,\"fields\":[{\"value\":\"test event\",\"key\":\"message\"},{\"value\":1,\"key\":\"chunks requested\"}]},{\"timestamp\":1616072924072.9448,\"fields\":[{\"value\":1,\"key\":\"chunks fetched\"}]}]", string(span["logs"].(json.RawMessage)))
+ expectedLogs := `
+ [
+ {
+ "timestamp": 1616072924072.856,
+ "name": "test event",
+ "fields": [
+ {
+ "value": 1,
+ "key": "chunks requested"
+ }
+ ]
+ },
+ {
+ "timestamp": 1616072924072.9448,
+ "fields": [
+ {
+ "value": 1,
+ "key": "chunks fetched"
+ }
+ ]
+ }
+ ]
+ `
+ assert.JSONEq(t, expectedLogs, string(span["logs"].(json.RawMessage)))
})
t.Run("should transform correct traceID", func(t *testing.T) {
diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianKeyValues.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianKeyValues.tsx
index f2d0a4d9be2..29399e29552 100644
--- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianKeyValues.tsx
+++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianKeyValues.tsx
@@ -67,7 +67,6 @@ export const getStyles = (theme: GrafanaTheme2) => {
summaryItem: css`
label: summaryItem;
display: inline;
- margin-left: 0.7em;
padding-right: 0.5rem;
border-right: 1px solid ${autoColor(theme, '#ddd')};
&:last-child {
@@ -90,10 +89,11 @@ export const getStyles = (theme: GrafanaTheme2) => {
export type AccordianKeyValuesProps = {
className?: string | TNil;
data: TraceKeyValuePair[];
+ logName?: string;
highContrast?: boolean;
interactive?: boolean;
isOpen: boolean;
- label: string;
+ label: string | React.ReactNode;
linksGetter?: ((pairs: TraceKeyValuePair[], index: number) => TraceLink[]) | TNil;
onToggle?: null | (() => void);
};
@@ -127,6 +127,7 @@ export function KeyValuesSummary({ data = null }: KeyValuesSummaryProps) {
export default function AccordianKeyValues({
className = null,
data,
+ logName,
highContrast = false,
interactive = true,
isOpen,
@@ -134,11 +135,12 @@ export default function AccordianKeyValues({
linksGetter,
onToggle = null,
}: AccordianKeyValuesProps) {
- const isEmpty = !Array.isArray(data) || !data.length;
+ const isEmpty = (!Array.isArray(data) || !data.length) && !logName;
const styles = useStyles2(getStyles);
const iconCls = cx(alignIcon, { [styles.emptyIcon]: isEmpty });
let arrow: React.ReactNode | null = null;
let headerProps: {} | null = null;
+ const tableFields = logName ? [{ key: 'event name', value: logName }, ...data] : data;
if (interactive) {
arrow = isOpen ? (
@@ -152,6 +154,8 @@ export default function AccordianKeyValues({
};
}
+ const showDataSummaryFields = data.length > 0 && !isOpen;
+
return (
{label}
- {isOpen || ':'}
+ {showDataSummaryFields && ':'}
- {!isOpen && }
+ {showDataSummaryFields && (
+
+
+
+ )}
- {isOpen &&
}
+ {isOpen &&
}
);
}
diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianLogs.test.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianLogs.test.tsx
index 8e5db8dcebd..f69f3c8808f 100644
--- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianLogs.test.tsx
+++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianLogs.test.tsx
@@ -30,6 +30,7 @@ const logs = [
{ key: 'message', value: 'oh the next log message' },
{ key: 'more', value: 'stuff' },
],
+ name: 'foo event name',
},
];
@@ -72,4 +73,47 @@ describe('AccordianLogs tests', () => {
expect(screen.getByText(/^something$/)).toBeInTheDocument();
expect(screen.getByText(/^else$/)).toBeInTheDocument();
});
+
+ it('shows log entries and long event name when expanded', () => {
+ const longNameLog = {
+ timestamp: 20,
+ name: 'This is a very very very very very very very long name',
+ fields: [{ key: 'foo', value: 'test' }],
+ };
+
+ setup({
+ isOpen: true,
+ logs: [longNameLog],
+ openedItems: new Set([longNameLog]),
+ } as AccordianLogsProps);
+
+ expect(
+ screen.getByRole('switch', {
+ name: '15μs (This is a very very ...)',
+ })
+ ).toBeInTheDocument();
+
+ expect(screen.getByRole('table')).toBeInTheDocument();
+ expect(screen.queryAllByRole('cell')).toHaveLength(6);
+ expect(screen.getByText(/^event name$/)).toBeInTheDocument();
+ expect(screen.getByText(/This is a very very very very very very very long name/)).toBeInTheDocument();
+ });
+
+ it('renders event name and duration when events list is closed', () => {
+ setup({ isOpen: true, openedItems: new Set() } as AccordianLogsProps);
+ expect(
+ screen.getByRole('switch', {
+ name: '15μs (foo event name) : message = oh the next log message more = stuff',
+ })
+ ).toBeInTheDocument();
+ expect(
+ screen.getByRole('switch', { name: '5μs: message = oh the log message something = else' })
+ ).toBeInTheDocument();
+ });
+
+ it('renders event name and duration when events list is open', () => {
+ setup({ isOpen: true, openedItems: new Set(logs) } as AccordianLogsProps);
+ expect(screen.getByRole('switch', { name: '15μs (foo event name)' })).toBeInTheDocument();
+ expect(screen.getByRole('switch', { name: '5μs' })).toBeInTheDocument();
+ });
});
diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianLogs.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianLogs.tsx
index 9e8cefccd8c..6ecfec0f80d 100644
--- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianLogs.tsx
+++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianLogs.tsx
@@ -59,6 +59,9 @@ const getStyles = (theme: GrafanaTheme2) => {
AccordianKeyValuesItem: css({
marginBottom: theme.spacing(0.5),
}),
+ parenthesis: css({
+ color: `${autoColor(theme, '#777')}`,
+ }),
};
};
@@ -108,22 +111,35 @@ export default function AccordianLogs({
{isOpen && (
- {_sortBy(logs, 'timestamp').map((log, i) => (
-
onItemToggle(log) : null}
- />
- ))}
+ {_sortBy(logs, 'timestamp').map((log, i) => {
+ const formattedDuration = formatDuration(log.timestamp - timestamp);
+ const truncateLogNameInSummary = log.name && log.name.length > 20;
+ const formattedLogName = log.name && truncateLogNameInSummary ? log.name.slice(0, 20) + '...' : log.name;
+ const label = formattedLogName ? (
+
+ {formattedDuration} ({formattedLogName})
+
+ ) : (
+ formattedDuration
+ );
+ return (
+ onItemToggle(log) : null}
+ />
+ );
+ })}
- Log timestamps are relative to the start time of the full trace.
+ Event timestamps are relative to the start time of the full trace.
)}
diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx
index 005d3af52ea..92b0731d467 100644
--- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx
+++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx
@@ -48,7 +48,7 @@ export const getStyles = (theme: GrafanaTheme2) => {
row: css`
label: row;
& > td {
- padding: 0rem 0.5rem;
+ padding: 0.5rem 0.5rem;
height: 30px;
}
&:nth-child(2n) > td {
@@ -63,6 +63,7 @@ export const getStyles = (theme: GrafanaTheme2) => {
color: ${autoColor(theme, '#888')};
white-space: pre;
width: 125px;
+ vertical-align: top;
`,
copyColumn: css`
label: copyColumn;
diff --git a/public/app/features/explore/TraceView/components/types/trace.ts b/public/app/features/explore/TraceView/components/types/trace.ts
index 0507287b987..bee45e059b4 100644
--- a/public/app/features/explore/TraceView/components/types/trace.ts
+++ b/public/app/features/explore/TraceView/components/types/trace.ts
@@ -31,6 +31,7 @@ export type TraceLink = {
export type TraceLog = {
timestamp: number;
fields: TraceKeyValuePair[];
+ name?: string;
};
export type TraceProcess = {
diff --git a/public/app/features/explore/TraceView/components/utils/filter-spans.test.ts b/public/app/features/explore/TraceView/components/utils/filter-spans.test.ts
index 1cf04d8424a..4bae04a74a2 100644
--- a/public/app/features/explore/TraceView/components/utils/filter-spans.test.ts
+++ b/public/app/features/explore/TraceView/components/utils/filter-spans.test.ts
@@ -55,6 +55,7 @@ describe('filterSpans', () => {
],
logs: [
{
+ name: 'logName0',
fields: [
{
key: 'logFieldKey0',
@@ -316,6 +317,10 @@ describe('filterSpans', () => {
).toEqual(new Set([spanID0]));
});
+ it('it should return logs have a name which matches the filter', () => {
+ expect(filterSpans({ ...defaultFilters, query: 'logName0' }, spans)).toEqual(new Set([spanID0]));
+ });
+
it('should return no spans when logs is null', () => {
const nullSpan = { ...span0, logs: null };
expect(
diff --git a/public/app/features/explore/TraceView/components/utils/filter-spans.tsx b/public/app/features/explore/TraceView/components/utils/filter-spans.tsx
index 942a13a476b..9f6d908d52e 100644
--- a/public/app/features/explore/TraceView/components/utils/filter-spans.tsx
+++ b/public/app/features/explore/TraceView/components/utils/filter-spans.tsx
@@ -90,7 +90,8 @@ export function getQueryMatches(query: string, spans: TraceSpan[] | TNil) {
(span.instrumentationLibraryName && isTextInQuery(queryParts, span.instrumentationLibraryName)) ||
(span.instrumentationLibraryVersion && isTextInQuery(queryParts, span.instrumentationLibraryVersion)) ||
(span.traceState && isTextInQuery(queryParts, span.traceState)) ||
- (span.logs !== null && span.logs.some((log) => isTextInKeyValues(log.fields))) ||
+ (span.logs !== null &&
+ span.logs.some((log) => (log.name && isTextInQuery(queryParts, log.name)) || isTextInKeyValues(log.fields))) ||
isTextInKeyValues(span.process.tags) ||
queryParts.some((queryPart) => queryPart === span.spanID);
diff --git a/public/app/plugins/datasource/jaeger/_importedDependencies/types/trace.ts b/public/app/plugins/datasource/jaeger/_importedDependencies/types/trace.ts
index 0507287b987..bee45e059b4 100644
--- a/public/app/plugins/datasource/jaeger/_importedDependencies/types/trace.ts
+++ b/public/app/plugins/datasource/jaeger/_importedDependencies/types/trace.ts
@@ -31,6 +31,7 @@ export type TraceLink = {
export type TraceLog = {
timestamp: number;
fields: TraceKeyValuePair[];
+ name?: string;
};
export type TraceProcess = {
diff --git a/public/app/plugins/datasource/jaeger/types.ts b/public/app/plugins/datasource/jaeger/types.ts
index fe6778957f6..3fc5f8de320 100644
--- a/public/app/plugins/datasource/jaeger/types.ts
+++ b/public/app/plugins/datasource/jaeger/types.ts
@@ -14,6 +14,7 @@ export type TraceLink = {
export type TraceLog = {
timestamp: number;
fields: TraceKeyValuePair[];
+ name?: string;
};
export type TraceProcess = {
diff --git a/public/app/plugins/datasource/tempo/resultTransformer.ts b/public/app/plugins/datasource/tempo/resultTransformer.ts
index 592b043307b..b41458590d1 100644
--- a/public/app/plugins/datasource/tempo/resultTransformer.ts
+++ b/public/app/plugins/datasource/tempo/resultTransformer.ts
@@ -132,7 +132,7 @@ function getLogs(span: collectorTypes.opentelemetryProto.trace.v1.Span) {
fields.push({ key: attribute.key, value: getAttributeValue(attribute.value) });
}
}
- logs.push({ fields, timestamp: event.timeUnixNano / 1000000 });
+ logs.push({ fields, timestamp: event.timeUnixNano / 1000000, name: event.name });
}
}
@@ -364,7 +364,7 @@ function getOTLPEvents(logs: TraceLog[]): collectorTypes.opentelemetryProto.trac
timeUnixNano: log.timestamp * 1000000,
attributes: [],
droppedAttributesCount: 0,
- name: '',
+ name: log.name || '',
};
for (const field of log.fields) {
event.attributes!.push({
diff --git a/public/app/plugins/datasource/tempo/test/testResponse.ts b/public/app/plugins/datasource/tempo/test/testResponse.ts
index 493cb97501a..765e55cf08c 100644
--- a/public/app/plugins/datasource/tempo/test/testResponse.ts
+++ b/public/app/plugins/datasource/tempo/test/testResponse.ts
@@ -1920,7 +1920,7 @@ export const otlpDataFrameFromResponse = new MutableDataFrame({
name: 'logs',
type: FieldType.other,
config: {},
- values: [[]],
+ values: [[{ name: 'DNSDone', fields: [{ key: 'addr', value: '172.18.0.6' }] }]],
},
{
name: 'references',
@@ -2138,7 +2138,20 @@ export const otlpDataFrameToResponse = new MutableDataFrame({
name: 'logs',
type: FieldType.other,
config: {},
- values: [[]],
+ values: [
+ [
+ {
+ fields: [
+ {
+ key: 'addr',
+ value: '172.18.0.6',
+ },
+ ],
+ timestamp: 1627471657255.809,
+ name: 'DNSDone',
+ },
+ ],
+ ],
state: {
displayName: 'logs',
},
@@ -2240,6 +2253,14 @@ export const otlpResponse = {
{ key: 'http.url', value: { stringValue: '/' } },
{ key: 'component', value: { stringValue: 'net/http' } },
],
+ events: [
+ {
+ name: 'DNSDone',
+ attributes: [{ key: 'addr', value: { stringValue: '172.18.0.6' } }],
+ droppedAttributesCount: 0,
+ timeUnixNano: 1627471657255809000,
+ },
+ ],
links: [
{
spanId: 'spanId',