[APM] Add missing trace_id to transaction histogram (#27591)

* [APM] Add missing trace_id to transaction histogram

* Handle 404 properly

* Fix `unhandled` issue

* Fix tests
This commit is contained in:
Søren Louv-Jansen 2018-12-21 14:21:07 +01:00 committed by GitHub
parent c72dd3f2d9
commit c7bd296ac4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 100 additions and 89 deletions

View file

@ -34,7 +34,7 @@ describe('StickyProperties', () => {
{ {
label: 'Handled', label: 'Handled',
fieldName: 'error.exception.handled', fieldName: 'error.exception.handled',
val: 'true' val: true
}, },
{ {
label: 'User ID', label: 'User ID',

View file

@ -101,7 +101,7 @@ function getPropertyValue({
); );
} }
return <PropertyValue>{val}</PropertyValue>; return <PropertyValue>{String(val)}</PropertyValue>;
} }
export function StickyProperties({ export function StickyProperties({

View file

@ -40,6 +40,7 @@ export async function loadTransactionDistribution({
transactionName, transactionName,
transactionType = 'request', transactionType = 'request',
transactionId, transactionId,
traceId,
kuery kuery
}: Required<IUrlParams>) { }: Required<IUrlParams>) {
return callApi<ITransactionDistributionAPIResponse>({ return callApi<ITransactionDistributionAPIResponse>({
@ -50,6 +51,7 @@ export async function loadTransactionDistribution({
start, start,
end, end,
transactionId, transactionId,
traceId,
esFilterQuery: await getEncodedEsQuery(kuery) esFilterQuery: await getEncodedEsQuery(kuery)
} }
}); });

View file

@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { KFetchError } from 'ui/kfetch/kfetch_error';
import { TransactionAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/get_transaction'; import { TransactionAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/get_transaction';
import { SpanListAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/spans/get_spans'; import { SpanListAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/spans/get_spans';
import { Span } from 'x-pack/plugins/apm/typings/es_schemas/Span'; import { Span } from 'x-pack/plugins/apm/typings/es_schemas/Span';
@ -43,20 +44,31 @@ export async function loadTransaction({
traceId, traceId,
kuery kuery
}: IUrlParams) { }: IUrlParams) {
const result = await callApi<TransactionAPIResponse>( try {
{ const result = await callApi<TransactionAPIResponse>(
pathname: `/api/apm/services/${serviceName}/transactions/${transactionId}`, {
query: { pathname: `/api/apm/services/${serviceName}/transactions/${transactionId}`,
traceId, query: {
start, traceId,
end, start,
esFilterQuery: await getEncodedEsQuery(kuery) end,
esFilterQuery: await getEncodedEsQuery(kuery)
}
},
{
camelcase: false
} }
}, );
{ return addVersion(result);
camelcase: false } catch (e) {
} const err: KFetchError = e;
);
return addVersion(result); // swallow 404 errors
if (err.res.status === 404) {
return;
}
// re-throw all other errors
throw err;
}
} }

View file

@ -47,6 +47,7 @@ export function TransactionDistributionRequest({
serviceName, serviceName,
transactionType, transactionType,
transactionId, transactionId,
traceId,
start, start,
end, end,
transactionName, transactionName,
@ -66,6 +67,7 @@ export function TransactionDistributionRequest({
serviceName, serviceName,
transactionType, transactionType,
transactionId, transactionId,
traceId,
start, start,
end, end,
transactionName, transactionName,

View file

@ -45,6 +45,7 @@ export function bucketFetcher(
transactionName: string, transactionName: string,
transactionType: string, transactionType: string,
transactionId: string, transactionId: string,
traceId: string,
bucketSize: number, bucketSize: number,
setup: Setup setup: Setup
): Promise<ESResponse> { ): Promise<ESResponse> {
@ -77,6 +78,7 @@ export function bucketFetcher(
bool: { bool: {
filter, filter,
should: [ should: [
{ term: { [TRACE_ID]: traceId } },
{ term: { [TRANSACTION_ID]: transactionId } }, { term: { [TRANSACTION_ID]: transactionId } },
{ term: { [TRANSACTION_SAMPLED]: true } } { term: { [TRANSACTION_SAMPLED]: true } }
] ]

View file

@ -13,6 +13,7 @@ export async function getBuckets(
transactionName: string, transactionName: string,
transactionType: string, transactionType: string,
transactionId: string, transactionId: string,
traceId: string,
bucketSize: number, bucketSize: number,
setup: Setup setup: Setup
) { ) {
@ -21,6 +22,7 @@ export async function getBuckets(
transactionName, transactionName,
transactionType, transactionType,
transactionId, transactionId,
traceId,
bucketSize, bucketSize,
setup setup
); );

View file

@ -21,6 +21,7 @@ export async function getDistribution(
transactionName: string, transactionName: string,
transactionType: string, transactionType: string,
transactionId: string, transactionId: string,
traceId: string,
setup: Setup setup: Setup
): Promise<ITransactionDistributionAPIResponse> { ): Promise<ITransactionDistributionAPIResponse> {
const bucketSize = await calculateBucketSize( const bucketSize = await calculateBucketSize(
@ -35,6 +36,7 @@ export async function getDistribution(
transactionName, transactionName,
transactionType, transactionType,
transactionId, transactionId,
traceId,
bucketSize, bucketSize,
setup setup
); );

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { SearchParams } from 'elasticsearch'; import { ESFilter } from 'elasticsearch';
import { oc } from 'ts-optchain'; import { oc } from 'ts-optchain';
import { Transaction } from 'x-pack/plugins/apm/typings/es_schemas/Transaction'; import { Transaction } from 'x-pack/plugins/apm/typings/es_schemas/Transaction';
import { import {
@ -23,38 +23,40 @@ export async function getTransaction(
): Promise<TransactionAPIResponse> { ): Promise<TransactionAPIResponse> {
const { start, end, esFilterQuery, client, config } = setup; const { start, end, esFilterQuery, client, config } = setup;
const params: SearchParams = { const filter: ESFilter[] = [
index: config.get('apm_oss.transactionIndices'), { term: { [PROCESSOR_EVENT]: 'transaction' } },
{ term: { [TRANSACTION_ID]: transactionId } },
{
range: {
'@timestamp': {
gte: start,
lte: end,
format: 'epoch_millis'
}
}
}
];
if (esFilterQuery) {
filter.push(esFilterQuery);
}
if (traceId) {
filter.push({ term: { [TRACE_ID]: traceId } });
}
const params = {
index: config.get<string>('apm_oss.transactionIndices'),
body: { body: {
size: 1, size: 1,
query: { query: {
bool: { bool: {
filter: [ filter
{ term: { [PROCESSOR_EVENT]: 'transaction' } },
{ term: { [TRANSACTION_ID]: transactionId } },
{
range: {
'@timestamp': {
gte: start,
lte: end,
format: 'epoch_millis'
}
}
}
]
} }
} }
} }
}; };
if (esFilterQuery) {
params.body.query.bool.filter.push(esFilterQuery);
}
if (traceId) {
params.body.query.bool.filter.push({ term: { [TRACE_ID]: traceId } });
}
const resp = await client<Transaction>('search', params); const resp = await client<Transaction>('search', params);
return oc(resp).hits.hits[0]._source(); return oc(resp).hits.hits[0]._source();
} }

View file

@ -14,6 +14,7 @@ import { Setup } from '../../helpers/setup_request';
export type SpanListAPIResponse = Span[]; export type SpanListAPIResponse = Span[];
// Deprecated and will be removed in 7.0. Only needed for backwards compatability pre 6.5 (introducition of v2 API and distributed tracing)
export async function getSpans( export async function getSpans(
transactionId: string, transactionId: string,
setup: Setup setup: Setup

View file

@ -12,7 +12,6 @@ import { initServicesApi } from '../services';
// @ts-ignore // @ts-ignore
import { initStatusApi } from '../status_check'; import { initStatusApi } from '../status_check';
import { initTracesApi } from '../traces'; import { initTracesApi } from '../traces';
import { initTransactionsApi } from '../transactions';
describe('route handlers should fail with a Boom error', () => { describe('route handlers should fail with a Boom error', () => {
let consoleErrorSpy: any; let consoleErrorSpy: any;
@ -76,8 +75,4 @@ describe('route handlers should fail with a Boom error', () => {
describe('trace routes', async () => { describe('trace routes', async () => {
await testRouteFailures(initTracesApi); await testRouteFailures(initTracesApi);
}); });
describe('transaction routes', async () => {
await testRouteFailures(initTransactionsApi);
});
}); });

View file

@ -109,19 +109,24 @@ export function initTransactionGroupsApi(server: Server) {
options: { options: {
validate: { validate: {
query: withDefaultValidators({ query: withDefaultValidators({
transactionId: Joi.string().default('') transactionId: Joi.string().default(''),
traceId: Joi.string().default('')
}) })
} }
}, },
handler: req => { handler: req => {
const setup = setupRequest(req); const setup = setupRequest(req);
const { serviceName, transactionType, transactionName } = req.params; const { serviceName, transactionType, transactionName } = req.params;
const { transactionId } = req.query as { transactionId: string }; const { transactionId, traceId } = req.query as {
transactionId: string;
traceId: string;
};
return getDistribution( return getDistribution(
serviceName, serviceName,
transactionName, transactionName,
transactionType, transactionType,
transactionId, transactionId,
traceId,
setup setup
).catch(defaultErrorHandler); ).catch(defaultErrorHandler);
} }

View file

@ -12,12 +12,6 @@ import { setupRequest } from '../lib/helpers/setup_request';
import { getTransaction } from '../lib/transactions/get_transaction'; import { getTransaction } from '../lib/transactions/get_transaction';
import { getSpans } from '../lib/transactions/spans/get_spans'; import { getSpans } from '../lib/transactions/spans/get_spans';
const defaultErrorHandler = (err: Error) => {
// tslint:disable-next-line
console.error(err.stack);
throw Boom.boomify(err, { statusCode: 400 });
};
export function initTransactionsApi(server: Server) { export function initTransactionsApi(server: Server) {
server.route({ server.route({
method: 'GET', method: 'GET',
@ -29,13 +23,16 @@ export function initTransactionsApi(server: Server) {
}) })
} }
}, },
handler: req => { handler: async req => {
const { transactionId } = req.params; const { transactionId } = req.params;
const { traceId } = req.query as { traceId: string }; const { traceId } = req.query as { traceId: string };
const setup = setupRequest(req); const setup = setupRequest(req);
return getTransaction(transactionId, traceId, setup).catch( const transaction = await getTransaction(transactionId, traceId, setup);
defaultErrorHandler if (transaction) {
); return transaction;
} else {
throw Boom.notFound('Cannot find the requested page');
}
} }
}); });
@ -51,7 +48,7 @@ export function initTransactionsApi(server: Server) {
handler: req => { handler: req => {
const { transactionId } = req.params; const { transactionId } = req.params;
const setup = setupRequest(req); const setup = setupRequest(req);
return getSpans(transactionId, setup).catch(defaultErrorHandler); return getSpans(transactionId, setup);
} }
}); });
} }

View file

@ -82,35 +82,27 @@ uiModule.directive('monitoringMain', (breadcrumbs, license, kbnUrl, config) => {
link(scope, _element, attributes, controller) { link(scope, _element, attributes, controller) {
config.watch('k7design', (val) => scope.showPluginBreadcrumbs = !val); config.watch('k7design', (val) => scope.showPluginBreadcrumbs = !val);
function getSetupObj() { controller.setup({
return { licenseService: license,
licenseService: license, breadcrumbsService: breadcrumbs,
breadcrumbsService: breadcrumbs, kbnUrlService: kbnUrl,
kbnUrlService: kbnUrl, attributes: {
attributes: { name: attributes.name,
name: attributes.name, product: attributes.product,
product: attributes.product, instance: attributes.instance,
instance: attributes.instance, resolver: attributes.resolver,
resolver: attributes.resolver, page: attributes.page,
page: attributes.page, tabIconClass: attributes.tabIconClass,
tabIconClass: attributes.tabIconClass, tabIconLabel: attributes.tabIconLabel,
tabIconLabel: attributes.tabIconLabel, pipelineId: attributes.pipelineId,
pipelineId: attributes.pipelineId, pipelineHash: attributes.pipelineHash,
pipelineHash: attributes.pipelineHash, pipelineVersions: get(scope, 'pageData.versions')
pipelineVersions: get(scope, 'pageData.versions') },
}, clusterName: get(scope, 'cluster.cluster_name')
clusterName: get(scope, 'cluster.cluster_name') });
};
}
const setupObj = getSetupObj(); attributes.$observe('instance', instance => controller.instance = instance);
controller.setup(setupObj); attributes.$observe('resolver', resolver => controller.resolver = resolver);
Object.keys(setupObj.attributes).forEach(key => {
attributes.$observe(key, () => controller.setup(getSetupObj()));
});
scope.$watch('pageData.versions', versions => {
controller.pipelineVersions = versions;
});
} }
}; };
}); });

View file

@ -98,9 +98,6 @@ uiRoutes.when('/logstash/pipelines/:id/:hash?', {
getPageData, getPageData,
reactNodeId: 'monitoringLogstashPipelineApp', reactNodeId: 'monitoringLogstashPipelineApp',
$scope, $scope,
options: {
enableTimeFilter: false,
},
$injector $injector
}); });