mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
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
This commit is contained in:
parent
8863fc29a4
commit
254b18c20b
14 changed files with 227 additions and 103 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -29,7 +29,6 @@ disabledPlugins
|
|||
webpackstats.json
|
||||
/config/*
|
||||
!/config/kibana.yml
|
||||
!/config/apm.js
|
||||
coverage
|
||||
selenium
|
||||
.babel_register_cache.json
|
||||
|
|
|
@ -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 {};
|
||||
}
|
||||
}
|
|
@ -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",
|
||||
|
|
80
src/apm.js
80
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');
|
||||
|
||||
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;
|
||||
|
|
|
@ -80,6 +80,9 @@ export interface InjectedMetadataParams {
|
|||
user?: Record<string, UserProvidedValues>;
|
||||
};
|
||||
};
|
||||
apm: {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -46,7 +46,6 @@ export const CopySourceTask = {
|
|||
'typings/**',
|
||||
'webpackShims/**',
|
||||
'config/kibana.yml',
|
||||
'config/apm.js',
|
||||
'tsconfig*.json',
|
||||
'.i18nrc.json',
|
||||
'kibana.d.ts',
|
||||
|
|
69
src/legacy/ui/apm/index.js
Normal file
69
src/legacy/ui/apm/index.js
Normal 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;
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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),
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -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 = () => {
|
|||
<Route component={ScrollToTopOnPathChange} />
|
||||
<Switch>
|
||||
{routes.map((route, i) => (
|
||||
<Route key={i} {...route} />
|
||||
<ApmRoute key={i} {...route} />
|
||||
))}
|
||||
</Switch>
|
||||
</MainContainer>
|
||||
|
|
7
x-pack/legacy/plugins/apm/typings/apm-rum-react.d.ts
vendored
Normal file
7
x-pack/legacy/plugins/apm/typings/apm-rum-react.d.ts
vendored
Normal 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';
|
|
@ -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",
|
||||
|
|
49
yarn.lock
49
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"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue