Skip to content

Commit

Permalink
[ObsUX] Span flyout show Otel plaintext stack trace (#172489)
Browse files Browse the repository at this point in the history
closes #169769

## Summary

This PR adapts the Stack Trace tab to also display plaintext stacktraces
generated by Otel agents

<img width="1201" alt="image"
src="https://github.com/elastic/kibana/assets/2767137/d0dafc6a-f251-4cd7-b50f-1e00d5e76e76">

<img width="1201" alt="image"
src="https://github.com/elastic/kibana/assets/2767137/a8ed7847-2d82-4716-9c27-41dae2375d7f">

<img width="1201" alt="image"
src="https://github.com/elastic/kibana/assets/2767137/118b61c2-c646-4cf1-b6c9-4aadc8e836ec">


### How to test

The easiest way to test:
```bash
node x-pack/plugins/apm/scripts/test/e2e.js --spec span_stacktrace.cy.ts   
```

---------

Co-authored-by: Kibana Machine <[email protected]>
  • Loading branch information
crespocarlos and kibanamachine authored Dec 18, 2023
1 parent 3419469 commit 4ee1b1f
Show file tree
Hide file tree
Showing 6 changed files with 220 additions and 5 deletions.
32 changes: 32 additions & 0 deletions packages/kbn-apm-synthtrace-client/src/lib/apm/apm_fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,35 @@ export interface GeoLocation {
type: string;
}

export interface APMStacktrace {
abs_path?: string;
classname?: string;
context?: {
post?: string[];
pre?: string[];
};
exclude_from_grouping?: boolean;
filename?: string;
function?: string;
module?: string;
library_frame?: boolean;
line?:
| {
column?: number;
number: number;
}
| {
context?: string;
};
sourcemap?: {
error?: string;
updated?: boolean;
};
vars?: {
[key: string]: unknown;
};
}

type ExperimentalFields = Partial<{
'metricset.interval': string;
'transaction.duration.summary': string;
Expand Down Expand Up @@ -80,6 +109,8 @@ export type ApmFields = Fields<{
'cloud.provider': string;
'cloud.region': string;
'cloud.service.name': string;
// otel
'code.stacktrace': string;
'container.id': string;
'destination.address': string;
'destination.port': number;
Expand Down Expand Up @@ -169,6 +200,7 @@ export type ApmFields = Fields<{
'span.duration.us': number;
'span.id': string;
'span.name': string;
'span.stacktrace': APMStacktrace[];
'span.self_time.count': number;
'span.self_time.sum.us': number;
'span.subtype': string;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { apm, timerange } from '@kbn/apm-synthtrace-client';
import { synthtrace } from '../../../synthtrace';

function getAPMGeneratedStacktrace() {
const apmGeneratedStacktrace = apm
.service({
name: 'apm-generated',
environment: 'production',
agentName: 'java',
})
.instance('instance a');

return Array.from(
timerange(
new Date('2022-01-01T00:00:00.000Z'),
new Date('2022-01-01T00:01:00.000Z')
)
.interval('1m')
.rate(1)
.generator((timestamp) => {
return apmGeneratedStacktrace
.transaction({ transactionName: `Transaction A` })
.defaults({
'service.language.name': 'java',
})
.timestamp(timestamp)
.duration(1000)
.success()
.children(
apmGeneratedStacktrace
.span({
spanName: `Span A`,
spanType: 'internal',
'span.stacktrace': [
{
library_frame: false,
exclude_from_grouping: false,
filename: 'OutputBuffer.java',
classname: 'org.apache.catalina.connector.OutputBuffer',
line: { number: 825 },
module: 'org.apache.catalina.connector',
function: 'flushByteBuffer',
},
],
})
.timestamp(timestamp + 50)
.duration(100)
.failure()
);
})
);
}

function getOtelGeneratedStacktrace() {
const apmGeneratedStacktrace = apm
.service({
name: 'otel-generated',
environment: 'production',
agentName: 'java',
})
.instance('instance a');

return Array.from(
timerange(
new Date('2022-01-01T00:00:00.000Z'),
new Date('2022-01-01T00:01:00.000Z')
)
.interval('1m')
.rate(1)
.generator((timestamp) => {
return apmGeneratedStacktrace
.transaction({ transactionName: `Transaction A` })
.timestamp(timestamp)
.duration(1000)
.defaults({
'service.language.name': 'java',
})
.success()
.children(
apmGeneratedStacktrace
.span({
spanName: `Span A`,
spanType: 'internal',
'code.stacktrace':
'java.lang.Throwable\n\tat co.elastic.otel.ElasticSpanProcessor.captureStackTrace(ElasticSpanProcessor.java:81)',
})
.timestamp(timestamp + 50)
.duration(100)
.failure()
);
})
);
}

export function generateSpanStacktraceData() {
const apmGeneratedStacktrace = getAPMGeneratedStacktrace();
const otelGeneratedStacktrace = getOtelGeneratedStacktrace();

synthtrace.index([...apmGeneratedStacktrace, ...otelGeneratedStacktrace]);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import url from 'url';
import { synthtrace } from '../../../synthtrace';
import { generateSpanStacktraceData } from './generate_span_stacktrace_data';

const start = '2022-01-01T00:00:00.000Z';
const end = '2022-01-01T00:15:00.000Z';

function getServiceInventoryUrl({ serviceName }: { serviceName: string }) {
return url.format({
pathname: `/app/apm/services/${serviceName}`,
query: {
rangeFrom: start,
rangeTo: end,
environment: 'ENVIRONMENT_ALL',
kuery: '',
serviceGroup: '',
transactionType: 'request',
comparisonEnabled: true,
offset: '1d',
},
});
}

describe('Span stacktrace', () => {
beforeEach(() => {
cy.loginAsViewerUser();
});
describe('span flyout', () => {
before(() => {
generateSpanStacktraceData();
});

after(() => {
synthtrace.clean();
});
it('Shows APM agent generated stacktrace', () => {
cy.visitKibana(getServiceInventoryUrl({ serviceName: 'apm-generated' }));
cy.contains('Transaction A').click();
cy.contains('Span A').click();
cy.getByTestSubj('spanStacktraceTab').click();
cy.contains(
'at org.apache.catalina.connector.OutputBuffer.flushByteBuffer(OutputBuffer.java:825)'
);
});

it('Shows Otel generated stacktrace', () => {
cy.visitKibana(getServiceInventoryUrl({ serviceName: 'otel-generated' }));
cy.contains('Transaction A').click();
cy.contains('Span A').click();
cy.getByTestSubj('spanStacktraceTab').click();
cy.contains(
`java.lang.Throwable at co.elastic.otel.ElasticSpanProcessor.captureStackTrace(ElasticSpanProcessor.java:81)`
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { euiStyled } from '@kbn/kibana-react-plugin/common';
import { ProcessorEvent } from '@kbn/observability-plugin/common';
import { isEmpty } from 'lodash';
import React, { Fragment } from 'react';
import { PlaintextStacktrace } from '../../../../../error_group_details/error_sampler/plaintext_stacktrace';
import { Span } from '../../../../../../../../typings/es_schemas/ui/span';
import { Transaction } from '../../../../../../../../typings/es_schemas/ui/transaction';
import { useFetcher, isPending } from '../../../../../../../hooks/use_fetcher';
Expand Down Expand Up @@ -204,6 +205,7 @@ function SpanFlyoutBody({
flyoutDetailTab?: string;
}) {
const stackframes = span.span.stacktrace;
const plaintextStacktrace = span.code?.stacktrace;
const codeLanguage = parentTransaction?.service.language?.name;
const spanDb = span.span.db;
const spanTypes = getSpanTypes(span);
Expand Down Expand Up @@ -232,10 +234,11 @@ function SpanFlyoutBody({
</Fragment>
),
},
...(!isEmpty(stackframes)
...(!isEmpty(stackframes) || !isEmpty(plaintextStacktrace)
? [
{
id: 'stack-trace',
'data-test-subj': 'spanStacktraceTab',
name: i18n.translate(
'xpack.apm.transactionDetails.spanFlyout.stackTraceTabLabel',
{
Expand All @@ -245,10 +248,17 @@ function SpanFlyoutBody({
content: (
<Fragment>
<EuiSpacer size="l" />
<Stacktrace
stackframes={stackframes}
codeLanguage={codeLanguage}
/>
{stackframes ? (
<Stacktrace
stackframes={stackframes}
codeLanguage={codeLanguage}
/>
) : (
<PlaintextStacktrace
stacktrace={plaintextStacktrace}
codeLanguage={codeLanguage}
/>
)}
</Fragment>
),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { Stackframe as StackframeComponent } from './stackframe';
interface Props {
stackframes?: Stackframe[];
codeLanguage?: string;
stackTrace?: string;
}

export function Stacktrace({ stackframes = [], codeLanguage }: Props) {
Expand Down
3 changes: 3 additions & 0 deletions x-pack/plugins/apm/typings/es_schemas/raw/span_raw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ export interface SpanRaw extends APMBaseDoc {
id: string;
};
child?: { id: string[] };
code?: {
stacktrace?: string;
};
http?: Http;
url?: Url;
}

0 comments on commit 4ee1b1f

Please sign in to comment.