Revert "Include tracing information in the Kibana logs (#112973) (#119567)

* Revert "Include tracing information in the Kibana logs (#112973) (#118604)"

This reverts commit 114c690799.

* Revert "Don't enable RUM agent if APM is run with contextPropagationOnly (#118685) (#118995)"

This reverts commit ca86b98b5a.
This commit is contained in:
Mikhail Shustov 2021-11-24 13:03:37 +01:00 committed by GitHub
parent d8b02ccfaf
commit 8dcf2b795c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 33 additions and 413 deletions

View file

@ -40,10 +40,8 @@ export ELASTIC_APM_SECRET_TOKEN=7YKhoXsO4MzjhXjx2c
if is_pr; then
if [[ "${GITHUB_PR_LABELS:-}" == *"ci:collect-apm"* ]]; then
export ELASTIC_APM_ACTIVE=true
export ELASTIC_APM_CONTEXT_PROPAGATION_ONLY=false
else
export ELASTIC_APM_ACTIVE=true
export ELASTIC_APM_CONTEXT_PROPAGATION_ONLY=true
export ELASTIC_APM_ACTIVE=false
fi
if [[ "${GITHUB_STEP_COMMIT_STATUS_ENABLED:-}" != "true" ]]; then
@ -65,7 +63,6 @@ if is_pr; then
export PR_TARGET_BRANCH="$GITHUB_PR_TARGET_BRANCH"
else
export ELASTIC_APM_ACTIVE=true
export ELASTIC_APM_CONTEXT_PROPAGATION_ONLY=false
export CHECKS_REPORTER_ACTIVE=false
fi

View file

@ -217,7 +217,7 @@
"deep-freeze-strict": "^1.1.1",
"deepmerge": "^4.2.2",
"del": "^5.1.0",
"elastic-apm-node": "3.24.0",
"elastic-apm-node": "^3.23.0",
"execa": "^4.0.2",
"exit-hook": "^2.2.0",
"expiry-js": "0.1.7",

View file

