diff --git a/docs/usage/assets/images/opentelemetry_choose_trace.png b/docs/usage/assets/images/opentelemetry_choose_trace.png new file mode 100644 index 0000000000000000000000000000000000000000..e1a28d7a55f716a566dc066dae693fdceec5906f Binary files /dev/null and b/docs/usage/assets/images/opentelemetry_choose_trace.png differ diff --git a/docs/usage/assets/images/opentelemetry_pick_service.png b/docs/usage/assets/images/opentelemetry_pick_service.png new file mode 100644 index 0000000000000000000000000000000000000000..8165983fbf66bd8528ea593117f7ca2d07072adc Binary files /dev/null and b/docs/usage/assets/images/opentelemetry_pick_service.png differ diff --git a/docs/usage/assets/images/opentelemetry_trace_viewer.png b/docs/usage/assets/images/opentelemetry_trace_viewer.png new file mode 100644 index 0000000000000000000000000000000000000000..098bed587bda45b5d48af3887633b710ca065149 Binary files /dev/null and b/docs/usage/assets/images/opentelemetry_trace_viewer.png differ diff --git a/docs/usage/examples/opentelemetry.md b/docs/usage/examples/opentelemetry.md new file mode 100644 index 0000000000000000000000000000000000000000..a86d94ab030bf0ca8b3eea23b86540e3cd447b8a --- /dev/null +++ b/docs/usage/examples/opentelemetry.md @@ -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. + + + +Press `Find Traces` to search for all Renovate traces and then click on one of the found traces to open the trace view. + + + +You should be able to see now the full trace view which shows each HTTP request and internal spans. + + + +### 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). diff --git a/docs/usage/opentelemetry.md b/docs/usage/opentelemetry.md new file mode 100644 index 0000000000000000000000000000000000000000..9a00e4255efbf4114c307d25f0d57fb6e626dc49 --- /dev/null +++ b/docs/usage/opentelemetry.md @@ -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) diff --git a/docs/usage/self-hosted-experimental.md b/docs/usage/self-hosted-experimental.md index f8941ad13ff72e258d02c44caef599203aed145a..d5f08ab2c22d43b33e4ef1ce236cd558b6e95300 100644 --- a/docs/usage/self-hosted-experimental.md +++ b/docs/usage/self-hosted-experimental.md @@ -62,3 +62,8 @@ Source: [AWS s3 documentation - Interface BucketEndpointInputConfig](https://doc ## `RENOVATE_X_EXEC_GPID_HANDLE` 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). diff --git a/lib/instrumentation/__mocks__/index.ts b/lib/instrumentation/__mocks__/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..be8ac341cd32d33d47ad8f459044ceb414638dc1 --- /dev/null +++ b/lib/instrumentation/__mocks__/index.ts @@ -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()); diff --git a/lib/instrumentation/__snapshots__/index.spec.ts.snap b/lib/instrumentation/__snapshots__/index.spec.ts.snap new file mode 100644 index 0000000000000000000000000000000000000000..385b530d449f37d6bf8f9ced1654acc58d47a6e1 --- /dev/null +++ b/lib/instrumentation/__snapshots__/index.spec.ts.snap @@ -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], + }, + }, + ], +} +`; diff --git a/lib/instrumentation/decorator.spec.ts b/lib/instrumentation/decorator.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..fdb33afd2a3149d91e3ee00e2350f861b84ed4d5 --- /dev/null +++ b/lib/instrumentation/decorator.spec.ts @@ -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); + }); +}); diff --git a/lib/instrumentation/decorator.ts b/lib/instrumentation/decorator.ts new file mode 100644 index 0000000000000000000000000000000000000000..39f1d365a84e39a51c7e3932b76a565664569470 --- /dev/null +++ b/lib/instrumentation/decorator.ts @@ -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, + }); + }); +} diff --git a/lib/instrumentation/index.spec.ts b/lib/instrumentation/index.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..b2cd369a4586f2bb4b08f1a1791a8128a4681acc --- /dev/null +++ b/lib/instrumentation/index.spec.ts @@ -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); + }); + }); +}); diff --git a/lib/instrumentation/index.ts b/lib/instrumentation/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..43a95d12187371b43060566f1e7d75007d921d91 --- /dev/null +++ b/lib/instrumentation/index.ts @@ -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; + } + }); +} diff --git a/lib/instrumentation/types.ts b/lib/instrumentation/types.ts new file mode 100644 index 0000000000000000000000000000000000000000..8b312d03d754d24ef3077e7dc751d74221a6720a --- /dev/null +++ b/lib/instrumentation/types.ts @@ -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; +} diff --git a/lib/instrumentation/utils.ts b/lib/instrumentation/utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..15066be3a31f423be457a77448cffcae81f1219a --- /dev/null +++ b/lib/instrumentation/utils.ts @@ -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; +} diff --git a/lib/renovate.ts b/lib/renovate.ts index 50ee830e6be7e868aa9bb6b62461918c049f164c..9b7c126ca100c71ae2901fe7550ab6af2830cd14 100644 --- a/lib/renovate.ts +++ b/lib/renovate.ts @@ -1,5 +1,6 @@ #!/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 * as proxy from './proxy'; import * as globalWorker from './workers/global'; @@ -13,7 +14,9 @@ proxy.bootstrap(); // eslint-disable-next-line @typescript-eslint/no-floating-promises (async (): Promise<void> => { - process.exitCode = await globalWorker.start(); + process.exitCode = await instrument('run', () => globalWorker.start()); + await telemetryShutdown(); //gracefully shutdown OpenTelemetry + // istanbul ignore if if (process.env.RENOVATE_X_HARD_EXIT) { process.exit(process.exitCode); diff --git a/lib/workers/global/index.ts b/lib/workers/global/index.ts index 75a0daf0728f8b8519f6be736151b0ef7283bc72..88919445eefc3083a2f64b6e4bda2becf38812cb 100644 --- a/lib/workers/global/index.ts +++ b/lib/workers/global/index.ts @@ -15,6 +15,7 @@ import type { } from '../../config/types'; import { CONFIG_PRESETS_INVALID } from '../../constants/error-messages'; import { pkg } from '../../expose.cjs'; +import { instrument } from '../../instrumentation'; import { getProblems, logger, setMeta } from '../../logger'; import * as hostRules from '../../util/host-rules'; import * as queue from '../../util/http/queue'; @@ -107,30 +108,37 @@ export async function resolveGlobalExtends( export async function start(): Promise<number> { let config: AllConfig; try { - // read global config from file, env and cli args - config = await getGlobalConfig(); - if (config?.globalExtends) { - // resolve global presets immediately - config = mergeChildConfig( - config, - await resolveGlobalExtends(config.globalExtends) - ); - } - // initialize all submodules - config = await globalInitialize(config); + await instrument('config', async () => { + // read global config from file, env and cli args + config = await getGlobalConfig(); + if (config?.globalExtends) { + // resolve global presets immediately + config = mergeChildConfig( + config, + await resolveGlobalExtends(config.globalExtends) + ); + } + // initialize all submodules + config = await globalInitialize(config); - // Set platform and endpoint in case local presets are used - GlobalConfig.set({ platform: config.platform, endpoint: config.endpoint }); + // Set platform and endpoint in case local presets are used + GlobalConfig.set({ + platform: config.platform, + endpoint: config.endpoint, + }); - await validatePresets(config); + await validatePresets(config); - checkEnv(); + checkEnv(); - // validate secrets. Will throw and abort if invalid - validateConfigSecrets(config); + // validate secrets. Will throw and abort if invalid + validateConfigSecrets(config); + }); // autodiscover repositories (needs to come after platform initialization) - config = await autodiscoverRepositories(config); + config = await instrument('discover', () => + autodiscoverRepositories(config) + ); if (is.nonEmptyString(config.writeDiscoveredRepos)) { const content = JSON.stringify(config.repositories); @@ -146,19 +154,32 @@ export async function start(): Promise<number> { if (haveReachedLimits()) { break; } - const repoConfig = await getRepositoryConfig(config, repository); - if (repoConfig.hostRules) { - logger.debug('Reinitializing hostRules for repo'); - hostRules.clear(); - repoConfig.hostRules.forEach((rule) => hostRules.add(rule)); - repoConfig.hostRules = []; - } - - // host rules can change concurrency - queue.clear(); - - await repositoryWorker.renovateRepository(repoConfig); - setMeta({}); + await instrument( + 'repository', + async () => { + const repoConfig = await getRepositoryConfig(config, repository); + if (repoConfig.hostRules) { + logger.debug('Reinitializing hostRules for repo'); + hostRules.clear(); + repoConfig.hostRules.forEach((rule) => hostRules.add(rule)); + repoConfig.hostRules = []; + } + + // host rules can change concurrency + queue.clear(); + + await repositoryWorker.renovateRepository(repoConfig); + setMeta({}); + }, + { + attributes: { + repository: + typeof repository === 'string' + ? repository + : repository.repository, + }, + } + ); } } catch (err) /* istanbul ignore next */ { if (err.message.startsWith('Init: ')) { diff --git a/lib/workers/repository/index.ts b/lib/workers/repository/index.ts index 03b70d28b750afc6d3a07f4ad318907d02f602e6..d59d4bab40f6af725e8aae19dc093be8f1daf170 100644 --- a/lib/workers/repository/index.ts +++ b/lib/workers/repository/index.ts @@ -3,6 +3,7 @@ import { GlobalConfig } from '../../config/global'; import { applySecretsToConfig } from '../../config/secrets'; import type { RenovateConfig } from '../../config/types'; import { pkg } from '../../expose.cjs'; +import { instrument } from '../../instrumentation'; import { logger, setMeta } from '../../logger'; import { removeDanglingContainers } from '../../util/exec/docker'; import { deleteLocalFile, privateCacheDir } from '../../util/fs'; @@ -42,16 +43,22 @@ export async function renovateRepository( logger.debug('Using localDir: ' + localDir); config = await initRepo(config); addSplit('init'); - const { branches, branchList, packageFiles } = await extractDependencies( - config + const { branches, branchList, packageFiles } = await instrument( + 'extract', + () => extractDependencies(config) ); if ( GlobalConfig.get('dryRun') !== 'lookup' && GlobalConfig.get('dryRun') !== 'extract' ) { - await ensureOnboardingPr(config, packageFiles, branches); + await instrument('onboarding', () => + ensureOnboardingPr(config, packageFiles, branches) + ); addSplit('onboarding'); - const res = await updateRepo(config, branches); + + const res = await instrument('update', () => + updateRepo(config, branches) + ); setMeta({ repository: config.repository }); addSplit('update'); await setBranchCache(branches); diff --git a/package.json b/package.json index 0b93ce3c4bbc5b0600b99a877a00276a26bddb4e..31241f778e3479875f9f914c9b413c13cc18d755 100644 --- a/package.json +++ b/package.json @@ -145,6 +145,16 @@ "@cheap-glitch/mi-cron": "1.0.1", "@iarna/toml": "2.2.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/ruby-semver": "1.1.6", "@sindresorhus/is": "4.6.0", diff --git a/yarn.lock b/yarn.lock index e3f08ff25974355faf89b48de04310f3421b5ba9..4c4d2d3b531d046d3b5f4a55445c8a76006cfe4a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2313,10 +2313,172 @@ resolved "https://registry.yarnpkg.com/@openpgp/web-stream-tools/-/web-stream-tools-0.0.12.tgz#8a80170c7590ecee2af4220c5cb1efe1a02946eb" 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": version "2.3.1" resolved "https://registry.yarnpkg.com/@pkgr/utils/-/utils-2.3.1.tgz#0a9b06ffddee364d6642b3cd562ca76f55b34a03" integrity sha512-wfzX8kc1PMyUILA+1Z/EqoE4UCXGy0iRGMhPwdfae1+f0OXlLqCk+By+aMzgJBzR9AzS4CDizioG6Ss1gvAFJw== + dependencies: cross-spawn "^7.0.3" is-glob "^4.0.3" @@ -2733,6 +2895,13 @@ dependencies: "@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": version "1.8.8" 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" 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" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" 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" 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: version "2.29.4" 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" 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: version "1.2.1" 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" 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" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== @@ -9157,6 +9340,11 @@ shelljs@0.8.5: interpret "^1.0.0" 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: version "2.1.2" resolved "https://registry.yarnpkg.com/shlex/-/shlex-2.1.2.tgz#5b5384d603885281c1dee05d56975865edddcba0"