[7.7] Instrument Kibana with APM RUM agent (#44281) (#62484)

* 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

* Remove unneded changes

* fix apminit

Co-authored-by: Vignesh Shanmugam <vignesh.shanmugam22@gmail.com>
This commit is contained in:
Nathan L Smith 2020-04-03 16:46:34 -05:00 committed by GitHub
parent cd60282794
commit b602bed491
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 220 additions and 103 deletions

1
.gitignore vendored
View file

@ -29,7 +29,6 @@ disabledPlugins
webpackstats.json
/config/*
!/config/kibana.yml
!/config/apm.js
coverage
selenium
.babel_register_cache.json

View file

@ -1,87 +0,0 @@
/*
* 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.
*/
/**
* DO NOT EDIT THIS FILE!
*
* This file contains the configuration for the Elastic APM instrumentaion of
* Kibana itself and is only intented to be used during development of Kibana.
*
* Instrumentation is turned off by default. Once activated it will send APM
* data to an Elasticsearch cluster accessible by Elastic employees.
*
* To modify the configuration, either use environment variables, or create a
* file named `config/apm.dev.js`, which exports a config object as described
* in the docs.
*
* For an overview over the available configuration files, see:
* https://www.elastic.co/guide/en/apm/agent/nodejs/current/configuration.html
*
* For general information about Elastic APM, see:
* https://www.elastic.co/guide/en/apm/get-started/current/index.html
*/
const { readFileSync } = require('fs');
const { join } = require('path');
const { execSync } = require('child_process');
const merge = require('lodash.merge');
module.exports = 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: {},
centralConfig: false,
logUncaughtExceptions: true,
},
devConfig()
);
const rev = gitRev();
if (rev !== null) module.exports.globalLabels.git_rev = rev;
try {
const filename = join(__dirname, '..', 'data', 'uuid');
module.exports.globalLabels.kibana_uuid = readFileSync(filename, 'utf-8');
} catch (e) {} // eslint-disable-line no-empty
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 {
return require('./apm.dev'); // eslint-disable-line import/no-unresolved
} catch (e) {
return {};
}
}

View file

@ -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",

View file

