Skip to content

Commit 254b18c

Browse files
Instrument Kibana with APM RUM agent (#44281)
* Instrument Kibana with APM RUM agent * make route-change transaction work with properl url * extract page-load transaction url from app link * check if app is hidden and set active:false * make distributed tracing work and merge config * remove config/apm.js and address review * address review comments * add apm.js to build tassks * move apm from dev to src * add @types/hoist-non-react-statics which is required by react rum * apply changes correctly from master
1 parent 8863fc2 commit 254b18c

File tree

14 files changed

+226
-102
lines changed

14 files changed

+226
-102
lines changed

.gitignore

-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ disabledPlugins
2929
webpackstats.json
3030
/config/*
3131
!/config/kibana.yml
32-
!/config/apm.js
3332
coverage
3433
selenium
3534
.babel_register_cache.json

config/apm.js

-87
This file was deleted.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@
112112
"dependencies": {
113113
"@babel/core": "^7.5.5",
114114
"@babel/register": "^7.7.0",
115+
"@elastic/apm-rum": "^4.6.0",
115116
"@elastic/charts": "^14.0.0",
116117
"@elastic/datemath": "5.0.2",
117118
"@elastic/ems-client": "1.0.5",

src/apm.js

+69-9
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,81 @@
1717
* under the License.
1818
*/
1919

20-
const { existsSync } = require('fs');
2120
const { join } = require('path');
22-
const { name, version } = require('../package.json');
21+
const { readFileSync } = require('fs');
22+
const { execSync } = require('child_process');
23+
const merge = require('lodash.merge');
24+
const { name, version, build } = require('../package.json');
2325

24-
module.exports = function(serviceName = name) {
25-
if (process.env.kbnWorkerType === 'optmzr') return;
26+
const ROOT_DIR = join(__dirname, '..');
27+
28+
function gitRev() {
29+
try {
30+
return execSync('git rev-parse --short HEAD', {
31+
encoding: 'utf-8',
32+
stdio: ['ignore', 'pipe', 'ignore'],
33+
}).trim();
34+
} catch (e) {
35+
return null;
36+
}
37+
}
38+
39+
function devConfig() {
40+
try {
41+
const apmDevConfigPath = join(ROOT_DIR, 'config', 'apm.dev.js');
42+
return require(apmDevConfigPath); // eslint-disable-line import/no-dynamic-require
43+
} catch (e) {
44+
return {};
45+
}
46+
}
47+
48+
const apmConfig = merge(
49+
{
50+
active: false,
51+
serverUrl: 'https://f1542b814f674090afd914960583265f.apm.us-central1.gcp.cloud.es.io:443',
52+
// The secretToken below is intended to be hardcoded in this file even though
53+
// it makes it public. This is not a security/privacy issue. Normally we'd
54+
// instead disable the need for a secretToken in the APM Server config where
55+
// the data is transmitted to, but due to how it's being hosted, it's easier,
56+
// for now, to simply leave it in.
57+
secretToken: 'R0Gjg46pE9K9wGestd',
58+
globalLabels: {},
59+
breakdownMetrics: true,
60+
centralConfig: false,
61+
logUncaughtExceptions: true,
62+
},
63+
devConfig()
64+
);
65+
66+
try {
67+
const filename = join(ROOT_DIR, 'data', 'uuid');
68+
apmConfig.globalLabels.kibana_uuid = readFileSync(filename, 'utf-8');
69+
} catch (e) {} // eslint-disable-line no-empty
2670

27-
const conf = {
28-
serviceName: `${serviceName}-${version.replace(/\./g, '_')}`,
71+
const rev = gitRev();
72+
if (rev !== null) apmConfig.globalLabels.git_rev = rev;
73+
74+
function getConfig(serviceName) {
75+
return {
76+
...apmConfig,
77+
...{
78+
serviceName: `${serviceName}-${version.replace(/\./g, '_')}`,
79+
},
2980
};
81+
}
82+
83+
/**
84+
* Flag to disable APM RUM support on all kibana builds by default
85+
*/
86+
const isKibanaDistributable = Boolean(build && build.distributable === true);
3087

31-
const configFile = join(__dirname, '..', 'config', 'apm.js');
88+
module.exports = function(serviceName = name) {
89+
if (process.env.kbnWorkerType === 'optmzr') return;
3290

33-
if (existsSync(configFile)) conf.configFile = configFile;
34-
else conf.active = false;
91+
const conf = getConfig(serviceName);
3592

3693
require('elastic-apm-node').start(conf);
3794
};
95+
96+
module.exports.getConfig = getConfig;
97+
module.exports.isKibanaDistributable = isKibanaDistributable;

src/core/public/injected_metadata/injected_metadata_service.ts

+3
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ export interface InjectedMetadataParams {
8080
user?: Record<string, UserProvidedValues>;
8181
};
8282
};
83+
apm: {
84+
[key: string]: unknown;
85+
};
8386
};
8487
}
8588

src/dev/build/tasks/copy_source_task.js

