feat(telemetry): add OpenTelemetry tracing MVC (#15731)

This commit is contained in:
Sebastian Poxhofer 2022-10-23 06:36:35 +02:00 committed by Rhys Arkins
parent b5a515b533
commit b53c581e5c
19 changed files with 1055 additions and 36 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

View file

@ -0,0 +1,233 @@
# OpenTelemetry
Requirements:
- docker-compose
## Prepare setup
Create a `docker-compose.yaml` and `otel-collector-config.yml` file as seen below in a folder.
`docker-compose.yaml`:
```yaml
version: '3'
services:
# Jaeger
jaeger:
image: jaegertracing/all-in-one:1
ports:
- '16686:16686'
- '14250'
otel-collector:
image: otel/opentelemetry-collector-contrib:0.52.0
command: ['--config=/etc/otel-collector-config.yml']
volumes:
- ./otel-collector-config.yml:/etc/otel-collector-config.yml
ports:
- '1888:1888' # pprof extension
- '13133:13133' # health_check extension
- '55679:55679' # zpages extension
- '4318:4318' # OTLP HTTP
- '4317:4317' # OTLP GRPC
- '9123:9123' # Prometheus exporter
depends_on:
- jaeger
```
`otel-collector-config.yml`:
```yaml
receivers:
otlp:
protocols:
grpc:
http:
exporters:
jaeger:
endpoint: jaeger:14250
tls:
insecure: true
logging:
prometheus:
endpoint: '0.0.0.0:9123'
processors:
batch:
spanmetrics:
metrics_exporter: prometheus
latency_histogram_buckets: [10ms, 100ms, 250ms, 1s, 30s, 1m, 5m]
dimensions:
- name: http.method
- name: http.status_code
- name: http.host
dimensions_cache_size: 1000
aggregation_temporality: 'AGGREGATION_TEMPORALITY_CUMULATIVE'
extensions:
health_check:
pprof:
zpages:
service:
extensions: [pprof, zpages, health_check]
pipelines:
traces:
receivers: [otlp]
exporters: [jaeger, logging]
processors: [spanmetrics, batch]
metrics:
receivers: [otlp]
exporters: [prometheus]
```
Start setup using this command inside the folder containing the files created in the earlier steps:
```
docker-compose up
```
This command will start an [OpenTelemetry Collector](https://github.com/open-telemetry/opentelemetry-collector-contrib) and an instance of [Jaeger](https://www.jaegertracing.io/).
Jaeger will be now reachable under [http://localhost:16686](http://localhost:16686).
## Run Renovate with OpenTelemetry
To start Renovate with OpenTelemetry enabled run following command, after pointing to your `config.js` config file:
```
docker run \
--rm \
-e OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 \
-v "/path/to/your/config.js:/usr/src/app/config.js" \
renovate/renovate:latest
```
You should now see `trace_id` and `span_id` fields in the logs.
```
INFO: Repository finished (repository=org/example)
"durationMs": 5574,
"trace_id": "f9a4c33852333fc2a0fbdc163100c987",
"span_id": "4ac1323eeaee
```
### Traces
Open now Jaeger under [http://localhost:16686](http://localhost:16686).
You should now be able to pick `renovate` under in the field `service` field.
![service picker](../assets/images/opentelemetry_pick_service.png)
Press `Find Traces` to search for all Renovate traces and then click on one of the found traces to open the trace view.
![pick trace](../assets/images/opentelemetry_choose_trace.png)
You should be able to see now the full trace view which shows each HTTP request and internal spans.
![trace view](../assets/images/opentelemetry_trace_viewer.png)
### Metrics
Additional to the received traces some metrics are calculated.
This is achieved using the [spanmetricsprocessor](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/spanmetricsprocessor).
The previous implemented setup will produce following metrics, which are exposed under [http://localhost:9123/metrics](http://localhost:9123/metrics):
```
# HELP calls_total
# TYPE calls_total counter
### Example of internal spans
calls_total{operation="renovate repository",service_name="renovate",span_kind="SPAN_KIND_INTERNAL",status_code="STATUS_CODE_UNSET"} 3
calls_total{operation="run",service_name="renovate",span_kind="SPAN_KIND_INTERNAL",status_code="STATUS_CODE_UNSET"} 1
### Example of http calls from Renovate to external services
calls_total{http_host="api.github.com:443",http_method="POST",http_status_code="200",operation="HTTPS POST",service_name="renovate",span_kind="SPAN_KIND_CLIENT",status_code="STATUS_CODE_UNSET"} 9
...
# HELP latency
# TYPE latency histogram
### Example of internal spans
latency_bucket{operation="renovate repository",service_name="renovate",span_kind="SPAN_KIND_INTERNAL",status_code="STATUS_CODE_UNSET",le="0.1"} 0
...
latency_bucket{operation="renovate repository",service_name="renovate",span_kind="SPAN_KIND_INTERNAL",status_code="STATUS_CODE_UNSET",le="9.223372036854775e+12"} 3
latency_bucket{operation="renovate repository",service_name="renovate",span_kind="SPAN_KIND_INTERNAL",status_code="STATUS_CODE_UNSET",le="+Inf"} 3
latency_sum{operation="renovate repository",service_name="renovate",span_kind="SPAN_KIND_INTERNAL",status_code="STATUS_CODE_UNSET"} 30947.4689
latency_count{operation="renovate repository",service_name="renovate",span_kind="SPAN_KIND_INTERNAL",status_code="STATUS_CODE_UNSET"} 3
...
### Example of http calls from Renovate to external services
latency_bucket{http_host="api.github.com:443",http_method="POST",http_status_code="200",operation="HTTPS POST",service_name="renovate",span_kind="SPAN_KIND_CLIENT",status_code="STATUS_CODE_UNSET",le="0.1"} 0
...
latency_bucket{http_host="api.github.com:443",http_method="POST",http_status_code="200",operation="HTTPS POST",service_name="renovate",span_kind="SPAN_KIND_CLIENT",status_code="STATUS_CODE_UNSET",le="250"} 3
latency_bucket{http_host="api.github.com:443",http_method="POST",http_status_code="200",operation="HTTPS POST",service_name="renovate",span_kind="SPAN_KIND_CLIENT",status_code="STATUS_CODE_UNSET",le="9.223372036854775e+12"} 9
latency_bucket{http_host="api.github.com:443",http_method="POST",http_status_code="200",operation="HTTPS POST",service_name="renovate",span_kind="SPAN_KIND_CLIENT",status_code="STATUS_CODE_UNSET",le="+Inf"} 9
latency_sum{http_host="api.github.com:443",http_method="POST",http_status_code="200",operation="HTTPS POST",service_name="renovate",span_kind="SPAN_KIND_CLIENT",status_code="STATUS_CODE_UNSET"} 2306.1385999999998
latency_count{http_host="api.github.com:443",http_method="POST",http_status_code="200",operation="HTTPS POST",service_name="renovate",span_kind="SPAN_KIND_CLIENT",status_code="STATUS_CODE_UNSET"} 9
```
The [spanmetricsprocessor](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/spanmetricsprocessor) creates two sets of metrics.
#### Calls metric
At first there are the `calls_total` metrics which display how often specific trace spans have been observed.
For example:
`calls_total{operation="renovate repository",service_name="renovate",span_kind="SPAN_KIND_INTERNAL",status_code="STATUS_CODE_UNSET"} 3` signals that 3 repositories have been renovated.
`calls_total{operation="run",service_name="renovate",span_kind="SPAN_KIND_INTERNAL",status_code="STATUS_CODE_UNSET"} 1` represents how often Renovate has been run.
If we combine this using the PrometheusQueryLanguage ( PromQL ), we can calculate the average count of repositories each Renovate run handles.
```
calls_total{operation="renovate repository",service_name="renovate"} / calls_total{operation="run",service_name="renovate"}
```
This metrics is also for spans generated by http calls:
```yaml
calls_total{http_host="registry.terraform.io:443",http_method="GET",http_status_code="200",operation="HTTPS GET",service_name="renovate",span_kind="SPAN_KIND_CLIENT",status_code="STATUS_CODE_UNSET"} 5
```
#### Latency buckets
The second class of metrics exposed are the latency focused latency buckets which allow to create [heatmaps](https://grafana.com/docs/grafana/latest/basics/intro-histograms/#heatmaps).
A request is added to a backed if the latency is bigger than the bucket value (`le`). `request_duration => le`
As an example if we receive a request which need `1.533s` to complete get following metrics:
```
latency_bucket{http_host="api.github.com:443",le="0.1"} 0
latency_bucket{http_host="api.github.com:443",le="1"} 0
latency_bucket{http_host="api.github.com:443",le="2"} 1
latency_bucket{http_host="api.github.com:443",le="6"} 1
latency_bucket{http_host="api.github.com:443",le="10"} 1
latency_bucket{http_host="api.github.com:443",le="100"} 1
latency_bucket{http_host="api.github.com:443",le="250"} 1
latency_bucket{http_host="api.github.com:443",le="9.223372036854775e+12"} 1
latency_bucket{http_host="api.github.com:443",le="+Inf"} 1
latency_sum{http_host="api.github.com:443"} 1.533
latency_count{http_host="api.github.com:443"} 1
```
Now we have another request which this time takes 10s to complete:
```
latency_bucket{http_host="api.github.com:443",le="0.1"} 0
latency_bucket{http_host="api.github.com:443",le="1"} 0
latency_bucket{http_host="api.github.com:443",le="2"} 1
latency_bucket{http_host="api.github.com:443",le="6"} 1
latency_bucket{http_host="api.github.com:443",le="10"} 2
latency_bucket{http_host="api.github.com:443",le="100"} 2
latency_bucket{http_host="api.github.com:443",le="250"} 2
latency_bucket{http_host="api.github.com:443",le="9.223372036854775e+12"} 2
latency_bucket{http_host="api.github.com:443",le="+Inf"} 2
latency_sum{http_host="api.github.com:443"} 11.533
latency_count{http_host="api.github.com:443"} 2
```
More about the functionality can be found on the Prometheus page for [metric types](https://prometheus.io/docs/concepts/metric_types/#histogram).

View file

@ -0,0 +1,30 @@
---
title: OpenTelemetry
description: How to use OpenTelemetry with Renovate
---
# OpenTelemetry and Renovate
**THIS FEATURE IS EXPERIMENTAL** and is subject to change in minor versions.
Renovate supports OpenTelemetry which is an emerging monitoring standard.
OpenTelemetry supports three types of observability data:
- traces
- metrics
- logs
Renovate can only send traces and only via the OpenTelemetryProtocol (OTLP), other observability data or transfer protocols are not supported.
## Usage
To activate the instrumentation, the environment variable `OTEL_EXPORTER_OTLP_ENDPOINT` has to be set.
This sets the endpoint where to send the telemetry data.
If this endpoint is set, all other environment variables defined by the [OpenTelemetry specification](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md) are supported.
For debugging purposes the telemetry can also be printed to the console if the environment variable `RENOVATE_TRACING_CONSOLE_EXPORTER` is set.
## Examples
An usage example with a local OpenTelemetry setup can be found in the [OpenTelemetry examples](examples/opentelemetry.md)

View file

@ -62,3 +62,8 @@ Source: [AWS s3 documentation - Interface BucketEndpointInputConfig](https://doc
## `RENOVATE_X_EXEC_GPID_HANDLE` ## `RENOVATE_X_EXEC_GPID_HANDLE`
If set, Renovate will terminate the whole process group of a terminated child process spawned by Renovate. If set, Renovate will terminate the whole process group of a terminated child process spawned by Renovate.
## `OTEL_EXPORTER_OTLP_ENDPOINT`
If set, Renovate will export OpenTelemetry data to the supplied endpoint.
For more information see [the OpenTelemetry docs](opentelemetry.md).

View file

@ -0,0 +1,5 @@
import { NoopTracer } from '@opentelemetry/api/build/src/trace/NoopTracer';
import { NoopTracerProvider } from '@opentelemetry/api/build/src/trace/NoopTracerProvider';
export const getTracerProvider = jest.fn(args => new NoopTracerProvider());
export const getTracer = jest.fn(args => new NoopTracer());

View file

@ -0,0 +1,124 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`instrumentation/index activate console logger 1`] = `
MultiSpanProcessor {
"_spanProcessors": [
SimpleSpanProcessor {
"_exporter": ConsoleSpanExporter {},
"_shutdownOnce": BindOnceFuture {
"_callback": [Function],
"_deferred": Deferred {
"_promise": Promise {},
"_reject": [Function],
"_resolve": [Function],
},
"_isCalled": false,
"_that": [Circular],
},
},
],
}
`;
exports[`instrumentation/index activate console logger and remote logger 1`] = `
MultiSpanProcessor {
"_spanProcessors": [
SimpleSpanProcessor {
"_exporter": ConsoleSpanExporter {},
"_shutdownOnce": BindOnceFuture {
"_callback": [Function],
"_deferred": Deferred {
"_promise": Promise {},
"_reject": [Function],
"_resolve": [Function],
},
"_isCalled": false,
"_that": [Circular],
},
},
BatchSpanProcessor {
"_exportTimeoutMillis": 30000,
"_exporter": OTLPTraceExporter {
"DEFAULT_HEADERS": {},
"_concurrencyLimit": Infinity,
"_sendingPromises": [],
"_shutdownOnce": BindOnceFuture {
"_callback": [Function],
"_deferred": Deferred {
"_promise": Promise {},
"_reject": [Function],
"_resolve": [Function],
},
"_isCalled": false,
"_that": [Circular],
},
"agent": undefined,
"compression": "none",
"headers": {},
"shutdown": [Function],
"timeoutMillis": 10000,
"url": "https://collector.example.com/v1/traces",
},
"_finishedSpans": [],
"_maxExportBatchSize": 512,
"_maxQueueSize": 2048,
"_scheduledDelayMillis": 5000,
"_shutdownOnce": BindOnceFuture {
"_callback": [Function],
"_deferred": Deferred {
"_promise": Promise {},
"_reject": [Function],
"_resolve": [Function],
},
"_isCalled": false,
"_that": [Circular],
},
},
],
}
`;
exports[`instrumentation/index activate remote logger 1`] = `
MultiSpanProcessor {
"_spanProcessors": [
BatchSpanProcessor {
"_exportTimeoutMillis": 30000,
"_exporter": OTLPTraceExporter {
"DEFAULT_HEADERS": {},
"_concurrencyLimit": Infinity,
"_sendingPromises": [],
"_shutdownOnce": BindOnceFuture {
"_callback": [Function],
"_deferred": Deferred {
"_promise": Promise {},
"_reject": [Function],
"_resolve": [Function],
},
"_isCalled": false,
"_that": [Circular],
},
"agent": undefined,
"compression": "none",
"headers": {},
"shutdown": [Function],
"timeoutMillis": 10000,
"url": "https://collector.example.com/v1/traces",
},
"_finishedSpans": [],
"_maxExportBatchSize": 512,
"_maxQueueSize": 2048,
"_scheduledDelayMillis": 5000,
"_shutdownOnce": BindOnceFuture {
"_callback": [Function],
"_deferred": Deferred {
"_promise": Promise {},
"_reject": [Function],
"_resolve": [Function],
},
"_isCalled": false,
"_that": [Circular],
},
},
],
}
`;

View file

@ -0,0 +1,49 @@
import { afterAll } from '@jest/globals';
import { instrument } from './decorator';
import { disableInstrumentations } from '.';
afterAll(disableInstrumentations);
describe('instrumentation/decorator', () => {
const spy = jest.fn(() => Promise.resolve());
beforeEach(() => {
jest.clearAllMocks();
});
it('should instrument async function', async () => {
class MyClass {
@instrument({ name: 'getNumber' })
public async getNumber(): Promise<number> {
await spy();
return Math.random();
}
}
const myClass = new MyClass();
const result = await myClass.getNumber();
expect(result).toBeDefined();
expect(result).toBeNumber();
expect(spy).toHaveBeenCalledTimes(1);
});
it('should instrument multiple async function calls', async () => {
class MyClass {
@instrument({ name: 'getNumber' })
public async getNumber(): Promise<number> {
await spy();
return Math.random();
}
}
const myClass = new MyClass();
await myClass.getNumber();
await myClass.getNumber();
const result = await myClass.getNumber();
expect(result).toBeDefined();
expect(result).toBeNumber();
expect(spy).toHaveBeenCalledTimes(3);
});
});

View file

@ -0,0 +1,21 @@
import { Decorator, decorate } from '../util/decorator';
import type { SpanParameters } from './types';
import { instrument as instrumentFunc } from '.';
/**
* instruments a decorated method.
*/
export function instrument<T>({
name,
attributes,
ignoreParentSpan,
kind,
}: SpanParameters): Decorator<T> {
return decorate(async ({ callback }) => {
return await instrumentFunc(name, callback, {
attributes,
root: ignoreParentSpan,
kind,
});
});
}

View file

@ -0,0 +1,121 @@
import { afterAll } from '@jest/globals';
import { ProxyTracerProvider } from '@opentelemetry/api';
import * as api from '@opentelemetry/api';
import { NoopTracerProvider } from '@opentelemetry/api/build/src/trace/NoopTracerProvider';
import { MultiSpanProcessor } from '@opentelemetry/sdk-trace-base/build/src/MultiSpanProcessor';
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
import {
disableInstrumentations,
getTracerProvider,
init,
instrument,
} from '.';
afterAll(disableInstrumentations);
describe('instrumentation/index', () => {
const oldEnv = process.env;
beforeEach(() => {
jest.clearAllMocks();
api.trace.disable(); // clear global components
process.env = { ...oldEnv };
});
afterAll(() => {
process.env = oldEnv; // Restore old environment
});
it('should use NoopTraceProvider if not activated', () => {
init();
const traceProvider = getTracerProvider();
expect(traceProvider).toBeInstanceOf(ProxyTracerProvider);
const provider = traceProvider as ProxyTracerProvider;
expect(provider.getDelegate()).toBeInstanceOf(NoopTracerProvider);
});
it('activate console logger', () => {
process.env.RENOVATE_TRACING_CONSOLE_EXPORTER = 'true';
init();
const traceProvider = getTracerProvider();
expect(traceProvider).toBeInstanceOf(ProxyTracerProvider);
const proxyProvider = traceProvider as ProxyTracerProvider;
const delegateProvider = proxyProvider.getDelegate();
expect(delegateProvider).toBeInstanceOf(NodeTracerProvider);
const nodeProvider = delegateProvider as NodeTracerProvider;
const provider = nodeProvider.getActiveSpanProcessor();
expect(provider).toBeInstanceOf(MultiSpanProcessor);
expect(provider).toMatchSnapshot();
});
it('activate remote logger', () => {
process.env.OTEL_EXPORTER_OTLP_ENDPOINT = 'https://collector.example.com';
init();
const traceProvider = getTracerProvider();
expect(traceProvider).toBeInstanceOf(ProxyTracerProvider);
const proxyProvider = traceProvider as ProxyTracerProvider;
const delegateProvider = proxyProvider.getDelegate();
expect(delegateProvider).toBeInstanceOf(NodeTracerProvider);
const nodeProvider = delegateProvider as NodeTracerProvider;
const provider = nodeProvider.getActiveSpanProcessor();
expect(provider).toBeInstanceOf(MultiSpanProcessor);
expect(provider).toMatchSnapshot();
});
it('activate console logger and remote logger', () => {
process.env.RENOVATE_TRACING_CONSOLE_EXPORTER = 'true';
process.env.OTEL_EXPORTER_OTLP_ENDPOINT = 'https://collector.example.com';
init();
const traceProvider = getTracerProvider();
expect(traceProvider).toBeInstanceOf(ProxyTracerProvider);
const proxyProvider = traceProvider as ProxyTracerProvider;
const delegateProvider = proxyProvider.getDelegate();
expect(delegateProvider).toBeInstanceOf(NodeTracerProvider);
const nodeProvider = delegateProvider as NodeTracerProvider;
const provider = nodeProvider.getActiveSpanProcessor();
expect(provider).toBeInstanceOf(MultiSpanProcessor);
expect(provider).toMatchSnapshot();
});
describe('instrument', () => {
it('should return result', () => {
const value = 'testResult';
const result = instrument('test', () => {
return value;
});
expect(result).toStrictEqual(value);
});
it('should rethrow exception', () => {
const error = new Error('testError');
expect(() =>
instrument('test', () => {
throw error;
})
).toThrow(error);
});
it('should return result for async fn', async () => {
const value = 'testResult';
const result = await instrument('test', async () => {
return await new Promise((resolve) => {
resolve(value);
});
});
expect(result).toStrictEqual(value);
});
it('should rethrow exception for async fn', async () => {
const error = new Error('testError');
await expect(
instrument('test', async () => {
await Promise.resolve();
throw error;
})
).rejects.toThrow(error);
});
});
});

View file

@ -0,0 +1,165 @@
import { ClientRequest } from 'http';
import type {
Context,
Span,
SpanOptions,
Tracer,
TracerProvider,
} from '@opentelemetry/api';
import * as api from '@opentelemetry/api';
import { ProxyTracerProvider, SpanStatusCode } from '@opentelemetry/api';
import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import {
Instrumentation,
registerInstrumentations,
} from '@opentelemetry/instrumentation';
import { BunyanInstrumentation } from '@opentelemetry/instrumentation-bunyan';
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
import { Resource } from '@opentelemetry/resources';
import {
BatchSpanProcessor,
ConsoleSpanExporter,
SimpleSpanProcessor,
} from '@opentelemetry/sdk-trace-base';
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
import { pkg } from '../expose.cjs';
import {
isTraceDebuggingEnabled,
isTraceSendingEnabled,
isTracingEnabled,
} from './utils';
let instrumentations: Instrumentation[] = [];
init();
export function init(): void {
if (!isTracingEnabled()) {
return;
}
const traceProvider = new NodeTracerProvider({
resource: new Resource({
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/semantic_conventions/README.md#semantic-attributes-with-sdk-provided-default-value
[SemanticResourceAttributes.SERVICE_NAME]: 'renovate',
[SemanticResourceAttributes.SERVICE_NAMESPACE]: 'renovatebot.com',
[SemanticResourceAttributes.SERVICE_VERSION]: pkg.version,
}),
});
// add processors
if (isTraceDebuggingEnabled()) {
traceProvider.addSpanProcessor(
new SimpleSpanProcessor(new ConsoleSpanExporter())
);
}
// OTEL specification environment variable
if (isTraceSendingEnabled()) {
const exporter = new OTLPTraceExporter();
traceProvider.addSpanProcessor(new BatchSpanProcessor(exporter));
}
const contextManager = new AsyncLocalStorageContextManager();
traceProvider.register({
contextManager,
});
instrumentations = [
new HttpInstrumentation({
applyCustomAttributesOnSpan: /* istanbul ignore next */ (
span,
request,
response
) => {
// ignore 404 errors when the branch protection of Github could not be found. This is expected if no rules are configured
if (
request instanceof ClientRequest &&
request.host === `api.github.com` &&
request.path.endsWith(`/protection`) &&
response.statusCode === 404
) {
span.setStatus({ code: SpanStatusCode.OK });
}
},
}),
new BunyanInstrumentation(),
];
registerInstrumentations({
instrumentations,
});
}
/* istanbul ignore next */
// https://github.com/open-telemetry/opentelemetry-js-api/issues/34
export async function shutdown(): Promise<void> {
const traceProvider = getTracerProvider();
if (traceProvider instanceof NodeTracerProvider) {
await traceProvider.shutdown();
} else if (traceProvider instanceof ProxyTracerProvider) {
const delegateProvider = traceProvider.getDelegate();
if (delegateProvider instanceof NodeTracerProvider) {
await delegateProvider.shutdown();
}
}
}
/* istanbul ignore next */
export function disableInstrumentations(): void {
for (const instrumentation of instrumentations) {
instrumentation.disable();
}
}
export function getTracerProvider(): TracerProvider {
return api.trace.getTracerProvider();
}
function getTracer(): Tracer {
return getTracerProvider().getTracer('renovate');
}
export function instrument<F extends (span: Span) => ReturnType<F>>(
name: string,
fn: F
): ReturnType<F>;
export function instrument<F extends (span: Span) => ReturnType<F>>(
name: string,
fn: F,
options: SpanOptions
): ReturnType<F>;
export function instrument<F extends (span: Span) => ReturnType<F>>(
name: string,
fn: F,
options: SpanOptions = {},
context: Context = api.context.active()
): ReturnType<F> {
return getTracer().startActiveSpan(name, options, context, (span: Span) => {
try {
const ret = fn(span);
if (ret instanceof Promise) {
return ret
.catch((e) => {
span.setStatus({
code: SpanStatusCode.ERROR,
message: e,
});
throw e;
})
.finally(() => span.end()) as ReturnType<F>;
}
span.end();
return ret;
} catch (e) {
span.setStatus({
code: SpanStatusCode.ERROR,
message: e,
});
span.end();
throw e;
}
});
}

View file

@ -0,0 +1,26 @@
import type { Attributes, SpanKind } from '@opentelemetry/api';
/**
* The instrumentation decorator parameters.
*/
export interface SpanParameters {
/**
* The name of the span
*/
name: string;
/**
* Attributes which should be added to the span
*/
attributes?: Attributes;
/**
* Should this span be added to the root span or to the current active span
*/
ignoreParentSpan?: boolean;
/**
* Type of span this represents. Default: SpanKind.Internal
*/
kind?: SpanKind;
}

View file

@ -0,0 +1,11 @@
export function isTracingEnabled(): boolean {
return isTraceDebuggingEnabled() || isTraceSendingEnabled();
}
export function isTraceDebuggingEnabled(): boolean {
return !!process.env.RENOVATE_TRACING_CONSOLE_EXPORTER;
}
export function isTraceSendingEnabled(): boolean {
return !!process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
}

View file

@ -1,5 +1,6 @@
#!/usr/bin/env node #!/usr/bin/env node
import { instrument, shutdown as telemetryShutdown } from './instrumentation'; // has to be imported before logger and other libraries which are instrumentalised
import { logger } from './logger'; import { logger } from './logger';
import * as proxy from './proxy'; import * as proxy from './proxy';
import * as globalWorker from './workers/global'; import * as globalWorker from './workers/global';
@ -13,7 +14,9 @@ proxy.bootstrap();
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises
(async (): Promise<void> => { (async (): Promise<void> => {
process.exitCode = await globalWorker.start(); process.exitCode = await instrument('run', () => globalWorker.start());
await telemetryShutdown(); //gracefully shutdown OpenTelemetry
// istanbul ignore if // istanbul ignore if
if (process.env.RENOVATE_X_HARD_EXIT) { if (process.env.RENOVATE_X_HARD_EXIT) {
process.exit(process.exitCode); process.exit(process.exitCode);

View file

@ -15,6 +15,7 @@ import type {
} from '../../config/types'; } from '../../config/types';
import { CONFIG_PRESETS_INVALID } from '../../constants/error-messages'; import { CONFIG_PRESETS_INVALID } from '../../constants/error-messages';
import { pkg } from '../../expose.cjs'; import { pkg } from '../../expose.cjs';
import { instrument } from '../../instrumentation';
import { getProblems, logger, setMeta } from '../../logger'; import { getProblems, logger, setMeta } from '../../logger';
import * as hostRules from '../../util/host-rules'; import * as hostRules from '../../util/host-rules';
import * as queue from '../../util/http/queue'; import * as queue from '../../util/http/queue';
@ -107,6 +108,7 @@ export async function resolveGlobalExtends(
export async function start(): Promise<number> { export async function start(): Promise<number> {
let config: AllConfig; let config: AllConfig;
try { try {
await instrument('config', async () => {
// read global config from file, env and cli args // read global config from file, env and cli args
config = await getGlobalConfig(); config = await getGlobalConfig();
if (config?.globalExtends) { if (config?.globalExtends) {
@ -120,7 +122,10 @@ export async function start(): Promise<number> {
config = await globalInitialize(config); config = await globalInitialize(config);
// Set platform and endpoint in case local presets are used // Set platform and endpoint in case local presets are used
GlobalConfig.set({ platform: config.platform, endpoint: config.endpoint }); GlobalConfig.set({
platform: config.platform,
endpoint: config.endpoint,
});
await validatePresets(config); await validatePresets(config);
@ -128,9 +133,12 @@ export async function start(): Promise<number> {
// validate secrets. Will throw and abort if invalid // validate secrets. Will throw and abort if invalid
validateConfigSecrets(config); validateConfigSecrets(config);
});
// autodiscover repositories (needs to come after platform initialization) // autodiscover repositories (needs to come after platform initialization)
config = await autodiscoverRepositories(config); config = await instrument('discover', () =>
autodiscoverRepositories(config)
);
if (is.nonEmptyString(config.writeDiscoveredRepos)) { if (is.nonEmptyString(config.writeDiscoveredRepos)) {
const content = JSON.stringify(config.repositories); const content = JSON.stringify(config.repositories);
@ -146,6 +154,9 @@ export async function start(): Promise<number> {
if (haveReachedLimits()) { if (haveReachedLimits()) {
break; break;
} }
await instrument(
'repository',
async () => {
const repoConfig = await getRepositoryConfig(config, repository); const repoConfig = await getRepositoryConfig(config, repository);
if (repoConfig.hostRules) { if (repoConfig.hostRules) {
logger.debug('Reinitializing hostRules for repo'); logger.debug('Reinitializing hostRules for repo');
@ -159,6 +170,16 @@ export async function start(): Promise<number> {
await repositoryWorker.renovateRepository(repoConfig); await repositoryWorker.renovateRepository(repoConfig);
setMeta({}); setMeta({});
},
{
attributes: {
repository:
typeof repository === 'string'
? repository
: repository.repository,
},
}
);
} }
} catch (err) /* istanbul ignore next */ { } catch (err) /* istanbul ignore next */ {
if (err.message.startsWith('Init: ')) { if (err.message.startsWith('Init: ')) {

View file

@ -3,6 +3,7 @@ import { GlobalConfig } from '../../config/global';
import { applySecretsToConfig } from '../../config/secrets'; import { applySecretsToConfig } from '../../config/secrets';
import type { RenovateConfig } from '../../config/types'; import type { RenovateConfig } from '../../config/types';
import { pkg } from '../../expose.cjs'; import { pkg } from '../../expose.cjs';
import { instrument } from '../../instrumentation';
import { logger, setMeta } from '../../logger'; import { logger, setMeta } from '../../logger';
import { removeDanglingContainers } from '../../util/exec/docker'; import { removeDanglingContainers } from '../../util/exec/docker';
import { deleteLocalFile, privateCacheDir } from '../../util/fs'; import { deleteLocalFile, privateCacheDir } from '../../util/fs';
@ -42,16 +43,22 @@ export async function renovateRepository(
logger.debug('Using localDir: ' + localDir); logger.debug('Using localDir: ' + localDir);
config = await initRepo(config); config = await initRepo(config);
addSplit('init'); addSplit('init');
const { branches, branchList, packageFiles } = await extractDependencies( const { branches, branchList, packageFiles } = await instrument(
config 'extract',
() => extractDependencies(config)
); );
if ( if (
GlobalConfig.get('dryRun') !== 'lookup' && GlobalConfig.get('dryRun') !== 'lookup' &&
GlobalConfig.get('dryRun') !== 'extract' GlobalConfig.get('dryRun') !== 'extract'
) { ) {
await ensureOnboardingPr(config, packageFiles, branches); await instrument('onboarding', () =>
ensureOnboardingPr(config, packageFiles, branches)
);
addSplit('onboarding'); addSplit('onboarding');
const res = await updateRepo(config, branches);
const res = await instrument('update', () =>
updateRepo(config, branches)
);
setMeta({ repository: config.repository }); setMeta({ repository: config.repository });
addSplit('update'); addSplit('update');
await setBranchCache(branches); await setBranchCache(branches);

View file

@ -145,6 +145,16 @@
"@cheap-glitch/mi-cron": "1.0.1", "@cheap-glitch/mi-cron": "1.0.1",
"@iarna/toml": "2.2.5", "@iarna/toml": "2.2.5",
"@renovatebot/osv-offline": "1.0.5", "@renovatebot/osv-offline": "1.0.5",
"@opentelemetry/api": "1.2.0",
"@opentelemetry/context-async-hooks": "1.6.0",
"@opentelemetry/exporter-trace-otlp-http": "0.32.0",
"@opentelemetry/instrumentation": "0.32.0",
"@opentelemetry/instrumentation-bunyan": "0.29.0",
"@opentelemetry/instrumentation-http": "0.32.0",
"@opentelemetry/resources": "1.6.0",
"@opentelemetry/sdk-trace-base": "1.6.0",
"@opentelemetry/sdk-trace-node": "1.6.0",
"@opentelemetry/semantic-conventions": "1.6.0",
"@renovatebot/pep440": "2.1.5", "@renovatebot/pep440": "2.1.5",
"@renovatebot/ruby-semver": "1.1.6", "@renovatebot/ruby-semver": "1.1.6",
"@sindresorhus/is": "4.6.0", "@sindresorhus/is": "4.6.0",

192
yarn.lock
View file

@ -2313,10 +2313,172 @@
resolved "https://registry.yarnpkg.com/@openpgp/web-stream-tools/-/web-stream-tools-0.0.12.tgz#8a80170c7590ecee2af4220c5cb1efe1a02946eb" resolved "https://registry.yarnpkg.com/@openpgp/web-stream-tools/-/web-stream-tools-0.0.12.tgz#8a80170c7590ecee2af4220c5cb1efe1a02946eb"
integrity sha512-OGQ7a7UlALBOPxTWqLjPoa6YjHtLYF5ETb3zwx2A2Qq3YsstJX4q/OvYx60v2MavmBBJELsBQNugdJu0uMBhSw== integrity sha512-OGQ7a7UlALBOPxTWqLjPoa6YjHtLYF5ETb3zwx2A2Qq3YsstJX4q/OvYx60v2MavmBBJELsBQNugdJu0uMBhSw==
"@opentelemetry/api-metrics@0.29.2":
version "0.29.2"
resolved "https://registry.yarnpkg.com/@opentelemetry/api-metrics/-/api-metrics-0.29.2.tgz#daa823e0965754222b49a6ae6133df8b39ff8fd2"
integrity sha512-yRdF5beqKuEdsPNoO7ijWCQ9HcyN0Tlgicf8RS6gzGOI54d6Hj7yKquJ6+X9XV+CSRbRWJYb+lOsXyso7uyX2g==
dependencies:
"@opentelemetry/api" "^1.0.0"
"@opentelemetry/api-metrics@0.32.0":
version "0.32.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/api-metrics/-/api-metrics-0.32.0.tgz#0f09f78491a4b301ddf54a8b8a38ffa99981f645"
integrity sha512-g1WLhpG8B6iuDyZJFRGsR+JKyZ94m5LEmY2f+duEJ9Xb4XRlLHrZvh6G34OH6GJ8iDHxfHb/sWjJ1ZpkI9yGMQ==
dependencies:
"@opentelemetry/api" "^1.0.0"
"@opentelemetry/api@1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.2.0.tgz#89ef99401cde6208cff98760b67663726ef26686"
integrity sha512-0nBr+VZNKm9tvNDZFstI3Pq1fCTEDK5OZTnVKNvBNAKgd0yIvmwsP4m61rEv7ZP+tOUjWJhROpxK5MsnlF911g==
"@opentelemetry/api@^1.0.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.1.0.tgz#563539048255bbe1a5f4f586a4a10a1bb737f44a"
integrity sha512-hf+3bwuBwtXsugA2ULBc95qxrOqP2pOekLz34BJhcAKawt94vfeNyUKpYc0lZQ/3sCP6LqRa7UAdHA7i5UODzQ==
"@opentelemetry/context-async-hooks@1.6.0":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/context-async-hooks/-/context-async-hooks-1.6.0.tgz#e3839bf8c010d7e660d9762fe2407171d352b88e"
integrity sha512-7xpyfHfuHnuCm5eAk4j4MIZjRM/hsiLlKEFIwI8SXNlbxqmb/JRrntOjN/AT+KeihkMw+xAx+0lsYPUANCSaQw==
"@opentelemetry/core@1.6.0":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.6.0.tgz#c55f8ab7496acef7dbd8c4eedef6a4d4a0143c95"
integrity sha512-MsEhsyCTfYme6frK8/AqEWwbS9SB3Ta5bjgz4jPQJjL7ijUM3JiLVvqh/kHo1UlUjbUbLmGG7jA5Nw4d7SMcLQ==
dependencies:
"@opentelemetry/semantic-conventions" "1.6.0"
"@opentelemetry/exporter-trace-otlp-http@0.32.0":
version "0.32.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.32.0.tgz#55773290a221855c4e8c422e8fb5e7ff4aa5f04e"
integrity sha512-8n44NDoEFoYG3mMToZxNyUKkHSGfzSShw6I2V5FApcH7rid20LmKiNuzc7lACneDIZBld+GGpLRuFhWniW8JhA==
dependencies:
"@opentelemetry/core" "1.6.0"
"@opentelemetry/otlp-exporter-base" "0.32.0"
"@opentelemetry/otlp-transformer" "0.32.0"
"@opentelemetry/resources" "1.6.0"
"@opentelemetry/sdk-trace-base" "1.6.0"
"@opentelemetry/instrumentation-bunyan@0.29.0":
version "0.29.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-bunyan/-/instrumentation-bunyan-0.29.0.tgz#04132ef917e39200d76331e32b36a968bafbfbd3"
integrity sha512-i1FZ+W96vQCIpkMKPZW0HOA79ve9PLIcTAFH0adU/CvtRRMSxyKPTKzWMGHcWr6DueKIPEorpMG+nO2Z/yk9iQ==
dependencies:
"@opentelemetry/instrumentation" "^0.29.2"
"@types/bunyan" "1.8.7"
"@opentelemetry/instrumentation-http@0.32.0":
version "0.32.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-http/-/instrumentation-http-0.32.0.tgz#63ea9e3a3d114a7e3f922e3a39b57afa874e6478"
integrity sha512-EbNdJl6IjouphbxPVGV8/utiqB2DhveyH5TD6vxjc2OXlQ3A/mKg3fYSSWB+rYQBuuli+jWQfBJe2ntOFZtTMw==
dependencies:
"@opentelemetry/core" "1.6.0"
"@opentelemetry/instrumentation" "0.32.0"
"@opentelemetry/semantic-conventions" "1.6.0"
semver "^7.3.5"
"@opentelemetry/instrumentation@0.32.0":
version "0.32.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.32.0.tgz#27c5975a323a2ba83d9bf2ea8b11faaab37c5827"
integrity sha512-y6ADjHpkUz/v1nkyyYjsQa/zorhX+0qVGpFvXMcbjU4sHnBnC02c6wcc93sIgZfiQClIWo45TGku1KQxJ5UUbQ==
dependencies:
"@opentelemetry/api-metrics" "0.32.0"
require-in-the-middle "^5.0.3"
semver "^7.3.2"
shimmer "^1.2.1"
"@opentelemetry/instrumentation@^0.29.2":
version "0.29.2"
resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.29.2.tgz#70e6d4e1a84508f5e9d8c7c426adcd7b0dba6c95"
integrity sha512-LXx5V0ONNATQFCE8C5uqnxWSm4rcXLssdLHdXjtGdxRmURqj/JO8jYefqXCD0LzsqEQ6yxOx2GZ0dgXvhBVdTw==
dependencies:
"@opentelemetry/api-metrics" "0.29.2"
require-in-the-middle "^5.0.3"
semver "^7.3.2"
shimmer "^1.2.1"
"@opentelemetry/otlp-exporter-base@0.32.0":
version "0.32.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.32.0.tgz#37dde162835a8fd23fa040f07e2938deb335fc4b"
integrity sha512-Dscxu4VNKrkD1SwGKdc7bAtLViGFJC8ah6Dr/vZn22NFHXSa53lSzDdTKeSTNNWH9sCGu/65LS45VMd4PsRvwQ==
dependencies:
"@opentelemetry/core" "1.6.0"
"@opentelemetry/otlp-transformer@0.32.0":
version "0.32.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/otlp-transformer/-/otlp-transformer-0.32.0.tgz#652c8f4c56c95f7d7ec39e20573b885d27ca13f1"
integrity sha512-PFAqfKgJpTOZryPe1UMm7R578PLxsK0wCAuKSt6m8v1bN/4DO8DX4HD7k3mYGZVU5jNg8tVZSwyIpY6ryrHDMQ==
dependencies:
"@opentelemetry/api-metrics" "0.32.0"
"@opentelemetry/core" "1.6.0"
"@opentelemetry/resources" "1.6.0"
"@opentelemetry/sdk-metrics" "0.32.0"
"@opentelemetry/sdk-trace-base" "1.6.0"
"@opentelemetry/propagator-b3@1.6.0":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/propagator-b3/-/propagator-b3-1.6.0.tgz#db0dee4f28cb4f1830f3cd35013b652b8078f355"
integrity sha512-azs3aCIFrr3qkA/6lNIAYJ+wgDQ6cFoyeHVcZXP0E96AiOeVqtAu5ZXSA63Cw/63pSw0Itmx6CHUGu41enc0TQ==
dependencies:
"@opentelemetry/core" "1.6.0"
"@opentelemetry/propagator-jaeger@1.6.0":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/propagator-jaeger/-/propagator-jaeger-1.6.0.tgz#e3e910d71967efb7923674ac407b14c117ea7f31"
integrity sha512-QgvWVgRS+APP7aHGPHgKo7HXJg2BbwW394kDNW1HeIxrywliUdAk8h5SJ/VGehy/dTzCFwbDd5Y3TMQRUNCHDg==
dependencies:
"@opentelemetry/core" "1.6.0"
"@opentelemetry/resources@1.6.0":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.6.0.tgz#9756894131b9b0dfbcc0cecb5d4bd040d9c1b09d"
integrity sha512-07GlHuq72r2rnJugYVdGumviQvfrl8kEPidkZSVoseLVfIjV7nzxxt5/vqs9pK7JItWOrvjRdr/jTBVayFBr/w==
dependencies:
"@opentelemetry/core" "1.6.0"
"@opentelemetry/semantic-conventions" "1.6.0"
"@opentelemetry/sdk-metrics@0.32.0":
version "0.32.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-metrics/-/sdk-metrics-0.32.0.tgz#463cd3a2b267f044db9aaab85887a171710345a0"
integrity sha512-zC9RCOIsXRqOHWmWfcxArtDHbip2/jaIH1yu/OKau/shDZYFluAxY6zAEYIb4YEAzKKEF+fpaoRgpodDWNGVGA==
dependencies:
"@opentelemetry/api-metrics" "0.32.0"
"@opentelemetry/core" "1.6.0"
"@opentelemetry/resources" "1.6.0"
lodash.merge "4.6.2"
"@opentelemetry/sdk-trace-base@1.6.0":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.6.0.tgz#8b1511c0b0f3e6015e345f5ed4a683adf03e3e3c"
integrity sha512-yx/uuzHdT0QNRSEbCgXHc0GONk90uvaFcPGaNowIFSl85rTp4or4uIIMkG7R8ckj8xWjDSjsaztH6yQxoZrl5g==
dependencies:
"@opentelemetry/core" "1.6.0"
"@opentelemetry/resources" "1.6.0"
"@opentelemetry/semantic-conventions" "1.6.0"
"@opentelemetry/sdk-trace-node@1.6.0":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-node/-/sdk-trace-node-1.6.0.tgz#fb15e030b19931f1cd6ad755add2bfd77fb915b6"
integrity sha512-rE6hL68QuSS2vDXZBhNiAkeN7kzDGrrJdzGeeyxQahmugnId5jmu1OYERIeULiKHQVkBjvycfmwPYsCL+3PsHQ==
dependencies:
"@opentelemetry/context-async-hooks" "1.6.0"
"@opentelemetry/core" "1.6.0"
"@opentelemetry/propagator-b3" "1.6.0"
"@opentelemetry/propagator-jaeger" "1.6.0"
"@opentelemetry/sdk-trace-base" "1.6.0"
semver "^7.3.5"
"@opentelemetry/semantic-conventions@1.6.0":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.6.0.tgz#ed410c9eb0070491cff9fe914246ce41f88d6f74"
integrity sha512-aPfcBeLErM/PPiAuAbNFLN5sNbZLc3KZlar27uohllN8Zs6jJbHyJU1y7cMA6W/zuq+thkaG8mujiS+3iD/FWQ==
"@pkgr/utils@^2.3.1": "@pkgr/utils@^2.3.1":
version "2.3.1" version "2.3.1"
resolved "https://registry.yarnpkg.com/@pkgr/utils/-/utils-2.3.1.tgz#0a9b06ffddee364d6642b3cd562ca76f55b34a03" resolved "https://registry.yarnpkg.com/@pkgr/utils/-/utils-2.3.1.tgz#0a9b06ffddee364d6642b3cd562ca76f55b34a03"
integrity sha512-wfzX8kc1PMyUILA+1Z/EqoE4UCXGy0iRGMhPwdfae1+f0OXlLqCk+By+aMzgJBzR9AzS4CDizioG6Ss1gvAFJw== integrity sha512-wfzX8kc1PMyUILA+1Z/EqoE4UCXGy0iRGMhPwdfae1+f0OXlLqCk+By+aMzgJBzR9AzS4CDizioG6Ss1gvAFJw==
dependencies: dependencies:
cross-spawn "^7.0.3" cross-spawn "^7.0.3"
is-glob "^4.0.3" is-glob "^4.0.3"
@ -2733,6 +2895,13 @@
dependencies: dependencies:
"@babel/types" "^7.3.0" "@babel/types" "^7.3.0"
"@types/bunyan@1.8.7":
version "1.8.7"
resolved "https://registry.yarnpkg.com/@types/bunyan/-/bunyan-1.8.7.tgz#63cc65b5ecff6217d1509409a575e7b991f80831"
integrity sha512-jaNt6xX5poSmXuDAkQrSqx2zkR66OrdRDuVnU8ldvn3k/Ci/7Sf5nooKspQWimDnw337Bzt/yirqSThTjvrHkg==
dependencies:
"@types/node" "*"
"@types/bunyan@1.8.8": "@types/bunyan@1.8.8":
version "1.8.8" version "1.8.8"
resolved "https://registry.yarnpkg.com/@types/bunyan/-/bunyan-1.8.8.tgz#8d6d33f090f37c07e2a80af30ae728450a101008" resolved "https://registry.yarnpkg.com/@types/bunyan/-/bunyan-1.8.8.tgz#8d6d33f090f37c07e2a80af30ae728450a101008"
@ -7208,7 +7377,7 @@ lodash.memoize@4.x:
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==
lodash.merge@^4.6.2: lodash.merge@4.6.2, lodash.merge@^4.6.2:
version "4.6.2" version "4.6.2"
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
@ -7649,6 +7818,11 @@ modify-values@^1.0.0:
resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022"
integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==
module-details-from-path@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/module-details-from-path/-/module-details-from-path-1.0.3.tgz#114c949673e2a8a35e9d35788527aa37b679da2b"
integrity sha1-EUyUlnPiqKNenTV4hSeqN7Z52is=
moment@^2.19.3: moment@^2.19.3:
version "2.29.4" version "2.29.4"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108"
@ -8897,6 +9071,15 @@ require-directory@^2.1.1:
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
require-in-the-middle@^5.0.3:
version "5.1.0"
resolved "https://registry.yarnpkg.com/require-in-the-middle/-/require-in-the-middle-5.1.0.tgz#b768f800377b47526d026bbf5a7f727f16eb412f"
integrity sha512-M2rLKVupQfJ5lf9OvqFGIT+9iVLnTmjgbOmpil12hiSQNn5zJTKGPoIisETNjfK+09vP3rpm1zJajmErpr2sEQ==
dependencies:
debug "^4.1.1"
module-details-from-path "^1.0.3"
resolve "^1.12.0"
resolve-alpn@^1.0.0: resolve-alpn@^1.0.0:
version "1.2.1" version "1.2.1"
resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9"
@ -8924,7 +9107,7 @@ resolve.exports@^1.1.0:
resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9"
integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==
resolve@^1.1.6, resolve@^1.10.0, resolve@^1.20.0, resolve@^1.22.0: resolve@^1.1.6, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.20.0, resolve@^1.22.0:
version "1.22.1" version "1.22.1"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
@ -9157,6 +9340,11 @@ shelljs@0.8.5:
interpret "^1.0.0" interpret "^1.0.0"
rechoir "^0.6.2" rechoir "^0.6.2"
shimmer@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337"
integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==
shlex@2.1.2: shlex@2.1.2:
version "2.1.2" version "2.1.2"
resolved "https://registry.yarnpkg.com/shlex/-/shlex-2.1.2.tgz#5b5384d603885281c1dee05d56975865edddcba0" resolved "https://registry.yarnpkg.com/shlex/-/shlex-2.1.2.tgz#5b5384d603885281c1dee05d56975865edddcba0"