@ -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');
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 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);
module.exports = function(serviceName = name) {
if (process.env.kbnWorkerType === 'optmzr') return;
const conf = {
serviceName: `${serviceName}-${version.replace(/\./g, '_')}`,
};
const configFile = join(__dirname, '..', 'config', 'apm.js');
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;

View file

@ -46,7 +46,6 @@ export const CopySourceTask = {
'typings/**',
'webpackShims/**',
'config/kibana.yml',
'config/apm.js',
'tsconfig*.json',
'.i18nrc.json',
'kibana.d.ts',

View file

@ -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;
}

View file

@ -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;

View file

@ -17,6 +17,8 @@
* under the License.
*/
import { apmImport, apmInit } from '../apm';
export const appEntryTemplate = bundle => `
/**
* Kibana entry file
@ -26,11 +28,14 @@ export const appEntryTemplate = bundle => `
* context: ${bundle.getContext()}
*/
${apmImport()}
import { i18n } from '@kbn/i18n';
import { CoreSystem } from '__kibanaCore__'
const injectedMetadata = JSON.parse(document.querySelector('kbn-injected-metadata').getAttribute('data'));
${apmInit('injectedMetadata.vars.apmConfig')}
i18n.load(injectedMetadata.i18n.translationsUrl)
.catch(e => e)
.then((i18nError) => {

View file

@ -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
@ -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,

View file

@ -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';
@ -62,7 +63,7 @@ const App = () => {
<APMIndicesPermission>
<Switch>
{routes.map((route, i) => (
<Route key={i} {...route} />
<ApmRoute key={i} {...route} />
))}
</Switch>
</APMIndicesPermission>

View file

@ -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';

View file

@ -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",

View file

@ -2551,6 +2551,31 @@
debug "^3.1.0"
lodash.once "^4.1.1"
"@elastic/apm-rum-core@^4.7.0":
version "4.7.0"
resolved "https://registry.yarnpkg.com/@elastic/apm-rum-core/-/apm-rum-core-4.7.0.tgz#b00b58bf7380f2e36652e5333e3ca97608986e40"
integrity sha512-/lTZWfA3ces3qoKCx72Sc+w43lZkyktaQlbYoYO86h3tNX7tScc/7YBBHI9oxKMcXweqkKOcpnwNZFy71bb86w==
dependencies:
error-stack-parser "^1.3.5"
es6-promise "^4.2.8"
opentracing "^0.14.3"
uuid "^3.1.0"
"@elastic/apm-rum-react@^0.3.2":
version "0.3.2"
resolved "https://registry.yarnpkg.com/@elastic/apm-rum-react/-/apm-rum-react-0.3.2.tgz#134634643e15ebcf97b6f17b2c74a50afdbe1c64"
integrity sha512-hU1srW9noygppyrLmipulu30c+LWEie8V/dQjEqLYMx2mRZRwNIue3midYgWa6qrWqgYZhwpAtWrWcXc+AWk2Q==
dependencies:
"@elastic/apm-rum" "^4.6.0"
hoist-non-react-statics "^3.3.0"
"@elastic/apm-rum@^4.6.0":
version "4.6.0"
resolved "https://registry.yarnpkg.com/@elastic/apm-rum/-/apm-rum-4.6.0.tgz#e2ac560dd4a4761c0e9b08c301418b1d4063bdd2"
integrity sha512-hsqvyTm5rT6lKgV06wvm8ID9aMsuJyw8wIOPjRwKmvzlTjayabxKTcr50lJJV8jY9OWfDkqymIqpHyCEChQAHQ==
dependencies:
"@elastic/apm-rum-core" "^4.7.0"
"@elastic/charts@^18.1.1":
version "18.2.0"
resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-18.2.0.tgz#e141151b4d7ecc71c9f6f235f8ce141665c67195"
@ -13176,6 +13201,13 @@ error-ex@^1.2.0, error-ex@^1.3.1:
dependencies:
is-arrayish "^0.2.1"
error-stack-parser@^1.3.5:
version "1.3.6"
resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-1.3.6.tgz#e0e73b93e417138d1cd7c0b746b1a4a14854c292"
integrity sha1-4Oc7k+QXE40c18C3RrGkoUhUwpI=
dependencies:
stackframe "^0.3.1"
error-stack-parser@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.0.4.tgz#a757397dc5d9de973ac9a5d7d4e8ade7cfae9101"
@ -13297,7 +13329,7 @@ es6-promise@^4.2.5:
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.6.tgz#b685edd8258886365ea62b57d30de28fadcd974f"
integrity sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q==
es6-promise@~4.2.4:
es6-promise@^4.2.8, es6-promise@~4.2.4:
version "4.2.8"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==
@ -22911,6 +22943,11 @@ opener@^1.4.2:
resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.1.tgz#6d2f0e77f1a0af0032aca716c2c1fbb8e7e8abed"
integrity sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==
opentracing@^0.14.3:
version "0.14.4"
resolved "https://registry.yarnpkg.com/opentracing/-/opentracing-0.14.4.tgz#a113408ea740da3a90fde5b3b0011a375c2e4268"
integrity sha512-nNnZDkUNExBwEpb7LZaeMeQgvrlO8l4bgY/LvGNZCR0xG/dGWqHqjKrAmR5GUoYo0FIz38kxasvA1aevxWs2CA==
opn@^5.3.0:
version "5.4.0"
resolved "https://registry.yarnpkg.com/opn/-/opn-5.4.0.tgz#cb545e7aab78562beb11aa3bfabc7042e1761035"
@ -28421,6 +28458,11 @@ stack-utils@^1.0.1:
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.1.tgz#d4f33ab54e8e38778b0ca5cfd3b3afb12db68620"
integrity sha1-1PM6tU6OOHeLDKXP07OvsS22hiA=
stackframe@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-0.3.1.tgz#33aa84f1177a5548c8935533cbfeb3420975f5a4"
integrity sha1-M6qE8Rd6VUjIk1Uzy/6zQgl19aQ=
stackframe@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.1.0.tgz#e3fc2eb912259479c9822f7d1f1ff365bd5cbc83"