Skip to content

Commit

Permalink
Instrument Kibana with APM RUM agent (#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 Dec 18, 2019
1 parent 8863fc2 commit 254b18c
Show file tree
Hide file tree
Showing 14 changed files with 226 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 @@ -112,6 +112,7 @@
"dependencies": {
"@babel/core": "^7.5.5",
"@babel/register": "^7.7.0",
"@elastic/apm-rum": "^4.6.0",
"@elastic/charts": "^14.0.0",
"@elastic/datemath": "5.0.2",
"@elastic/ems-client": "1.0.5",
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 @@ -80,6 +80,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
6 changes: 5 additions & 1 deletion 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 @@ -34,12 +36,14 @@ 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
3 changes: 3 additions & 0 deletions src/legacy/ui/ui_render/ui_render_mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { AppBootstrap } from './bootstrap';
import { mergeVariables } from './lib';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { fromRoot } from '../../../core/server/utils';
import { getApmConfig } from '../apm';

export function uiRenderMixin(kbnServer, server, config) {
function replaceInjectedVars(request, injectedVars) {
Expand Down Expand Up @@ -282,6 +283,8 @@ export function uiRenderMixin(kbnServer, server, config) {
uiPlugins,

legacyMetadata,

apm: getApmConfig(legacyMetadata.app),
},
});

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 { metadata } from 'ui/metadata';
import {
Expand Down Expand Up @@ -52,7 +53,7 @@ const App = () => {
<Route component={ScrollToTopOnPathChange} />
<Switch>
{routes.map((route, i) => (
<Route key={i} {...route} />
<ApmRoute key={i} {...route} />
))}
</Switch>
</MainContainer>
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';
2 changes: 2 additions & 0 deletions x-pack/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"@types/graphql": "^0.13.2",
"@types/gulp": "^4.0.6",
"@types/hapi__wreck": "^15.0.1",
"@types/hoist-non-react-statics": "^3.3.0",
"@types/history": "^4.7.3",
"@types/jest": "24.0.19",
"@types/joi": "^13.4.2",
Expand Down Expand Up @@ -175,6 +176,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": "1.0.5",
"@elastic/eui": "17.0.0",
Expand Down
Loading

0 comments on commit 254b18c

Please sign in to comment.