Skip to content

Commit

Permalink
Instrument Kibana with APM RUM agent (elastic#44281)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
vigneshshanmugam authored and smith committed Apr 3, 2020
1 parent d4bb09f commit 78a32f3
Show file tree
Hide file tree
Showing 14 changed files with 230 additions and 102 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ disabledPlugins
webpackstats.json
/config/*
!/config/kibana.yml
!/config/apm.js
coverage
selenium
.babel_register_cache.json
Expand Down
87 changes: 0 additions & 87 deletions config/apm.js

This file was deleted.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@
"dependencies": {
"@babel/core": "^7.5.5",
"@babel/register": "^7.7.0",
"@elastic/apm-rum": "^4.6.0",
"@elastic/charts": "^18.1.1",
"@elastic/datemath": "5.0.2",
"@elastic/ems-client": "7.7.1",
Expand Down
78 changes: 69 additions & 9 deletions src/apm.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,81 @@
* under the License.
*/

const { existsSync } = require('fs');
const { join } = require('path');
const { name, version } = require('../package.json');
const { readFileSync } = require('fs');
const { execSync } = require('child_process');
const merge = require('lodash.merge');
const { name, version, build } = require('../package.json');

module.exports = function(serviceName = name) {
if (process.env.kbnWorkerType === 'optmzr') return;
const ROOT_DIR = join(__dirname, '..');

function gitRev() {
try {
return execSync('git rev-parse --short HEAD', {
encoding: 'utf-8',
stdio: ['ignore', 'pipe', 'ignore'],
}).trim();
} catch (e) {
return null;
}
}

function devConfig() {
try {
const apmDevConfigPath = join(ROOT_DIR, 'config', 'apm.dev.js');
return require(apmDevConfigPath); // eslint-disable-line import/no-dynamic-require
} catch (e) {
return {};
}
}

const apmConfig = merge(
{
active: false,
serverUrl: 'https://f1542b814f674090afd914960583265f.apm.us-central1.gcp.cloud.es.io:443',
// The secretToken below is intended to be hardcoded in this file even though
// it makes it public. This is not a security/privacy issue. Normally we'd
// instead disable the need for a secretToken in the APM Server config where
// the data is transmitted to, but due to how it's being hosted, it's easier,
// for now, to simply leave it in.
secretToken: 'R0Gjg46pE9K9wGestd',
globalLabels: {},
breakdownMetrics: true,
centralConfig: false,
logUncaughtExceptions: true,
},
devConfig()
);

try {
const filename = join(ROOT_DIR, 'data', 'uuid');
apmConfig.globalLabels.kibana_uuid = readFileSync(filename, 'utf-8');
} catch (e) {} // eslint-disable-line no-empty

const conf = {
serviceName: `${serviceName}-${version.replace(/\./g, '_')}`,
const rev = gitRev();
if (rev !== null) apmConfig.globalLabels.git_rev = rev;

function getConfig(serviceName) {
return {
...apmConfig,
...{
serviceName: `${serviceName}-${version.replace(/\./g, '_')}`,
},
};
}

/**
* Flag to disable APM RUM support on all kibana builds by default
*/
const isKibanaDistributable = Boolean(build && build.distributable === true);

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

if (existsSync(configFile)) conf.configFile = configFile;
else conf.active = false;
const conf = getConfig(serviceName);

require('elastic-apm-node').start(conf);
};

module.exports.getConfig = getConfig;
module.exports.isKibanaDistributable = isKibanaDistributable;
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ export interface InjectedMetadataParams {
user?: Record<string, UserProvidedValues>;
};
};
apm: {
[key: string]: unknown;
};
};
}

Expand Down
1 change: 0 additions & 1 deletion src/dev/build/tasks/copy_source_task.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ export const CopySourceTask = {
'typings/**',
'webpackShims/**',
'config/kibana.yml',
'config/apm.js',
'tsconfig*.json',
'.i18nrc.json',
'kibana.d.ts',
Expand Down
69 changes: 69 additions & 0 deletions src/legacy/ui/apm/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { getConfig, isKibanaDistributable } from '../../../apm';
import agent from 'elastic-apm-node';

const apmEnabled = !isKibanaDistributable && process.env.ELASTIC_APM_ACTIVE === 'true';

export function apmImport() {
return apmEnabled ? 'import { init } from "@elastic/apm-rum"' : '';
}

export function apmInit(config) {
return apmEnabled ? `init(${config})` : '';
}

export function getApmConfig(appMetadata) {
if (!apmEnabled) {
return {};
}
/**
* we use the injected app metadata from the server to extract the
* app URL path to be used for page-load transaction
*/
const navLink = appMetadata.getNavLink();
const pageUrl = navLink ? navLink.toJSON().url : appMetadata._url;

const config = {
...getConfig('kibana-frontend'),
...{
active: true,
pageLoadTransactionName: pageUrl,
},
};
/**
* Get current active backend transaction to make distrubuted tracing
* work for rendering the app
*/
const backendTransaction = agent.currentTransaction;

if (backendTransaction) {
const { sampled, traceId } = backendTransaction;
return {
...config,
...{
pageLoadTraceId: traceId,
pageLoadSampled: sampled,
pageLoadSpanId: backendTransaction.ensureParentId(),
},
};
}
return config;
}
18 changes: 17 additions & 1 deletion src/legacy/ui/public/routes/route_manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,23 @@ export default function RouteManager() {
}
};

