Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ObsUX] Span flyout show Otel plaintext stack trace #172489

Merged
merged 9 commits into from
Dec 18, 2023
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;
}
Loading