diff --git a/.gitignore b/.gitignore index e7391a5c292d0..02b20da297fc6 100644 --- a/.gitignore +++ b/.gitignore @@ -29,7 +29,6 @@ disabledPlugins webpackstats.json /config/* !/config/kibana.yml -!/config/apm.js coverage selenium .babel_register_cache.json diff --git a/config/apm.js b/config/apm.js deleted file mode 100644 index 0cfcd759f163b..0000000000000 --- a/config/apm.js +++ /dev/null @@ -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 {}; - } -} diff --git a/package.json b/package.json index 25f8809f2ddd5..8855b01ec1475 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/apm.js b/src/apm.js index cea6f8fc072aa..e3f4d84d9b523 100644 --- a/src/apm.js +++ b/src/apm.js @@ -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; diff --git a/src/core/public/injected_metadata/injected_metadata_service.ts b/src/core/public/injected_metadata/injected_metadata_service.ts index 0bde1b68e1876..6a44000bf617e 100644 --- a/src/core/public/injected_metadata/injected_metadata_service.ts +++ b/src/core/public/injected_metadata/injected_metadata_service.ts @@ -80,6 +80,9 @@ export interface InjectedMetadataParams { user?: Record; }; }; + apm: { + [key: string]: unknown; + }; }; } diff --git a/src/dev/build/tasks/copy_source_task.js b/src/dev/build/tasks/copy_source_task.js index e5698c37ba16f..ee9dc159de47f 100644 --- a/src/dev/build/tasks/copy_source_task.js +++ b/src/dev/build/tasks/copy_source_task.js @@ -46,7 +46,6 @@ export const CopySourceTask = { 'typings/**', 'webpackShims/**', 'config/kibana.yml', - 'config/apm.js', 'tsconfig*.json', '.i18nrc.json', 'kibana.d.ts', diff --git a/src/legacy/ui/apm/index.js b/src/legacy/ui/apm/index.js new file mode 100644 index 0000000000000..e2ff415865b56 --- /dev/null +++ b/src/legacy/ui/apm/index.js @@ -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; +} diff --git a/src/legacy/ui/public/routes/route_manager.js b/src/legacy/ui/public/routes/route_manager.js index cf6413fb5ba7e..1cf2a5fc5a64a 100644 --- a/src/legacy/ui/public/routes/route_manager.js +++ b/src/legacy/ui/public/routes/route_manager.js @@ -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; diff --git a/src/legacy/ui/ui_bundles/app_entry_template.js b/src/legacy/ui/ui_bundles/app_entry_template.js index 6e270b24ff4a9..2a95b48e83603 100644 --- a/src/legacy/ui/ui_bundles/app_entry_template.js +++ b/src/legacy/ui/ui_bundles/app_entry_template.js @@ -17,6 +17,8 @@ * under the License. */ +import { apmImport, apmInit } from '../apm'; + export const appEntryTemplate = bundle => ` /** * Kibana entry file @@ -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) => { diff --git a/src/legacy/ui/ui_render/ui_render_mixin.js b/src/legacy/ui/ui_render/ui_render_mixin.js index e3189f02bbbf2..549d4fb167e11 100644 --- a/src/legacy/ui/ui_render/ui_render_mixin.js +++ b/src/legacy/ui/ui_render/ui_render_mixin.js @@ -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) { @@ -282,6 +283,8 @@ export function uiRenderMixin(kbnServer, server, config) { uiPlugins, legacyMetadata, + + apm: getApmConfig(legacyMetadata.app), }, }); diff --git a/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx b/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx index 002f6af7fe6cc..216af91fbb591 100644 --- a/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx +++ b/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx @@ -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 { @@ -52,7 +53,7 @@ const App = () => { {routes.map((route, i) => ( - + ))} diff --git a/x-pack/legacy/plugins/apm/typings/apm-rum-react.d.ts b/x-pack/legacy/plugins/apm/typings/apm-rum-react.d.ts new file mode 100644 index 0000000000000..6f500caabd824 --- /dev/null +++ b/x-pack/legacy/plugins/apm/typings/apm-rum-react.d.ts @@ -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'; diff --git a/x-pack/package.json b/x-pack/package.json index 83db779666c96..5bf84b1be4149 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -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", @@ -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", diff --git a/yarn.lock b/yarn.lock index 8a6c2e91d25d7..7ffd19d49bf55 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1336,6 +1336,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@^14.0.0": version "14.0.0" resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-14.0.0.tgz#410c87e9ae53df5848aae09a210fa7d08510b376" @@ -3609,7 +3634,7 @@ resolved "https://registry.yarnpkg.com/@types/hoek/-/hoek-4.1.3.tgz#d1982d48fb0d2a0e5d7e9d91838264d8e428d337" integrity sha1-0ZgtSPsNKg5dfp2Rg4Jk2OQo0zc= -"@types/hoist-non-react-statics@*": +"@types/hoist-non-react-statics@*", "@types/hoist-non-react-statics@^3.3.0": version "3.3.1" resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== @@ -11205,6 +11230,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" @@ -11308,6 +11340,11 @@ 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.8: + 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== + es6-promisify@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" @@ -21014,6 +21051,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" @@ -26402,6 +26444,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"