@ -21,7 +21,7 @@ describe('ApmConfiguration', () => {
beforeEach(() => {
// start with an empty env to avoid CI from spoiling snapshots, env is unique for each jest file
process.env = {};
devConfigMock.raw = {};
packageMock.raw = {
version: '8.0.0',
build: {
@ -86,11 +86,10 @@ describe('ApmConfiguration', () => {
let config = new ApmConfiguration(mockedRootDir, {}, false);
expect(config.getConfig('serviceName')).toMatchInlineSnapshot(`
Object {
"active": true,
"active": false,
"breakdownMetrics": true,
"captureSpanStackTraces": false,
"centralConfig": false,
"contextPropagationOnly": true,
"environment": "development",
"globalLabels": Object {},
"logUncaughtExceptions": true,
@ -107,13 +106,12 @@ describe('ApmConfiguration', () => {
config = new ApmConfiguration(mockedRootDir, {}, true);
expect(config.getConfig('serviceName')).toMatchInlineSnapshot(`
Object {
"active": true,
"active": false,
"breakdownMetrics": false,
"captureBody": "off",
"captureHeaders": false,
"captureSpanStackTraces": false,
"centralConfig": false,
"contextPropagationOnly": true,
"environment": "development",
"globalLabels": Object {
"git_rev": "sha",
@ -166,12 +164,13 @@ describe('ApmConfiguration', () => {
it('does not load the configuration from the dev config in distributable', () => {
devConfigMock.raw = {
active: false,
active: true,
serverUrl: 'https://dev-url.co',
};
const config = new ApmConfiguration(mockedRootDir, {}, true);
expect(config.getConfig('serviceName')).toEqual(
expect.objectContaining({
active: true,
active: false,
})
);
});
@ -227,130 +226,4 @@ describe('ApmConfiguration', () => {
})
);
});
describe('contextPropagationOnly', () => {
it('sets "active: true" and "contextPropagationOnly: true" by default', () => {
expect(new ApmConfiguration(mockedRootDir, {}, false).getConfig('serviceName')).toEqual(
expect.objectContaining({
active: true,
contextPropagationOnly: true,
})
);
expect(new ApmConfiguration(mockedRootDir, {}, true).getConfig('serviceName')).toEqual(
expect.objectContaining({
active: true,
contextPropagationOnly: true,
})
);
});
it('value from config overrides the default', () => {
const kibanaConfig = {
elastic: {
apm: {
active: false,
contextPropagationOnly: false,
},
},
};
expect(
new ApmConfiguration(mockedRootDir, kibanaConfig, false).getConfig('serviceName')
).toEqual(
expect.objectContaining({
active: false,
contextPropagationOnly: false,
})
);
expect(
new ApmConfiguration(mockedRootDir, kibanaConfig, true).getConfig('serviceName')
).toEqual(
expect.objectContaining({
active: false,
contextPropagationOnly: false,
})
);
});
it('is "false" if "active: true" configured and "contextPropagationOnly" is not specified', () => {
const kibanaConfig = {
elastic: {
apm: {
active: true,
},
},
};
expect(
new ApmConfiguration(mockedRootDir, kibanaConfig, false).getConfig('serviceName')
).toEqual(
expect.objectContaining({
active: true,
contextPropagationOnly: false,
})
);
expect(
new ApmConfiguration(mockedRootDir, kibanaConfig, true).getConfig('serviceName')
).toEqual(
expect.objectContaining({
active: true,
contextPropagationOnly: false,
})
);
});
it('throws if "active: false" set without configuring "contextPropagationOnly: false"', () => {
const kibanaConfig = {
elastic: {
apm: {
active: false,
},
},
};
expect(() =>
new ApmConfiguration(mockedRootDir, kibanaConfig, false).getConfig('serviceName')
).toThrowErrorMatchingInlineSnapshot(
`"APM is disabled, but context propagation is enabled. Please disable context propagation with contextPropagationOnly:false"`
);
expect(() =>
new ApmConfiguration(mockedRootDir, kibanaConfig, true).getConfig('serviceName')
).toThrowErrorMatchingInlineSnapshot(
`"APM is disabled, but context propagation is enabled. Please disable context propagation with contextPropagationOnly:false"`
);
});
it('does not throw if "active: false" and "contextPropagationOnly: false" configured', () => {
const kibanaConfig = {
elastic: {
apm: {
active: false,
contextPropagationOnly: false,
},
},
};
expect(
new ApmConfiguration(mockedRootDir, kibanaConfig, false).getConfig('serviceName')
).toEqual(
expect.objectContaining({
active: false,
contextPropagationOnly: false,
})
);
expect(
new ApmConfiguration(mockedRootDir, kibanaConfig, true).getConfig('serviceName')
).toEqual(
expect.objectContaining({
active: false,
contextPropagationOnly: false,
})
);
});
});
});

View file

@ -17,8 +17,7 @@ import type { AgentConfigOptions as RUMAgentConfigOptions } from '@elastic/apm-r
// https://www.elastic.co/guide/en/apm/agent/nodejs/current/configuration.html
const DEFAULT_CONFIG: AgentConfigOptions = {
active: true,
contextPropagationOnly: true,
active: false,
environment: 'development',
logUncaughtExceptions: true,
globalLabels: {},
@ -75,8 +74,6 @@ export class ApmConfiguration {
private getBaseConfig() {
if (!this.baseConfig) {
const configFromSources = this.getConfigFromAllSources();
this.baseConfig = merge(
{
serviceVersion: this.kibanaVersion,
@ -85,7 +82,9 @@ export class ApmConfiguration {
this.getUuidConfig(),
this.getGitConfig(),
this.getCiConfig(),
configFromSources
this.getConfigFromKibanaConfig(),
this.getDevConfig(),
this.getConfigFromEnv()
);
/**
@ -118,12 +117,6 @@ export class ApmConfiguration {
config.active = true;
}
if (process.env.ELASTIC_APM_CONTEXT_PROPAGATION_ONLY === 'true') {
config.contextPropagationOnly = true;
} else if (process.env.ELASTIC_APM_CONTEXT_PROPAGATION_ONLY === 'false') {
config.contextPropagationOnly = false;
}
if (process.env.ELASTIC_APM_ENVIRONMENT || process.env.NODE_ENV) {
config.environment = process.env.ELASTIC_APM_ENVIRONMENT || process.env.NODE_ENV;
}
@ -260,28 +253,4 @@ export class ApmConfiguration {
return {};
}
}
/**
* Reads APM configuration from different sources and merges them together.
*/
private getConfigFromAllSources(): AgentConfigOptions {
const config = merge(
{},
this.getConfigFromKibanaConfig(),
this.getDevConfig(),
this.getConfigFromEnv()
);
if (config.active === false && config.contextPropagationOnly !== false) {
throw new Error(
'APM is disabled, but context propagation is enabled. Please disable context propagation with contextPropagationOnly:false'
);
}
if (config.active === true) {
config.contextPropagationOnly = config.contextPropagationOnly ?? false;
}
return config;
}
}

View file

@ -8,5 +8,4 @@
export { getConfiguration } from './config_loader';
export { initApm } from './init_apm';
export { shouldInstrumentClient } from './rum_agent_configuration';
export type { ApmConfiguration } from './config';

View file

@ -1,27 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { shouldInstrumentClient } from './rum_agent_configuration';
describe('shouldInstrumentClient', () => {
it('returns false if apm is disabled', () => {
expect(shouldInstrumentClient({ active: false })).toBe(false);
});
it('returns false if apm is enabled with contextPropagationOnly: true', () => {
expect(shouldInstrumentClient({ active: true, contextPropagationOnly: true })).toBe(false);
});
it('returns false if apm is enabled with disableSend: true', () => {
expect(shouldInstrumentClient({ active: true, disableSend: true })).toBe(false);
});
it('returns true if apm is enabled', () => {
expect(shouldInstrumentClient({ active: true })).toBe(true);
expect(shouldInstrumentClient({ active: true, contextPropagationOnly: false })).toBe(true);
expect(shouldInstrumentClient({ active: true, disableSend: false })).toBe(true);
});
});

View file

@ -1,14 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import type { AgentConfigOptions } from 'elastic-apm-node';
export function shouldInstrumentClient(config?: AgentConfigOptions): boolean {
return Boolean(
config?.active === true && config.contextPropagationOnly !== true && config.disableSend !== true
);
}

View file

@ -21,7 +21,4 @@ export interface LogRecord {
error?: Error;
meta?: LogMeta;
pid: number;
spanId?: string;
traceId?: string;
transactionId?: string;
}

View file

@ -7,10 +7,8 @@
*/
export const getConfigurationMock = jest.fn();
export const shouldInstrumentClientMock = jest.fn(() => true);
jest.doMock('@kbn/apm-config-loader', () => ({
getConfiguration: getConfigurationMock,
shouldInstrumentClient: shouldInstrumentClientMock,
}));
export const agentMock = {} as Record<string, any>;

View file

@ -6,11 +6,7 @@
* Side Public License, v 1.
*/
import {
getConfigurationMock,
agentMock,
shouldInstrumentClientMock,
} from './get_apm_config.test.mocks';
import { getConfigurationMock, agentMock } from './get_apm_config.test.mocks';
import { getApmConfig } from './get_apm_config';
const defaultApmConfig = {
@ -21,7 +17,6 @@ const defaultApmConfig = {
describe('getApmConfig', () => {
beforeEach(() => {
getConfigurationMock.mockReturnValue(defaultApmConfig);
shouldInstrumentClientMock.mockReturnValue(true);
});
afterEach(() => {
@ -30,7 +25,12 @@ describe('getApmConfig', () => {
});
it('returns null if apm is disabled', () => {
shouldInstrumentClientMock.mockReturnValue(false);
getConfigurationMock.mockReturnValue({
active: false,
});
expect(getApmConfig('/path')).toBeNull();
getConfigurationMock.mockReturnValue(undefined);
expect(getApmConfig('/path')).toBeNull();
});

View file

@ -7,11 +7,11 @@
*/
import agent from 'elastic-apm-node';
import { getConfiguration, shouldInstrumentClient } from '@kbn/apm-config-loader';
import { getConfiguration } from '@kbn/apm-config-loader';
export const getApmConfig = (requestPath: string) => {
const baseConfig = getConfiguration('kibana-frontend');
if (!shouldInstrumentClient(baseConfig)) {
if (!baseConfig?.active) {
return null;
}

View file

@ -117,10 +117,7 @@ Object {
"message": "trace message",
"meta": undefined,
"pid": Any<Number>,
"spanId": undefined,
"timestamp": 2012-02-01T14:33:22.011Z,
"traceId": undefined,
"transactionId": undefined,
}
`;
@ -136,9 +133,6 @@ Object {
"some": "value",
},
"pid": Any<Number>,
"spanId": undefined,
"timestamp": 2012-02-01T14:33:22.011Z,
"traceId": undefined,
"transactionId": undefined,
}
`;

View file

@ -88,26 +88,3 @@ Object {
},
}
`;
exports[`\`format()\` correctly formats record and includes correct ECS version. 7`] = `
Object {
"@timestamp": "2012-02-01T09:30:22.011-05:00",
"log": Object {
"level": "TRACE",
"logger": "context-7",
},
"message": "message-6",
"process": Object {
"pid": 5355,
},
"span": Object {
"id": "spanId-1",
},
"trace": Object {
"id": "traceId-1",
},
"transaction": Object {
"id": "transactionId-1",
},
}
`;

View file

@ -58,16 +58,6 @@ const records: LogRecord[] = [
timestamp,
pid: 5355,
},
{
context: 'context-7',
level: LogLevel.Trace,
message: 'message-6',
timestamp,
pid: 5355,
spanId: 'spanId-1',
traceId: 'traceId-1',
transactionId: 'transactionId-1',
},
];
test('`createConfigSchema()` creates correct schema.', () => {
@ -327,40 +317,3 @@ test('format() meta can not override timestamp', () => {
},
});
});
test('format() meta can not override tracing properties', () => {
const layout = new JsonLayout();
expect(
JSON.parse(
layout.format({
message: 'foo',
timestamp,
level: LogLevel.Debug,
context: 'bar',
pid: 3,
meta: {
span: { id: 'span_override' },
trace: { id: 'trace_override' },
transaction: { id: 'transaction_override' },
},
spanId: 'spanId-1',
traceId: 'traceId-1',
transactionId: 'transactionId-1',
})
)
).toStrictEqual({
ecs: { version: expect.any(String) },
'@timestamp': '2012-02-01T09:30:22.011-05:00',
message: 'foo',
log: {
level: 'DEBUG',
logger: 'bar',
},
process: {
pid: 3,
},
span: { id: 'spanId-1' },
trace: { id: 'traceId-1' },
transaction: { id: 'transactionId-1' },
});
});

View file

@ -54,9 +54,6 @@ export class JsonLayout implements Layout {
process: {
pid: record.pid,
},
span: record.spanId ? { id: record.spanId } : undefined,
trace: record.traceId ? { id: record.traceId } : undefined,
transaction: record.transactionId ? { id: record.transactionId } : undefined,
};
const output = record.meta ? merge({ ...record.meta }, log) : log;

View file

@ -5,7 +5,7 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import apmAgent from 'elastic-apm-node';
import { Appender, LogLevel, LogRecord, LoggerFactory, LogMeta, Logger } from '@kbn/logging';
function isError(x: any): x is Error {
@ -73,7 +73,6 @@ export class BaseLogger implements Logger {
meta,
timestamp: new Date(),
pid: process.pid,
...this.getTraceIds(),
};
}
@ -84,15 +83,6 @@ export class BaseLogger implements Logger {
meta,
timestamp: new Date(),
pid: process.pid,
...this.getTraceIds(),
};
}
private getTraceIds() {
return {
spanId: apmAgent.currentTraceIds['span.id'],
traceId: apmAgent.currentTraceIds['trace.id'],
transactionId: apmAgent.currentTraceIds['transaction.id'],
};
}
}

View file

@ -93,7 +93,7 @@ def withFunctionalTestEnv(List additionalEnvs = [], Closure closure) {
def corsTestServerPort = "64${parallelId}3"
// needed for https://github.com/elastic/kibana/issues/107246
def proxyTestServerPort = "64${parallelId}4"
def contextPropagationOnly = githubPr.isPr() ? "true" : "false"
def apmActive = githubPr.isPr() ? "false" : "true"
withEnv([
"CI_GROUP=${parallelId}",
@ -109,8 +109,7 @@ def withFunctionalTestEnv(List additionalEnvs = [], Closure closure) {
"KBN_NP_PLUGINS_BUILT=true",
"FLEET_PACKAGE_REGISTRY_PORT=${fleetPackageRegistryPort}",
"ALERTING_PROXY_PORT=${alertingProxyPort}",
"ELASTIC_APM_ACTIVE=true",
"ELASTIC_APM_CONTEXT_PROPAGATION_ONLY=${contextPropagationOnly}",
"ELASTIC_APM_ACTIVE=${apmActive}",
"ELASTIC_APM_TRANSACTION_SAMPLE_RATE=0.1",
] + additionalEnvs) {
closure()

View file

@ -42,7 +42,6 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
`--elasticsearch.hosts=${servers.elasticsearch.protocol}://${servers.elasticsearch.hostname}:${servers.elasticsearch.port}`,
`--elasticsearch.ssl.certificateAuthorities=${CA_CERT_PATH}`,
'--server.requestId.allowFromAnyIp=true',
'--execution_context.enabled=true',
'--logging.appenders.file.type=file',
`--logging.appenders.file.fileName=${logFilePath}`,

View file

@ -1,13 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import apmAgent from 'elastic-apm-node';
import { initApm } from '@kbn/apm-config-loader';
import { REPO_ROOT } from '@kbn/utils';
if (!apmAgent.isStarted()) {
initApm(process.argv, REPO_ROOT, false, 'test-plugin');
}

View file

@ -5,7 +5,6 @@
* 2.0.
*/
import './ensure_apm_started';
import { FixturePlugin } from './plugin';
export const plugin = () => new FixturePlugin();

View file

@ -4,7 +4,6 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import apmAgent from 'elastic-apm-node';
import { Plugin, CoreSetup } from 'kibana/server';
import { PluginSetupContract as AlertingPluginSetup } from '../../../../../../plugins/alerting/server/plugin';
@ -82,32 +81,6 @@ export class FixturePlugin implements Plugin<void, void, FixtureSetupDeps, Fixtu
await coreStart.elasticsearch.client.asInternalUser.ping();
},
});
const router = core.http.createRouter();
router.get(
{
path: '/emit_log_with_trace_id',
validate: false,
options: {
authRequired: false,
},
},
async (ctx, req, res) => {
const transaction = apmAgent.startTransaction();
const subscription = req.events.completed$.subscribe(() => {
transaction?.end();
subscription.unsubscribe();
});
await ctx.core.elasticsearch.client.asInternalUser.ping();
return res.ok({
body: {
traceId: apmAgent.currentTraceIds['trace.id'],
},
});
}
);
}
public start() {}

View file

@ -12,6 +12,5 @@ export default function ({ loadTestFile }: FtrProviderContext) {
this.tags('ciGroup1');
loadTestFile(require.resolve('./browser'));
loadTestFile(require.resolve('./server'));
loadTestFile(require.resolve('./log_correlation'));
});
}

View file

@ -1,38 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
import type { FtrProviderContext } from '../ftr_provider_context';
import { assertLogContains } from '../test_utils';
export default function ({ getService }: FtrProviderContext) {
const retry = getService('retry');
const supertest = getService('supertest');
describe('Log Correlation', () => {
it('Emits "trace.id" into the logs', async () => {
const response1 = await supertest
.get('/emit_log_with_trace_id')
.set('x-opaque-id', 'myheader1');
expect(response1.body.traceId).to.be.a('string');
const response2 = await supertest.get('/emit_log_with_trace_id');
expect(response1.body.traceId).to.be.a('string');
expect(response2.body.traceId).not.to.be(response1.body.traceId);
await assertLogContains({
description: 'traceId included in the Kibana logs',
predicate: (record) =>
// we don't check trace.id value since trace.id in the test plugin and Kibana are different on CI.
// because different 'elastic-apm-node' instaces are imported
Boolean(record.http?.request?.id?.includes('myheader1') && record.trace?.id),
retry,
});
});
});
}

View file

@ -32,7 +32,6 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
...functionalConfig.get('kbnTestServer'),
env: {
ELASTIC_APM_ACTIVE: 'true',
ELASTIC_APM_CONTEXT_PROPAGATION_ONLY: 'false',
ELASTIC_APM_ENVIRONMENT: process.env.CI ? 'ci' : 'development',
ELASTIC_APM_TRANSACTION_SAMPLE_RATE: '1.0',
ELASTIC_APM_SERVER_URL: APM_SERVER_URL,

View file

@ -12564,10 +12564,10 @@ ejs@^3.1.2, ejs@^3.1.6:
dependencies:
jake "^10.6.1"
elastic-apm-http-client@^10.3.0:
version "10.3.0"
resolved "https://registry.yarnpkg.com/elastic-apm-http-client/-/elastic-apm-http-client-10.3.0.tgz#12b95dc190a755cd1a8ce2c296cd28ef50f16aa4"
integrity sha512-BAqB7k5JA/x09L8BVj04WRoknRptmW2rLAoHQVrPvPhUm/IgNz63wPfiBuhWVE//Hl7xEpURO5pMV6az0UArkA==
elastic-apm-http-client@^10.1.0:
version "10.1.0"
resolved "https://registry.yarnpkg.com/elastic-apm-http-client/-/elastic-apm-http-client-10.1.0.tgz#8fbfa3f026f40d82b22b77bf4ed539cc20623edb"
integrity sha512-G+UsOQS8+kTyjbZ9PBXgbN8RGgeTe3FfbVljiwuN+eIf0UwpSR8k5Oh+Z2BELTTVwTcit7NCH4+B4MPayYx1mw==
dependencies:
breadth-filter "^2.0.0"
container-info "^1.0.1"
@ -12578,10 +12578,10 @@ elastic-apm-http-client@^10.3.0:
readable-stream "^3.4.0"
stream-chopper "^3.0.1"
elastic-apm-node@3.24.0:
version "3.24.0"
resolved "https://registry.yarnpkg.com/elastic-apm-node/-/elastic-apm-node-3.24.0.tgz#d7acb3352f928a23c28ebabab2bd30098562814e"
integrity sha512-Fmj/W2chWQa2zb1FfMYK2ypLB4TcnKNX+1klaJFbytRYDLgeSfo0EC7egvI3a+bLPZSRL5053PXOp7slVTPO6Q==
elastic-apm-node@^3.23.0:
version "3.23.0"
resolved "https://registry.yarnpkg.com/elastic-apm-node/-/elastic-apm-node-3.23.0.tgz#e842aa505d576003579803e45fe91f572db74a72"
integrity sha512-yzdO/MwAcjT+TbcBQBKWbDb4beDVmmrIaFCu9VA+z6Ow9GKlQv7QaD9/cQjuN8/KI6ASiJfQI8cPgqy1SgSUuA==
dependencies:
"@elastic/ecs-pino-format" "^1.2.0"
after-all-results "^2.0.0"
@ -12590,7 +12590,7 @@ elastic-apm-node@3.24.0:
basic-auth "^2.0.1"
cookie "^0.4.0"
core-util-is "^1.0.2"
elastic-apm-http-client "^10.3.0"
elastic-apm-http-client "^10.1.0"
end-of-stream "^1.4.4"
error-callsites "^2.0.4"
error-stack-parser "^2.0.6"