-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ export const CopySourceTask = {
4646
'typings/**',
4747
'webpackShims/**',
4848
'config/kibana.yml',
49-
'config/apm.js',
5049
'tsconfig*.json',
5150
'.i18nrc.json',
5251
'kibana.d.ts',

src/legacy/ui/apm/index.js

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
import { getConfig, isKibanaDistributable } from '../../../apm';
21+
import agent from 'elastic-apm-node';
22+
23+
const apmEnabled = !isKibanaDistributable && process.env.ELASTIC_APM_ACTIVE === 'true';
24+
25+
export function apmImport() {
26+
return apmEnabled ? 'import { init } from "@elastic/apm-rum"' : '';
27+
}
28+
29+
export function apmInit(config) {
30+
return apmEnabled ? `init(${config})` : '';
31+
}
32+
33+
export function getApmConfig(appMetadata) {
34+
if (!apmEnabled) {
35+
return {};
36+
}
37+
/**
38+
* we use the injected app metadata from the server to extract the
39+
* app URL path to be used for page-load transaction
40+
*/
41+
const navLink = appMetadata.getNavLink();
42+
const pageUrl = navLink ? navLink.toJSON().url : appMetadata._url;
43+
44+
const config = {
45+
...getConfig('kibana-frontend'),
46+
...{
47+
active: true,
48+
pageLoadTransactionName: pageUrl,
49+
},
50+
};
51+
/**
52+
* Get current active backend transaction to make distrubuted tracing
53+
* work for rendering the app
54+
*/
55+
const backendTransaction = agent.currentTransaction;
56+
57+
if (backendTransaction) {
58+
const { sampled, traceId } = backendTransaction;
59+
return {
60+
...config,
61+
...{
62+
pageLoadTraceId: traceId,
63+
pageLoadSampled: sampled,
64+
pageLoadSpanId: backendTransaction.ensureParentId(),
65+
},
66+
};
67+
}
68+
return config;
69+
}

src/legacy/ui/public/routes/route_manager.js

+17-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,23 @@ export default function RouteManager() {
5656
}
5757
};
5858

59-
self.run = function($location, $route, $injector) {
59+
self.run = function($location, $route, $injector, $rootScope) {
60+
if (window.elasticApm && typeof window.elasticApm.startTransaction === 'function') {
61+
/**
62+
* capture route-change events as transactions which happens after
63+
* the browser's on load event.
64+
*
65+
* In Kibana app, this logic would run after the boostrap js files gets
66+
* downloaded and get associated with the page-load transaction
67+
*/
68+
$rootScope.$on('$routeChangeStart', (_, nextRoute) => {
69+
if (nextRoute.$$route) {
70+
const name = nextRoute.$$route.originalPath;
71+
window.elasticApm.startTransaction(name, 'route-change');
72+
}
73+
});
74+
}
75+
6076
self.getBreadcrumbs = () => {
6177
const breadcrumbs = parsePathToBreadcrumbs($location.path());
6278
const map = $route.current.mapBreadcrumbs;

src/legacy/ui/ui_bundles/app_entry_template.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
* under the License.
1818
*/
1919

20+
import { apmImport, apmInit } from '../apm';
21+
2022
export const appEntryTemplate = bundle => `
2123
/**
2224
* Kibana entry file
@@ -34,12 +36,14 @@ import 'custom-event-polyfill';
3436
import 'whatwg-fetch';
3537
import 'abortcontroller-polyfill';
3638
import 'childnode-remove-polyfill';
37-
39+
${apmImport()}
3840
import { i18n } from '@kbn/i18n';
3941
import { CoreSystem } from '__kibanaCore__'
4042
4143
const injectedMetadata = JSON.parse(document.querySelector('kbn-injected-metadata').getAttribute('data'));
4244
45+
${apmInit('injectedMetadata.apm')}
46+
4347
i18n.load(injectedMetadata.i18n.translationsUrl)
4448
.catch(e => e)
4549
.then((i18nError) => {

src/legacy/ui/ui_render/ui_render_mixin.js

+3
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { AppBootstrap } from './bootstrap';
2828
import { mergeVariables } from './lib';
2929
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
3030
import { fromRoot } from '../../../core/server/utils';
31+
import { getApmConfig } from '../apm';
3132

3233
export function uiRenderMixin(kbnServer, server, config) {
3334
function replaceInjectedVars(request, injectedVars) {
@@ -282,6 +283,8 @@ export function uiRenderMixin(kbnServer, server, config) {
282283
uiPlugins,
283284

284285
legacyMetadata,
286+
287+
apm: getApmConfig(legacyMetadata.app),
285288
},
286289
});
287290

x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import React from 'react';
88
import ReactDOM from 'react-dom';
99
import { Route, Router, Switch } from 'react-router-dom';
10+
import { ApmRoute } from '@elastic/apm-rum-react';
1011
import styled from 'styled-components';
1112
import { metadata } from 'ui/metadata';
1213
import {
@@ -52,7 +53,7 @@ const App = () => {
5253
<Route component={ScrollToTopOnPathChange} />
5354
<Switch>
5455
{routes.map((route, i) => (
55-
<Route key={i} {...route} />
56+
<ApmRoute key={i} {...route} />
5657
))}
5758
</Switch>
5859
</MainContainer>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
declare module '@elastic/apm-rum-react';

x-pack/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
"@types/graphql": "^0.13.2",
6767
"@types/gulp": "^4.0.6",
6868
"@types/hapi__wreck": "^15.0.1",
69+
"@types/hoist-non-react-statics": "^3.3.0",
6970
"@types/history": "^4.7.3",
7071
"@types/jest": "24.0.19",
7172
"@types/joi": "^13.4.2",
@@ -175,6 +176,7 @@
175176
"@babel/core": "^7.5.5",
176177
"@babel/register": "^7.7.0",
177178
"@babel/runtime": "^7.5.5",
179+
"@elastic/apm-rum-react": "^0.3.2",
178180
"@elastic/datemath": "5.0.2",
179181
"@elastic/ems-client": "1.0.5",
180182
"@elastic/eui": "17.0.0",

0 commit comments

Comments
 (0)