self.run = function($location, $route, $injector) {
self.run = function($location, $route, $injector, $rootScope) {
if (window.elasticApm && typeof window.elasticApm.startTransaction === 'function') {
/**
* capture route-change events as transactions which happens after
* the browser's on load event.
*
* In Kibana app, this logic would run after the boostrap js files gets
* downloaded and get associated with the page-load transaction
*/
$rootScope.$on('$routeChangeStart', (_, nextRoute) => {
if (nextRoute.$$route) {
const name = nextRoute.$$route.originalPath;
window.elasticApm.startTransaction(name, 'route-change');
}
});
}

self.getBreadcrumbs = () => {
const breadcrumbs = parsePathToBreadcrumbs($location.path());
const map = $route.current.mapBreadcrumbs;
Expand Down
13 changes: 13 additions & 0 deletions src/legacy/ui/ui_bundles/app_entry_template.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
* under the License.
*/

import { apmImport, apmInit } from '../apm';

export const appEntryTemplate = bundle => `
/**
* Kibana entry file
Expand All @@ -26,11 +28,22 @@ export const appEntryTemplate = bundle => `
* context: ${bundle.getContext()}
*/
// import global polyfills
import Symbol_observable from 'symbol-observable';
import 'core-js/stable';
import 'regenerator-runtime/runtime';
import 'custom-event-polyfill';
import 'whatwg-fetch';
import 'abortcontroller-polyfill';
import 'childnode-remove-polyfill';
${apmImport()}
import { i18n } from '@kbn/i18n';
import { CoreSystem } from '__kibanaCore__'
const injectedMetadata = JSON.parse(document.querySelector('kbn-injected-metadata').getAttribute('data'));
${apmInit('injectedMetadata.apm')}
i18n.load(injectedMetadata.i18n.translationsUrl)
.catch(e => e)
.then((i18nError) => {
Expand Down
6 changes: 5 additions & 1 deletion src/legacy/ui/ui_render/ui_render_mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { AppBootstrap } from './bootstrap';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { fromRoot } from '../../../core/server/utils';
import { DllCompiler } from '../../../optimize/dynamic_dll_plugin';
import { getApmConfig } from '../apm';

/**
* @typedef {import('../../server/kbn_server').default} KbnServer
Expand Down Expand Up @@ -191,7 +192,10 @@ export function uiRenderMixin(kbnServer, server, config) {
uiSettings: { asScopedToClient },
} = kbnServer.newPlatform.__internals;
const uiSettings = asScopedToClient(savedObjects.getClient(h.request));
const vars = await legacy.getVars(app.getId(), h.request, overrides);
const vars = await legacy.getVars(app.getId(), h.request, {
apmConfig: getApmConfig(app),
...overrides,
});
const content = await rendering.render(h.request, uiSettings, {
app,
includeUserSettings,
Expand Down
3 changes: 2 additions & 1 deletion x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { Route, Router, Switch } from 'react-router-dom';
import { ApmRoute } from '@elastic/apm-rum-react';
import styled from 'styled-components';
import { i18n } from '@kbn/i18n';
import { AlertType } from '../../../../../plugins/apm/common/alert_types';
Expand Down Expand Up @@ -62,7 +63,7 @@ const App = () => {
<APMIndicesPermission>
<Switch>
{routes.map((route, i) => (
<Route key={i} {...route} />
<ApmRoute key={i} {...route} />
))}
</Switch>
</APMIndicesPermission>
Expand Down
7 changes: 7 additions & 0 deletions x-pack/legacy/plugins/apm/typings/apm-rum-react.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

declare module '@elastic/apm-rum-react';
1 change: 1 addition & 0 deletions x-pack/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@
"@babel/core": "^7.5.5",
"@babel/register": "^7.7.0",
"@babel/runtime": "^7.5.5",
"@elastic/apm-rum-react": "^0.3.2",
"@elastic/datemath": "5.0.2",
"@elastic/ems-client": "7.7.1",
"@elastic/eui": "21.0.1",
Expand Down
Loading

0 comments on commit 78a32f3

Please sign in to comment.