diff --git a/CHANGELOG.md b/CHANGELOG.md index 52e776705eb..f2980539eb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ For experimental package changes, see the [experimental CHANGELOG](experimental/ ### :bug: (Bug Fix) +* fix(sdk-metrics): increase the depth of the output to the console such that objects in the metric are printed fully to the console [#4522](https://github.com/open-telemetry/opentelemetry-js/pull/4522) @JacksonWeber + ### :books: (Refine Doc) ### :house: (Internal) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5fb78e3ba3b..a52223a3873 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -104,7 +104,7 @@ An entry into `CHANGELOG.md` or `experimental/CHANGELOG.md` is required for the - Changes to default settings - New components being added -It is reasonable to omit an entry to the changelog under these circuimstances: +It is reasonable to omit an entry to the changelog under these circumstances: - Updating test to remove flakiness or improve coverage - Updates to the CI/CD process diff --git a/api/README.md b/api/README.md index 56dd23d177d..4ca46c5c4ca 100644 --- a/api/README.md +++ b/api/README.md @@ -36,7 +36,7 @@ npm install @opentelemetry/api @opentelemetry/sdk-trace-base ### Trace Your Application -In order to get started with tracing, you will need to first register an SDK. The SDK you are using may provide a convenience method which calls the registration methods for you, but if you would like to call them directly they are documented here: [sdk registration methods][docs-sdk-registration]. +In order to get started with tracing, you will need to first register an SDK. The SDK you are using may provide a convenience method which calls the registration methods for you, but if you would like to call them directly they are documented here: [SDK registration methods][docs-sdk-registration]. Once you have registered an SDK, you can start and end spans. A simple example of basic SDK registration and tracing a simple operation is below. The example should export spans to the console once per second. For more information, see the [tracing documentation][docs-tracing]. diff --git a/api/test/common/metrics/Metric.test.ts b/api/test/common/metrics/Metric.test.ts index 95422d4cc2f..cfb12e476a9 100644 --- a/api/test/common/metrics/Metric.test.ts +++ b/api/test/common/metrics/Metric.test.ts @@ -42,7 +42,7 @@ describe('Metric', () => { const counter: Counter = { add(_value: number, _attribute: Attributes) {}, }; - // @ts-expect-error Expacting the type of Attributes + // @ts-expect-error Expecting the type of Attributes counter.add(1, { 'another-attribute': 'value' }); }); }); @@ -72,7 +72,7 @@ describe('Metric', () => { const counter: UpDownCounter = { add(_value: number, _attribute: Attributes) {}, }; - // @ts-expect-error Expacting the type of Attributes + // @ts-expect-error Expecting the type of Attributes counter.add(1, { 'another-attribute': 'value' }); }); }); @@ -102,7 +102,7 @@ describe('Metric', () => { const counter: Histogram = { record(_value: number, _attribute: Attributes) {}, }; - // @ts-expect-error Expacting the type of Attributes + // @ts-expect-error Expecting the type of Attributes counter.record(1, { 'another-attribute': 'value' }); }); }); diff --git a/doc/upgrade-guide.md b/doc/upgrade-guide.md index 7c8ea16f7cd..5d6ee28c92e 100644 --- a/doc/upgrade-guide.md +++ b/doc/upgrade-guide.md @@ -107,7 +107,7 @@ Collector exporter packages and types are renamed: - All plugins have been removed in favor of instrumentations. -- The `@opentelemetry/propagator-b3` package previously exported three propagators: `B3Propagator`,`B3SinglePropagator`, and `B3MultiPropagator`, but now only exports the `B3Propagator`. It extracts b3 context in single and multi-header encodings, and injects context using the single-header encoding by default, but can be configured to inject context using the multi-header endcoding during construction: `new B3Propagator({ injectEncoding: B3InjectEncoding.MULTI_HEADER })`. If you were previously using the `B3SinglePropagator` or `B3MultiPropagator` directly, you should update your code to use the `B3Propagator` with the appropriate configuration. See the [readme][otel-propagator-b3] for full details and usage. +- The `@opentelemetry/propagator-b3` package previously exported three propagators: `B3Propagator`,`B3SinglePropagator`, and `B3MultiPropagator`, but now only exports the `B3Propagator`. It extracts b3 context in single and multi-header encodings, and injects context using the single-header encoding by default, but can be configured to inject context using the multi-header encoding during construction: `new B3Propagator({ injectEncoding: B3InjectEncoding.MULTI_HEADER })`. If you were previously using the `B3SinglePropagator` or `B3MultiPropagator` directly, you should update your code to use the `B3Propagator` with the appropriate configuration. See the [README][otel-propagator-b3] for full details and usage. - Sampling configuration via environment variable has changed. If you were using `OTEL_SAMPLING_PROBABILITY` then you should replace it with `OTEL_TRACES_SAMPLER=parentbased_traceidratio` and `OTEL_TRACES_SAMPLER_ARG=` where `` is a number in the [0..1] range, e.g. "0.25". Default is 1.0 if unset. @@ -232,7 +232,7 @@ Some types exported from `"@opentelemetry/api"` have been changed to be more spe ## 0.15.0 to 0.16.0 -[PR-1863](https://github.com/open-telemetry/opentelemetry-js/pull/1863) removed public attributes `keepAlive` and `httpAgentOptions` from nodejs `CollectorTraceExporter` and `CollectorMetricExporter` +[PR-1863](https://github.com/open-telemetry/opentelemetry-js/pull/1863) removed public attributes `keepAlive` and `httpAgentOptions` from Node.js `CollectorTraceExporter` and `CollectorMetricExporter` ## 0.14.0 to 0.15.0 diff --git a/experimental/CHANGELOG.md b/experimental/CHANGELOG.md index 51e7e582172..8a19a85904b 100644 --- a/experimental/CHANGELOG.md +++ b/experimental/CHANGELOG.md @@ -10,6 +10,9 @@ All notable changes to experimental packages in this project will be documented ### :bug: (Bug Fix) +* fix(exporter-*-otlp-*): use parseHeaders() to ensure header-values are not 'undefined' #4540 + * Fixes a bug where passing `undefined` as a header value would crash the end-user app after the export timeout elapsed. + ### :books: (Refine Doc) ### :house: (Internal) @@ -59,6 +62,19 @@ All notable changes to experimental packages in this project will be documented * This breaking change only affects users that are using the *experimental* `@opentelemetry/instrumentation/hook.mjs` loader hook AND Node.js 18.19 or later: * This reverts back to an older version of `import-in-the-middle` due to * This version does not support Node.js 18.19 or later +* fix(exporter-*-otlp-grpc)!: lazy load gRPC to improve compatibility with `@opentelemetry/instrumenation-grpc` [#4432](https://github.com/open-telemetry/opentelemetry-js/pull/4432) @pichlermarc + * Fixes a bug where requiring up the gRPC exporter before enabling the instrumentation from `@opentelemetry/instrumentation-grpc` would lead to missing telemetry + * Breaking changes, removes several functions and properties that were used internally and were not intended for end-users + * `getServiceClientType()` + * this returned a static enum value that would denote the export type (`SPAN`, `METRICS`, `LOGS`) + * `getServiceProtoPath()` + * this returned a static enum value that would correspond to the gRPC service path + * `metadata` + * was used internally to access metadata, but as a side effect allowed end-users to modify metadata on runtime. + * `serviceClient` + * was used internally to keep track of the service client used by the exporter, as a side effect it allowed end-users to modify the gRPC service client that was used + * `compression` + * was used internally to keep track of the compression to use but was unintentionally exposed to the users. It allowed to read and write the value, writing, however, would have no effect. ### :bug: (Bug Fix) diff --git a/experimental/packages/exporter-logs-otlp-grpc/src/OTLPLogExporter.ts b/experimental/packages/exporter-logs-otlp-grpc/src/OTLPLogExporter.ts index 19b747fca72..b59a177b22e 100644 --- a/experimental/packages/exporter-logs-otlp-grpc/src/OTLPLogExporter.ts +++ b/experimental/packages/exporter-logs-otlp-grpc/src/OTLPLogExporter.ts @@ -16,17 +16,17 @@ import { LogRecordExporter, ReadableLogRecord } from '@opentelemetry/sdk-logs'; import { baggageUtils, getEnv } from '@opentelemetry/core'; -import { Metadata } from '@grpc/grpc-js'; import { OTLPGRPCExporterConfigNode, OTLPGRPCExporterNodeBase, - ServiceClientType, validateAndNormalizeUrl, DEFAULT_COLLECTOR_URL, + LogsSerializer, } from '@opentelemetry/otlp-grpc-exporter-base'; import { createExportLogsServiceRequest, IExportLogsServiceRequest, + IExportLogsServiceResponse, } from '@opentelemetry/otlp-transformer'; import { VERSION } from './version'; @@ -38,21 +38,27 @@ const USER_AGENT = { * OTLP Logs Exporter for Node */ export class OTLPLogExporter - extends OTLPGRPCExporterNodeBase + extends OTLPGRPCExporterNodeBase< + ReadableLogRecord, + IExportLogsServiceRequest, + IExportLogsServiceResponse + > implements LogRecordExporter { constructor(config: OTLPGRPCExporterConfigNode = {}) { - super(config); - const headers = { + const signalSpecificMetadata = { ...USER_AGENT, ...baggageUtils.parseKeyPairsIntoRecord( getEnv().OTEL_EXPORTER_OTLP_LOGS_HEADERS ), }; - this.metadata ||= new Metadata(); - for (const [k, v] of Object.entries(headers)) { - this.metadata.set(k, v); - } + super( + config, + signalSpecificMetadata, + 'LogsExportService', + '/opentelemetry.proto.collector.logs.v1.LogsService/Export', + LogsSerializer + ); } convert(logRecords: ReadableLogRecord[]): IExportLogsServiceRequest { @@ -63,14 +69,6 @@ export class OTLPLogExporter return validateAndNormalizeUrl(this.getUrlFromConfig(config)); } - getServiceClientType() { - return ServiceClientType.LOGS; - } - - getServiceProtoPath(): string { - return 'opentelemetry/proto/collector/logs/v1/logs_service.proto'; - } - getUrlFromConfig(config: OTLPGRPCExporterConfigNode): string { if (typeof config.url === 'string') { return config.url; diff --git a/experimental/packages/exporter-logs-otlp-grpc/test/OTLPLogExporter.test.ts b/experimental/packages/exporter-logs-otlp-grpc/test/OTLPLogExporter.test.ts index bb57df4a4ea..e48a1cef684 100644 --- a/experimental/packages/exporter-logs-otlp-grpc/test/OTLPLogExporter.test.ts +++ b/experimental/packages/exporter-logs-otlp-grpc/test/OTLPLogExporter.test.ts @@ -32,7 +32,6 @@ import { } from './logsHelper'; import * as core from '@opentelemetry/core'; import { CompressionAlgorithm } from '@opentelemetry/otlp-exporter-base'; -import { GrpcCompressionAlgorithm } from '@opentelemetry/otlp-grpc-exporter-base'; import { IExportLogsServiceRequest, IResourceLogs, @@ -292,7 +291,7 @@ const testCollectorExporter = (params: TestParams) => { }); assert.strictEqual( collectorExporter.compression, - GrpcCompressionAlgorithm.GZIP + CompressionAlgorithm.GZIP ); delete envSource.OTEL_EXPORTER_OTLP_COMPRESSION; }); @@ -320,44 +319,54 @@ describe('OTLPLogExporter - node (getDefaultUrl)', () => { describe('when configuring via environment', () => { const envSource = process.env; + + afterEach(function () { + // Ensure we don't pollute other tests if assertions fail + delete envSource.OTEL_EXPORTER_OTLP_ENDPOINT; + delete envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT; + delete envSource.OTEL_EXPORTER_OTLP_HEADERS; + delete envSource.OTEL_EXPORTER_OTLP_LOGS_HEADERS; + sinon.restore(); + }); + it('should use url defined in env', () => { envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar'; const collectorExporter = new OTLPLogExporter(); assert.strictEqual(collectorExporter.url, 'foo.bar'); - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; }); it('should override global exporter url with signal url defined in env', () => { envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar'; envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = 'http://foo.logs'; const collectorExporter = new OTLPLogExporter(); assert.strictEqual(collectorExporter.url, 'foo.logs'); - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; - envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = ''; }); it('should include user-agent header by default', () => { const collectorExporter = new OTLPLogExporter(); - assert.deepStrictEqual(collectorExporter.metadata?.get('User-Agent'), [ + const actualMetadata = + collectorExporter['_transport']['_parameters'].metadata(); + assert.deepStrictEqual(actualMetadata.get('User-Agent'), [ `OTel-OTLP-Exporter-JavaScript/${VERSION}`, ]); }); it('should use headers defined via env', () => { envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar'; const collectorExporter = new OTLPLogExporter(); - assert.deepStrictEqual(collectorExporter.metadata?.get('foo'), ['bar']); - envSource.OTEL_EXPORTER_OTLP_HEADERS = ''; + const actualMetadata = + collectorExporter['_transport']['_parameters'].metadata(); + assert.deepStrictEqual(actualMetadata.get('foo'), ['bar']); }); - it('should override global headers config with signal headers defined via env', () => { + it('should not override hard-coded headers config with headers defined via env', () => { const metadata = new grpc.Metadata(); metadata.set('foo', 'bar'); metadata.set('goo', 'lol'); envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=jar,bar=foo'; envSource.OTEL_EXPORTER_OTLP_LOGS_HEADERS = 'foo=boo'; const collectorExporter = new OTLPLogExporter({ metadata }); - assert.deepStrictEqual(collectorExporter.metadata?.get('foo'), ['boo']); - assert.deepStrictEqual(collectorExporter.metadata?.get('bar'), ['foo']); - assert.deepStrictEqual(collectorExporter.metadata?.get('goo'), ['lol']); - envSource.OTEL_EXPORTER_OTLP_LOGS_HEADERS = ''; - envSource.OTEL_EXPORTER_OTLP_HEADERS = ''; + const actualMetadata = + collectorExporter['_transport']['_parameters'].metadata(); + assert.deepStrictEqual(actualMetadata.get('foo'), ['bar']); + assert.deepStrictEqual(actualMetadata.get('goo'), ['lol']); + assert.deepStrictEqual(actualMetadata.get('bar'), ['foo']); }); }); diff --git a/experimental/packages/exporter-logs-otlp-http/src/platform/node/OTLPLogExporter.ts b/experimental/packages/exporter-logs-otlp-http/src/platform/node/OTLPLogExporter.ts index 9181b082cf9..0a020cb0593 100644 --- a/experimental/packages/exporter-logs-otlp-http/src/platform/node/OTLPLogExporter.ts +++ b/experimental/packages/exporter-logs-otlp-http/src/platform/node/OTLPLogExporter.ts @@ -21,7 +21,10 @@ import type { import type { OTLPExporterNodeConfigBase } from '@opentelemetry/otlp-exporter-base'; import type { IExportLogsServiceRequest } from '@opentelemetry/otlp-transformer'; import { getEnv, baggageUtils } from '@opentelemetry/core'; -import { OTLPExporterNodeBase } from '@opentelemetry/otlp-exporter-base'; +import { + OTLPExporterNodeBase, + parseHeaders, +} from '@opentelemetry/otlp-exporter-base'; import { createExportLogsServiceRequest } from '@opentelemetry/otlp-transformer'; import { getDefaultUrl } from '../config'; @@ -50,7 +53,7 @@ export class OTLPLogExporter ...baggageUtils.parseKeyPairsIntoRecord( getEnv().OTEL_EXPORTER_OTLP_LOGS_HEADERS ), - ...config.headers, + ...parseHeaders(config?.headers), }; } diff --git a/experimental/packages/exporter-logs-otlp-proto/src/platform/node/OTLPLogExporter.ts b/experimental/packages/exporter-logs-otlp-proto/src/platform/node/OTLPLogExporter.ts index caec7352fff..16e86fc21e0 100644 --- a/experimental/packages/exporter-logs-otlp-proto/src/platform/node/OTLPLogExporter.ts +++ b/experimental/packages/exporter-logs-otlp-proto/src/platform/node/OTLPLogExporter.ts @@ -19,6 +19,7 @@ import { OTLPExporterConfigBase, appendResourcePathToUrl, appendRootPathToUrlIfNeeded, + parseHeaders, } from '@opentelemetry/otlp-exporter-base'; import { OTLPProtoExporterNodeBase, @@ -57,7 +58,7 @@ export class OTLPLogExporter ...baggageUtils.parseKeyPairsIntoRecord( getEnv().OTEL_EXPORTER_OTLP_LOGS_HEADERS ), - ...config.headers, + ...parseHeaders(config?.headers), }; } convert(logs: ReadableLogRecord[]): IExportLogsServiceRequest { diff --git a/experimental/packages/exporter-trace-otlp-grpc/src/OTLPTraceExporter.ts b/experimental/packages/exporter-trace-otlp-grpc/src/OTLPTraceExporter.ts index c99826a176f..88e55734e6e 100644 --- a/experimental/packages/exporter-trace-otlp-grpc/src/OTLPTraceExporter.ts +++ b/experimental/packages/exporter-trace-otlp-grpc/src/OTLPTraceExporter.ts @@ -16,17 +16,17 @@ import { ReadableSpan, SpanExporter } from '@opentelemetry/sdk-trace-base'; import { baggageUtils, getEnv } from '@opentelemetry/core'; -import { Metadata } from '@grpc/grpc-js'; import { OTLPGRPCExporterConfigNode, OTLPGRPCExporterNodeBase, - ServiceClientType, validateAndNormalizeUrl, DEFAULT_COLLECTOR_URL, + TraceSerializer, } from '@opentelemetry/otlp-grpc-exporter-base'; import { createExportTraceServiceRequest, IExportTraceServiceRequest, + IExportTraceServiceResponse, } from '@opentelemetry/otlp-transformer'; import { VERSION } from './version'; @@ -38,21 +38,27 @@ const USER_AGENT = { * OTLP Trace Exporter for Node */ export class OTLPTraceExporter - extends OTLPGRPCExporterNodeBase + extends OTLPGRPCExporterNodeBase< + ReadableSpan, + IExportTraceServiceRequest, + IExportTraceServiceResponse + > implements SpanExporter { constructor(config: OTLPGRPCExporterConfigNode = {}) { - super(config); - const headers = { + const signalSpecificMetadata = { ...USER_AGENT, ...baggageUtils.parseKeyPairsIntoRecord( getEnv().OTEL_EXPORTER_OTLP_TRACES_HEADERS ), }; - this.metadata ||= new Metadata(); - for (const [k, v] of Object.entries(headers)) { - this.metadata.set(k, v); - } + super( + config, + signalSpecificMetadata, + 'TraceExportService', + '/opentelemetry.proto.collector.trace.v1.TraceService/Export', + TraceSerializer + ); } convert(spans: ReadableSpan[]): IExportTraceServiceRequest { @@ -63,14 +69,6 @@ export class OTLPTraceExporter return validateAndNormalizeUrl(this.getUrlFromConfig(config)); } - getServiceClientType() { - return ServiceClientType.SPANS; - } - - getServiceProtoPath(): string { - return 'opentelemetry/proto/collector/trace/v1/trace_service.proto'; - } - getUrlFromConfig(config: OTLPGRPCExporterConfigNode): string { if (typeof config.url === 'string') { return config.url; diff --git a/experimental/packages/exporter-trace-otlp-grpc/test/OTLPTraceExporter.test.ts b/experimental/packages/exporter-trace-otlp-grpc/test/OTLPTraceExporter.test.ts index f22fc95d2ad..9a3a13ce322 100644 --- a/experimental/packages/exporter-trace-otlp-grpc/test/OTLPTraceExporter.test.ts +++ b/experimental/packages/exporter-trace-otlp-grpc/test/OTLPTraceExporter.test.ts @@ -37,7 +37,6 @@ import { } from './traceHelper'; import * as core from '@opentelemetry/core'; import { CompressionAlgorithm } from '@opentelemetry/otlp-exporter-base'; -import { GrpcCompressionAlgorithm } from '@opentelemetry/otlp-grpc-exporter-base'; import { IExportTraceServiceRequest, IResourceSpans, @@ -302,7 +301,7 @@ const testCollectorExporter = (params: TestParams) => { }); assert.strictEqual( collectorExporter.compression, - GrpcCompressionAlgorithm.GZIP + CompressionAlgorithm.GZIP ); delete envSource.OTEL_EXPORTER_OTLP_COMPRESSION; }); @@ -330,44 +329,54 @@ describe('OTLPTraceExporter - node (getDefaultUrl)', () => { describe('when configuring via environment', () => { const envSource = process.env; + + afterEach(function () { + // Ensure we don't pollute other tests if assertions fail + delete envSource.OTEL_EXPORTER_OTLP_ENDPOINT; + delete envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT; + delete envSource.OTEL_EXPORTER_OTLP_HEADERS; + delete envSource.OTEL_EXPORTER_OTLP_TRACES_HEADERS; + sinon.restore(); + }); + it('should use url defined in env', () => { envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar'; const collectorExporter = new OTLPTraceExporter(); assert.strictEqual(collectorExporter.url, 'foo.bar'); - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; }); it('should override global exporter url with signal url defined in env', () => { envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar'; envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = 'http://foo.traces'; const collectorExporter = new OTLPTraceExporter(); assert.strictEqual(collectorExporter.url, 'foo.traces'); - envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; - envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = ''; }); it('should use headers defined via env', () => { envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar'; const collectorExporter = new OTLPTraceExporter(); - assert.deepStrictEqual(collectorExporter.metadata?.get('foo'), ['bar']); - envSource.OTEL_EXPORTER_OTLP_HEADERS = ''; + const actualMetadata = + collectorExporter['_transport']['_parameters'].metadata(); + assert.deepStrictEqual(actualMetadata.get('foo'), ['bar']); }); it('should include user agent in header', () => { const collectorExporter = new OTLPTraceExporter(); - assert.deepStrictEqual(collectorExporter.metadata?.get('User-Agent'), [ + const actualMetadata = + collectorExporter['_transport']['_parameters'].metadata(); + assert.deepStrictEqual(actualMetadata.get('User-Agent'), [ `OTel-OTLP-Exporter-JavaScript/${VERSION}`, ]); }); - it('should override global headers config with signal headers defined via env', () => { + it('should not override hard-coded headers config with headers defined via env', () => { const metadata = new grpc.Metadata(); metadata.set('foo', 'bar'); metadata.set('goo', 'lol'); envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=jar,bar=foo'; envSource.OTEL_EXPORTER_OTLP_TRACES_HEADERS = 'foo=boo'; const collectorExporter = new OTLPTraceExporter({ metadata }); - assert.deepStrictEqual(collectorExporter.metadata?.get('foo'), ['boo']); - assert.deepStrictEqual(collectorExporter.metadata?.get('bar'), ['foo']); - assert.deepStrictEqual(collectorExporter.metadata?.get('goo'), ['lol']); - envSource.OTEL_EXPORTER_OTLP_TRACES_HEADERS = ''; - envSource.OTEL_EXPORTER_OTLP_HEADERS = ''; + const actualMetadata = + collectorExporter['_transport']['_parameters'].metadata(); + assert.deepStrictEqual(actualMetadata.get('foo'), ['bar']); + assert.deepStrictEqual(actualMetadata.get('goo'), ['lol']); + assert.deepStrictEqual(actualMetadata.get('bar'), ['foo']); }); }); diff --git a/experimental/packages/exporter-trace-otlp-http/src/platform/node/OTLPTraceExporter.ts b/experimental/packages/exporter-trace-otlp-http/src/platform/node/OTLPTraceExporter.ts index aeb3b94ca72..892e5c5b692 100644 --- a/experimental/packages/exporter-trace-otlp-http/src/platform/node/OTLPTraceExporter.ts +++ b/experimental/packages/exporter-trace-otlp-http/src/platform/node/OTLPTraceExporter.ts @@ -16,7 +16,10 @@ import { ReadableSpan, SpanExporter } from '@opentelemetry/sdk-trace-base'; import { getEnv, baggageUtils } from '@opentelemetry/core'; -import { OTLPExporterNodeBase } from '@opentelemetry/otlp-exporter-base'; +import { + OTLPExporterNodeBase, + parseHeaders, +} from '@opentelemetry/otlp-exporter-base'; import { OTLPExporterNodeConfigBase, appendResourcePathToUrl, @@ -49,7 +52,7 @@ export class OTLPTraceExporter ...baggageUtils.parseKeyPairsIntoRecord( getEnv().OTEL_EXPORTER_OTLP_TRACES_HEADERS ), - ...config.headers, + ...parseHeaders(config?.headers), }; } diff --git a/experimental/packages/exporter-trace-otlp-proto/src/platform/node/OTLPTraceExporter.ts b/experimental/packages/exporter-trace-otlp-proto/src/platform/node/OTLPTraceExporter.ts index be115583cd6..48191660ecc 100644 --- a/experimental/packages/exporter-trace-otlp-proto/src/platform/node/OTLPTraceExporter.ts +++ b/experimental/packages/exporter-trace-otlp-proto/src/platform/node/OTLPTraceExporter.ts @@ -20,6 +20,7 @@ import { OTLPExporterNodeConfigBase, appendResourcePathToUrl, appendRootPathToUrlIfNeeded, + parseHeaders, } from '@opentelemetry/otlp-exporter-base'; import { OTLPProtoExporterNodeBase, @@ -52,7 +53,7 @@ export class OTLPTraceExporter ...baggageUtils.parseKeyPairsIntoRecord( getEnv().OTEL_EXPORTER_OTLP_TRACES_HEADERS ), - ...config.headers, + ...parseHeaders(config?.headers), }; } diff --git a/experimental/packages/opentelemetry-browser-detector/README.md b/experimental/packages/opentelemetry-browser-detector/README.md index a1e9002ea98..2ca25a7e76b 100644 --- a/experimental/packages/opentelemetry-browser-detector/README.md +++ b/experimental/packages/opentelemetry-browser-detector/README.md @@ -51,4 +51,4 @@ start().then(()=> console.log("Instrumentation started")); The browser identification attributes will be added to the resource spans when traces are created. These attributes include platform, brands, mobile, language if the browser supports -the userAgentData api, otherwise it will contain only the user_agent informations +the userAgentData api, otherwise it will contain only the user_agent information diff --git a/experimental/packages/opentelemetry-browser-detector/src/types.ts b/experimental/packages/opentelemetry-browser-detector/src/types.ts index c8567a4f95f..6cd6b020936 100644 --- a/experimental/packages/opentelemetry-browser-detector/src/types.ts +++ b/experimental/packages/opentelemetry-browser-detector/src/types.ts @@ -20,7 +20,7 @@ export type UserAgentData = { }; export const BROWSER_ATTRIBUTES = { - PLATFORM: 'browser.platform', //TODO replace with SemantecConventions attribute when available + PLATFORM: 'browser.platform', //TODO replace with SemanticConventions attribute when available BRANDS: 'browser.brands', MOBILE: 'browser.mobile', LANGUAGE: 'browser.language', diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/src/OTLPMetricExporter.ts b/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/src/OTLPMetricExporter.ts index b1b12272750..4151dbd5396 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/src/OTLPMetricExporter.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/src/OTLPMetricExporter.ts @@ -22,17 +22,18 @@ import { ResourceMetrics } from '@opentelemetry/sdk-metrics'; import { OTLPGRPCExporterConfigNode, OTLPGRPCExporterNodeBase, - ServiceClientType, validateAndNormalizeUrl, DEFAULT_COLLECTOR_URL, + MetricsSerializer, } from '@opentelemetry/otlp-grpc-exporter-base'; import { baggageUtils, getEnv } from '@opentelemetry/core'; -import { Metadata } from '@grpc/grpc-js'; import { createExportMetricsServiceRequest, IExportMetricsServiceRequest, + IExportMetricsServiceResponse, } from '@opentelemetry/otlp-transformer'; import { VERSION } from './version'; +import { parseHeaders } from '@opentelemetry/otlp-exporter-base'; const USER_AGENT = { 'User-Agent': `OTel-OTLP-Exporter-JavaScript/${VERSION}`, @@ -40,30 +41,24 @@ const USER_AGENT = { class OTLPMetricExporterProxy extends OTLPGRPCExporterNodeBase< ResourceMetrics, - IExportMetricsServiceRequest + IExportMetricsServiceRequest, + IExportMetricsServiceResponse > { constructor(config?: OTLPGRPCExporterConfigNode & OTLPMetricExporterOptions) { - super(config); - const headers = { + const signalSpecificMetadata = { ...USER_AGENT, ...baggageUtils.parseKeyPairsIntoRecord( getEnv().OTEL_EXPORTER_OTLP_METRICS_HEADERS ), - ...config?.headers, + ...parseHeaders(config?.headers), }; - - this.metadata ||= new Metadata(); - for (const [k, v] of Object.entries(headers)) { - this.metadata.set(k, v); - } - } - - getServiceProtoPath(): string { - return 'opentelemetry/proto/collector/metrics/v1/metrics_service.proto'; - } - - getServiceClientType(): ServiceClientType { - return ServiceClientType.METRICS; + super( + config, + signalSpecificMetadata, + 'MetricsExportService', + '/opentelemetry.proto.collector.metrics.v1.MetricsService/Export', + MetricsSerializer + ); } getDefaultUrl(config: OTLPGRPCExporterConfigNode): string { diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/test/OTLPMetricExporter.test.ts b/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/test/OTLPMetricExporter.test.ts index 79a6125a095..3ed034bde89 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/test/OTLPMetricExporter.test.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/test/OTLPMetricExporter.test.ts @@ -307,6 +307,15 @@ describe('OTLPMetricExporter - node (getDefaultUrl)', () => { }); describe('when configuring via environment', () => { + afterEach(function () { + // Ensure we don't pollute other tests if assertions fail + delete envSource.OTEL_EXPORTER_OTLP_ENDPOINT; + delete envSource.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT; + delete envSource.OTEL_EXPORTER_OTLP_HEADERS; + delete envSource.OTEL_EXPORTER_OTLP_METRICS_HEADERS; + sinon.restore(); + }); + const envSource = process.env; it('should use url defined in env', () => { envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar'; @@ -334,20 +343,20 @@ describe('when configuring via environment', () => { it('should use headers defined via env', () => { envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar'; const collectorExporter = new OTLPMetricExporter(); - assert.deepStrictEqual( - collectorExporter._otlpExporter.metadata?.get('foo'), - ['bar'] - ); + const actualMetadata = + collectorExporter._otlpExporter['_transport']['_parameters'].metadata(); + assert.deepStrictEqual(actualMetadata.get('foo'), ['bar']); envSource.OTEL_EXPORTER_OTLP_HEADERS = ''; }); it('should include user agent in header', () => { const collectorExporter = new OTLPMetricExporter(); - assert.deepStrictEqual( - collectorExporter._otlpExporter.metadata?.get('User-Agent'), - [`OTel-OTLP-Exporter-JavaScript/${VERSION}`] - ); + const actualMetadata = + collectorExporter._otlpExporter['_transport']['_parameters'].metadata(); + assert.deepStrictEqual(actualMetadata.get('User-Agent'), [ + `OTel-OTLP-Exporter-JavaScript/${VERSION}`, + ]); }); - it('should override global headers config with signal headers defined via env', () => { + it('should not override hard-coded headers config with headers defined via env', () => { const metadata = new grpc.Metadata(); metadata.set('foo', 'bar'); metadata.set('goo', 'lol'); @@ -357,21 +366,15 @@ describe('when configuring via environment', () => { metadata, temporalityPreference: AggregationTemporalityPreference.CUMULATIVE, }); - assert.deepStrictEqual( - collectorExporter._otlpExporter.metadata?.get('foo'), - ['boo'] - ); - assert.deepStrictEqual( - collectorExporter._otlpExporter.metadata?.get('bar'), - ['foo'] - ); - assert.deepStrictEqual( - collectorExporter._otlpExporter.metadata?.get('goo'), - ['lol'] - ); + const actualMetadata = + collectorExporter._otlpExporter['_transport']['_parameters'].metadata(); + assert.deepStrictEqual(actualMetadata.get('foo'), ['bar']); + assert.deepStrictEqual(actualMetadata.get('bar'), ['foo']); + assert.deepStrictEqual(actualMetadata.get('goo'), ['lol']); envSource.OTEL_EXPORTER_OTLP_METRICS_HEADERS = ''; envSource.OTEL_EXPORTER_OTLP_HEADERS = ''; }); + it('should override headers defined via env with headers defined in constructor', () => { envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar,bar=foo'; const collectorExporter = new OTLPMetricExporter({ @@ -379,14 +382,11 @@ describe('when configuring via environment', () => { foo: 'constructor', }, }); - assert.deepStrictEqual( - collectorExporter._otlpExporter.metadata?.get('foo'), - ['constructor'] - ); - assert.deepStrictEqual( - collectorExporter._otlpExporter.metadata?.get('bar'), - ['foo'] - ); + + const actualMetadata = + collectorExporter._otlpExporter['_transport']['_parameters'].metadata(); + assert.deepStrictEqual(actualMetadata.get('foo'), ['constructor']); + assert.deepStrictEqual(actualMetadata.get('bar'), ['foo']); envSource.OTEL_EXPORTER_OTLP_HEADERS = ''; }); }); diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/src/platform/node/OTLPMetricExporter.ts b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/src/platform/node/OTLPMetricExporter.ts index cd648ce8753..5831f74cf9a 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/src/platform/node/OTLPMetricExporter.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/src/platform/node/OTLPMetricExporter.ts @@ -23,6 +23,7 @@ import { OTLPExporterNodeConfigBase, appendResourcePathToUrl, appendRootPathToUrlIfNeeded, + parseHeaders, } from '@opentelemetry/otlp-exporter-base'; import { createExportMetricsServiceRequest, @@ -48,7 +49,7 @@ class OTLPExporterNodeProxy extends OTLPExporterNodeBase< ...baggageUtils.parseKeyPairsIntoRecord( getEnv().OTEL_EXPORTER_OTLP_METRICS_HEADERS ), - ...config?.headers, + ...parseHeaders(config?.headers), }; } diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/src/OTLPMetricExporter.ts b/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/src/OTLPMetricExporter.ts index 52c9991e30a..9d424687cf6 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/src/OTLPMetricExporter.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/src/OTLPMetricExporter.ts @@ -26,6 +26,7 @@ import { OTLPExporterNodeConfigBase, appendResourcePathToUrl, appendRootPathToUrlIfNeeded, + parseHeaders, } from '@opentelemetry/otlp-exporter-base'; import { createExportMetricsServiceRequest, @@ -51,7 +52,7 @@ class OTLPMetricExporterNodeProxy extends OTLPProtoExporterNodeBase< ...baggageUtils.parseKeyPairsIntoRecord( getEnv().OTEL_EXPORTER_OTLP_METRICS_HEADERS ), - ...config?.headers, + ...parseHeaders(config?.headers), }; } diff --git a/experimental/packages/opentelemetry-instrumentation-fetch/test/fetch.test.ts b/experimental/packages/opentelemetry-instrumentation-fetch/test/fetch.test.ts index 832b2d89990..292f05ef91b 100644 --- a/experimental/packages/opentelemetry-instrumentation-fetch/test/fetch.test.ts +++ b/experimental/packages/opentelemetry-instrumentation-fetch/test/fetch.test.ts @@ -654,7 +654,7 @@ describe('fetch', () => { clearData(); }); - it('applies attributes when the request is succesful', async () => { + it('applies attributes when the request is successful', async () => { await prepare(url, span => { span.setAttribute(CUSTOM_ATTRIBUTE_KEY, 'custom value'); }); diff --git a/experimental/packages/opentelemetry-instrumentation-grpc/src/clientUtils.ts b/experimental/packages/opentelemetry-instrumentation-grpc/src/clientUtils.ts index f4c5470d16c..281e884d3f0 100644 --- a/experimental/packages/opentelemetry-instrumentation-grpc/src/clientUtils.ts +++ b/experimental/packages/opentelemetry-instrumentation-grpc/src/clientUtils.ts @@ -152,7 +152,7 @@ export function patchResponseStreamEvents(span: Span, call: EventEmitter) { } /** - * Execute grpc client call. Apply completitionspan properties and end the + * Execute grpc client call. Apply completion span properties and end the * span on callback or receiving an emitted event. */ export function makeGrpcClientRemoteCall( diff --git a/experimental/packages/opentelemetry-instrumentation-grpc/test/helper.ts b/experimental/packages/opentelemetry-instrumentation-grpc/test/helper.ts index 9d861fd6512..131dde04b4b 100644 --- a/experimental/packages/opentelemetry-instrumentation-grpc/test/helper.ts +++ b/experimental/packages/opentelemetry-instrumentation-grpc/test/helper.ts @@ -136,7 +136,7 @@ export async function startServer(proto: any, port: number) { server.addService(proto.GrpcTester.service, { // An error is emitted every time // request.num <= MAX_ERROR_STATUS = (status.UNAUTHENTICATED) - // in those cases, erro.code = request.num + // in those cases, error.code = request.num // This method returns the request // This method returns the request diff --git a/experimental/packages/opentelemetry-instrumentation-http/README.md b/experimental/packages/opentelemetry-instrumentation-http/README.md index 312eb73bd76..8fd50c3107c 100644 --- a/experimental/packages/opentelemetry-instrumentation-http/README.md +++ b/experimental/packages/opentelemetry-instrumentation-http/README.md @@ -67,7 +67,7 @@ The following options are deprecated: | Options | Type | Description | | ------- | ---- | ----------- | | `ignoreIncomingPaths` | `IgnoreMatcher[]` | Http instrumentation will not trace all incoming requests that match paths | -| `ignoreOutgoingUrls` | `IgnoreMatcher[]` | Http instrumentation will not trace all outgoing requests that match urls | +| `ignoreOutgoingUrls` | `IgnoreMatcher[]` | Http instrumentation will not trace all outgoing requests that match URLs | ## Useful links diff --git a/experimental/packages/opentelemetry-instrumentation-http/src/utils.ts b/experimental/packages/opentelemetry-instrumentation-http/src/utils.ts index a70dd3de854..a782b8ab60a 100644 --- a/experimental/packages/opentelemetry-instrumentation-http/src/utils.ts +++ b/experimental/packages/opentelemetry-instrumentation-http/src/utils.ts @@ -373,7 +373,7 @@ export const getOutgoingRequestMetricAttributes = ( spanAttributes[SemanticAttributes.HTTP_METHOD]; metricAttributes[SemanticAttributes.NET_PEER_NAME] = spanAttributes[SemanticAttributes.NET_PEER_NAME]; - //TODO: http.url attribute, it should susbtitute any parameters to avoid high cardinality. + //TODO: http.url attribute, it should substitute any parameters to avoid high cardinality. return metricAttributes; }; @@ -514,7 +514,7 @@ export const getIncomingRequestMetricAttributes = ( spanAttributes[SemanticAttributes.NET_HOST_NAME]; metricAttributes[SemanticAttributes.HTTP_FLAVOR] = spanAttributes[SemanticAttributes.HTTP_FLAVOR]; - //TODO: http.target attribute, it should susbtitute any parameters to avoid high cardinality. + //TODO: http.target attribute, it should substitute any parameters to avoid high cardinality. return metricAttributes; }; diff --git a/experimental/packages/opentelemetry-instrumentation-http/test/functionals/http-enable.test.ts b/experimental/packages/opentelemetry-instrumentation-http/test/functionals/http-enable.test.ts index e5b2cebb97b..c3eeaf5ee92 100644 --- a/experimental/packages/opentelemetry-instrumentation-http/test/functionals/http-enable.test.ts +++ b/experimental/packages/opentelemetry-instrumentation-http/test/functionals/http-enable.test.ts @@ -555,7 +555,7 @@ describe('HttpInstrumentation', () => { }); for (const arg of ['string', {}, new Date()]) { - it(`should be tracable and not throw exception in ${protocol} instrumentation when passing the following argument ${JSON.stringify( + it(`should be traceable and not throw exception in ${protocol} instrumentation when passing the following argument ${JSON.stringify( arg )}`, async () => { try { @@ -1101,10 +1101,10 @@ describe('HttpInstrumentation', () => { it('should set rpc metadata for incoming http request', async () => { server = http.createServer((request, response) => { - const rpcMemadata = getRPCMetadata(context.active()); - assert(typeof rpcMemadata !== 'undefined'); - assert(rpcMemadata.type === RPCType.HTTP); - assert(rpcMemadata.span.setAttribute('key', 'value')); + const rpcMetadata = getRPCMetadata(context.active()); + assert(typeof rpcMetadata !== 'undefined'); + assert(rpcMetadata.type === RPCType.HTTP); + assert(rpcMetadata.span.setAttribute('key', 'value')); response.end('Test Server Response'); }); await new Promise(resolve => server.listen(serverPort, resolve)); diff --git a/experimental/packages/opentelemetry-instrumentation-http/test/functionals/https-enable.test.ts b/experimental/packages/opentelemetry-instrumentation-http/test/functionals/https-enable.test.ts index 9da1c115b45..668cf9566d5 100644 --- a/experimental/packages/opentelemetry-instrumentation-http/test/functionals/https-enable.test.ts +++ b/experimental/packages/opentelemetry-instrumentation-http/test/functionals/https-enable.test.ts @@ -465,7 +465,7 @@ describe('HttpsInstrumentation', () => { }); for (const arg of ['string', {}, new Date()]) { - it(`should be tracable and not throw exception in ${protocol} instrumentation when passing the following argument ${JSON.stringify( + it(`should be traceable and not throw exception in ${protocol} instrumentation when passing the following argument ${JSON.stringify( arg )}`, async () => { try { diff --git a/experimental/packages/opentelemetry-instrumentation-http/test/functionals/utils.test.ts b/experimental/packages/opentelemetry-instrumentation-http/test/functionals/utils.test.ts index 35b3c1ab5bc..4001335355a 100644 --- a/experimental/packages/opentelemetry-instrumentation-http/test/functionals/utils.test.ts +++ b/experimental/packages/opentelemetry-instrumentation-http/test/functionals/utils.test.ts @@ -311,7 +311,7 @@ describe('Utility', () => { ); }); - it('should succesfully process without middleware stack', () => { + it('should successfully process without middleware stack', () => { const request = { socket: {}, } as IncomingMessage; diff --git a/experimental/packages/opentelemetry-instrumentation/README.md b/experimental/packages/opentelemetry-instrumentation/README.md index 6c27e3d36d0..39ddaeab9f1 100644 --- a/experimental/packages/opentelemetry-instrumentation/README.md +++ b/experimental/packages/opentelemetry-instrumentation/README.md @@ -216,13 +216,13 @@ registerInstrumentations({ The `registerInstrumentations()` API allows to specify which `TracerProvider` and/or `MeterProvider` to use by the given options object. If nothing is specified the global registered provider is used. Usually this is what most users want therefore it's recommended to keep this default. -There might be usecase where someone has the need for more providers within an application. Please note that special care must be takes in such setups +There might be use case where someone has the need for more providers within an application. Please note that special care must be takes in such setups to avoid leaking information from one provider to the other because there are a lot places where e.g. the global `ContextManager` or `Propagator` is used. -## Instrumentation for ES Modules In NodeJS (experimental) +## Instrumentation for ES Modules In Node.js (experimental) -As the module loading mechanism for ESM is different than CJS, you need to select a custom loader so instrumentation can load hook on the esm module it want to patch. To do so, you must provide the `--experimental-loader=@opentelemetry/instrumentation/hook.mjs` flag to the `node` binary. Alternatively you can set the `NODE_OPTIONS` environment variable to `NODE_OPTIONS="--experimental-loader=@opentelemetry/instrumentation/hook.mjs"`. -As the ESM module loader from NodeJS is experimental, so is our support for it. Feel free to provide feedback or report issues about it. +As the module loading mechanism for ESM is different than CJS, you need to select a custom loader so instrumentation can load hook on the ESM module it want to patch. To do so, you must provide the `--experimental-loader=@opentelemetry/instrumentation/hook.mjs` flag to the `node` binary. Alternatively you can set the `NODE_OPTIONS` environment variable to `NODE_OPTIONS="--experimental-loader=@opentelemetry/instrumentation/hook.mjs"`. +As the ESM module loader from Node.js is experimental, so is our support for it. Feel free to provide feedback or report issues about it. **Note**: ESM Instrumentation is not yet supported for Node 20. diff --git a/experimental/packages/opentelemetry-sdk-node/README.md b/experimental/packages/opentelemetry-sdk-node/README.md index 0afe166c719..51ef136be95 100644 --- a/experimental/packages/opentelemetry-sdk-node/README.md +++ b/experimental/packages/opentelemetry-sdk-node/README.md @@ -132,7 +132,7 @@ An array of span processors to register to the tracer provider. ### traceExporter -Configure a trace exporter. If an exporter is configured, it will be used with a [BatchSpanProcessor](../../../packages/opentelemetry-sdk-trace-base/src/platform/node/export/BatchSpanProcessor.ts). If an exporter OR span processor is not configured programatically, this package will auto setup the default `otlp` exporter with `http/protobuf` protocol with a `BatchSpanProcessor`. +Configure a trace exporter. If an exporter is configured, it will be used with a [BatchSpanProcessor](../../../packages/opentelemetry-sdk-trace-base/src/platform/node/export/BatchSpanProcessor.ts). If an exporter OR span processor is not configured programmatically, this package will auto setup the default `otlp` exporter with `http/protobuf` protocol with a `BatchSpanProcessor`. ### spanLimits diff --git a/experimental/packages/opentelemetry-sdk-node/test/TracerProviderWithEnvExporter.test.ts b/experimental/packages/opentelemetry-sdk-node/test/TracerProviderWithEnvExporter.test.ts index 7ceb5e3dce7..21a1fa93d79 100644 --- a/experimental/packages/opentelemetry-sdk-node/test/TracerProviderWithEnvExporter.test.ts +++ b/experimental/packages/opentelemetry-sdk-node/test/TracerProviderWithEnvExporter.test.ts @@ -132,7 +132,7 @@ describe('set up trace exporter with env exporters', () => { assert(listOfProcessors === undefined); delete env.OTEL_TRACES_EXPORTER; }); - it('log warning that sdk will not be initalized when exporter is set to none', async () => { + it('log warning that sdk will not be initialized when exporter is set to none', async () => { env.OTEL_TRACES_EXPORTER = 'none'; new TracerProviderWithEnvExporters(); diff --git a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts index 8251a999847..2019fdce290 100644 --- a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts +++ b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts @@ -951,7 +951,7 @@ describe('setup exporter from env', () => { assert(activeProcessor instanceof NoopSpanProcessor); delete env.OTEL_TRACES_EXPORTER; }); - it('log warning that sdk will not be initalized when exporter is set to none', async () => { + it('log warning that sdk will not be initialized when exporter is set to none', async () => { env.OTEL_TRACES_EXPORTER = 'none'; const sdk = new NodeSDK(); sdk.start(); diff --git a/experimental/packages/otlp-exporter-base/src/util.ts b/experimental/packages/otlp-exporter-base/src/util.ts index f5dc70c9e88..2824752e100 100644 --- a/experimental/packages/otlp-exporter-base/src/util.ts +++ b/experimental/packages/otlp-exporter-base/src/util.ts @@ -35,7 +35,9 @@ export function parseHeaders( if (typeof value !== 'undefined') { headers[key] = String(value); } else { - diag.warn(`Header "${key}" has wrong value and will be ignored`); + diag.warn( + `Header "${key}" has invalid value (${value}) and will be ignored` + ); } }); return headers; diff --git a/experimental/packages/otlp-exporter-base/test/common/util.test.ts b/experimental/packages/otlp-exporter-base/test/common/util.test.ts index b00d1f36a5c..4448ec06da0 100644 --- a/experimental/packages/otlp-exporter-base/test/common/util.test.ts +++ b/experimental/packages/otlp-exporter-base/test/common/util.test.ts @@ -46,7 +46,7 @@ describe('utils', () => { const args = spyWarn.args[0]; assert.strictEqual( args[0], - 'Header "foo1" has wrong value and will be ignored' + 'Header "foo1" has invalid value (undefined) and will be ignored' ); }); @@ -72,7 +72,7 @@ describe('utils', () => { const finalUrl = appendResourcePathToUrl(url, resourcePath); assert.strictEqual(finalUrl, url + '/' + resourcePath); }); - it('should append resourse path even when url already contains path ', () => { + it('should append resource path even when url already contains path ', () => { const url = 'http://foo.bar/v1/traces'; const resourcePath = 'v1/traces'; @@ -82,7 +82,7 @@ describe('utils', () => { }); // only invoked with signal specific endpoint - describe('appendRootPathToUrlIfNeeded - specifc signal http endpoint', () => { + describe('appendRootPathToUrlIfNeeded - specific signal http endpoint', () => { it('should append root path when missing', () => { const url = 'http://foo.bar'; @@ -110,7 +110,7 @@ describe('utils', () => { } }); - it('should not change string when url is not parseable', () => { + it('should not change string when url is not parsable', () => { const url = 'this is not a URL'; const finalUrl = appendRootPathToUrlIfNeeded(url); diff --git a/experimental/packages/otlp-grpc-exporter-base/src/LogsExportServiceClient.ts b/experimental/packages/otlp-grpc-exporter-base/src/LogsExportServiceClient.ts deleted file mode 100644 index b867743cea7..00000000000 --- a/experimental/packages/otlp-grpc-exporter-base/src/LogsExportServiceClient.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import * as root from './generated/root'; -import * as grpc from '@grpc/grpc-js'; -import { - IExportLogsServiceRequest, - IExportLogsServiceResponse, -} from '@opentelemetry/otlp-transformer'; -import { ExportType } from './internal-types'; - -const responseType = root.opentelemetry.proto.collector.logs.v1 - .ExportLogsServiceResponse as ExportType; - -const requestType = root.opentelemetry.proto.collector.logs.v1 - .ExportLogsServiceRequest as ExportType; - -const logsServiceDefinition = { - export: { - path: '/opentelemetry.proto.collector.logs.v1.LogsService/Export', - requestStream: false, - responseStream: false, - requestSerialize: (arg: IExportLogsServiceRequest) => { - return Buffer.from(requestType.encode(arg).finish()); - }, - requestDeserialize: (arg: Buffer) => { - return requestType.decode(arg); - }, - responseSerialize: (arg: IExportLogsServiceResponse) => { - return Buffer.from(responseType.encode(arg).finish()); - }, - responseDeserialize: (arg: Buffer) => { - return responseType.decode(arg); - }, - }, -}; - -// Creates a new instance of a gRPC service client for OTLP logs -export const LogsExportServiceClient: grpc.ServiceClientConstructor = - grpc.makeGenericClientConstructor(logsServiceDefinition, 'LogsExportService'); diff --git a/experimental/packages/otlp-grpc-exporter-base/src/MetricsExportServiceClient.ts b/experimental/packages/otlp-grpc-exporter-base/src/MetricsExportServiceClient.ts deleted file mode 100644 index 7f81be6087d..00000000000 --- a/experimental/packages/otlp-grpc-exporter-base/src/MetricsExportServiceClient.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import * as root from './generated/root'; -import * as grpc from '@grpc/grpc-js'; -import { - IExportMetricsServiceRequest, - IExportMetricsServiceResponse, -} from '@opentelemetry/otlp-transformer'; -import { ExportType } from './internal-types'; - -const responseType = root.opentelemetry.proto.collector.metrics.v1 - .ExportMetricsServiceResponse as ExportType; - -const requestType = root.opentelemetry.proto.collector.metrics.v1 - .ExportMetricsServiceRequest as ExportType; - -const metricsServiceDefinition = { - export: { - path: '/opentelemetry.proto.collector.metrics.v1.MetricsService/Export', - requestStream: false, - responseStream: false, - requestSerialize: (arg: IExportMetricsServiceRequest) => { - return Buffer.from(requestType.encode(arg).finish()); - }, - requestDeserialize: (arg: Buffer) => { - return requestType.decode(arg); - }, - responseSerialize: (arg: IExportMetricsServiceResponse) => { - return Buffer.from(responseType.encode(arg).finish()); - }, - responseDeserialize: (arg: Buffer) => { - return responseType.decode(arg); - }, - }, -}; - -// Creates a new instance of a gRPC service client for OTLP metrics -export const MetricExportServiceClient: grpc.ServiceClientConstructor = - grpc.makeGenericClientConstructor( - metricsServiceDefinition, - 'MetricsExportService' - ); diff --git a/experimental/packages/otlp-grpc-exporter-base/src/OTLPGRPCExporterNodeBase.ts b/experimental/packages/otlp-grpc-exporter-base/src/OTLPGRPCExporterNodeBase.ts index b4f06472c61..cfa7fba3d73 100644 --- a/experimental/packages/otlp-grpc-exporter-base/src/OTLPGRPCExporterNodeBase.ts +++ b/experimental/packages/otlp-grpc-exporter-base/src/OTLPGRPCExporterNodeBase.ts @@ -15,19 +15,20 @@ */ import { diag } from '@opentelemetry/api'; -import { Metadata } from '@grpc/grpc-js'; -import { - OTLPGRPCExporterConfigNode, - GRPCQueueItem, - ServiceClientType, -} from './types'; -import { ServiceClient } from './types'; -import { getEnv, baggageUtils } from '@opentelemetry/core'; -import { configureCompression, GrpcCompressionAlgorithm } from './util'; +import { GRPCQueueItem, OTLPGRPCExporterConfigNode } from './types'; +import { baggageUtils, getEnv } from '@opentelemetry/core'; import { + CompressionAlgorithm, OTLPExporterBase, OTLPExporterError, } from '@opentelemetry/otlp-exporter-base'; +import { + createEmptyMetadata, + GrpcExporterTransport, +} from './grpc-exporter-transport'; +import { configureCompression, configureCredentials } from './util'; +import { ISerializer } from './serializers'; +import { IExporterTransport } from './exporter-transport'; /** * OTLP Exporter abstract base class @@ -35,59 +36,84 @@ import { export abstract class OTLPGRPCExporterNodeBase< ExportItem, ServiceRequest, + ServiceResponse, > extends OTLPExporterBase< OTLPGRPCExporterConfigNode, ExportItem, ServiceRequest > { grpcQueue: GRPCQueueItem[] = []; - metadata?: Metadata; - serviceClient?: ServiceClient = undefined; - private _send!: Function; - compression: GrpcCompressionAlgorithm; + compression: CompressionAlgorithm; + private _transport: IExporterTransport; + private _serializer: ISerializer; - constructor(config: OTLPGRPCExporterConfigNode = {}) { + constructor( + config: OTLPGRPCExporterConfigNode = {}, + signalSpecificMetadata: Record, + grpcName: string, + grpcPath: string, + serializer: ISerializer + ) { super(config); + this._serializer = serializer; if (config.headers) { diag.warn('Headers cannot be set when using grpc'); } - const headers = baggageUtils.parseKeyPairsIntoRecord( + const nonSignalSpecificMetadata = baggageUtils.parseKeyPairsIntoRecord( getEnv().OTEL_EXPORTER_OTLP_HEADERS ); - this.metadata = config.metadata || new Metadata(); - for (const [k, v] of Object.entries(headers)) { - this.metadata.set(k, v); + const rawMetadata = Object.assign( + {}, + nonSignalSpecificMetadata, + signalSpecificMetadata + ); + + let credentialProvider = () => { + return configureCredentials(undefined, this.getUrlFromConfig(config)); + }; + + if (config.credentials != null) { + const credentials = config.credentials; + credentialProvider = () => { + return credentials; + }; } - this.compression = configureCompression(config.compression); - } - private _sendPromise( - objects: ExportItem[], - onSuccess: () => void, - onError: (error: OTLPExporterError) => void - ): void { - const promise = new Promise((resolve, reject) => { - this._send(this, objects, resolve, reject); - }).then(onSuccess, onError); + // Ensure we don't modify the original. + const configMetadata = config.metadata?.clone(); + const metadataProvider = () => { + const metadata = configMetadata ?? createEmptyMetadata(); + for (const [key, value] of Object.entries(rawMetadata)) { + // only override with env var data if the key has no values. + // not using Metadata.merge() as it will keep both values. + if (metadata.get(key).length < 1) { + metadata.set(key, value); + } + } - this._sendingPromises.push(promise); - const popPromise = () => { - const index = this._sendingPromises.indexOf(promise); - this._sendingPromises.splice(index, 1); + return metadata; }; - promise.then(popPromise, popPromise); - } - onInit(config: OTLPGRPCExporterConfigNode): void { - // defer to next tick and lazy load to avoid loading grpc too early - // and making this impossible to be instrumented - setImmediate(() => { - // eslint-disable-next-line @typescript-eslint/no-var-requires - const { onInit } = require('./util'); - onInit(this, config); + this.compression = configureCompression(config.compression); + this._transport = new GrpcExporterTransport({ + address: this.getDefaultUrl(config), + compression: this.compression, + credentials: credentialProvider, + grpcName: grpcName, + grpcPath: grpcPath, + metadata: metadataProvider, + timeoutMillis: this.timeoutMillis, }); } + onInit() { + // Intentionally left empty; nothing to do. + } + + override onShutdown() { + this._transport.shutdown(); + } + send( objects: ExportItem[], onSuccess: () => void, @@ -97,28 +123,33 @@ export abstract class OTLPGRPCExporterNodeBase< diag.debug('Shutdown already started. Cannot send objects'); return; } - if (!this._send) { - // defer to next tick and lazy load to avoid loading grpc too early - // and making this impossible to be instrumented - setImmediate(() => { - // eslint-disable-next-line @typescript-eslint/no-var-requires - const { send } = require('./util'); - this._send = send; - - this._sendPromise(objects, onSuccess, onError); - }); - } else { - this._sendPromise(objects, onSuccess, onError); - } - } - onShutdown(): void { - if (this.serviceClient) { - this.serviceClient.close(); + const converted = this.convert(objects); + const data = this._serializer.serializeRequest(converted); + + if (data == null) { + onError(new Error('Could not serialize message')); + return; } + + const promise = this._transport.send(data).then(response => { + if (response.status === 'success') { + onSuccess(); + return; + } + if (response.status === 'failure' && response.error) { + onError(response.error); + } + onError(new OTLPExporterError('Export failed with unknown error')); + }, onError); + + this._sendingPromises.push(promise); + const popPromise = () => { + const index = this._sendingPromises.indexOf(promise); + this._sendingPromises.splice(index, 1); + }; + promise.then(popPromise, popPromise); } - abstract getServiceProtoPath(): string; - abstract getServiceClientType(): ServiceClientType; abstract getUrlFromConfig(config: OTLPGRPCExporterConfigNode): string; } diff --git a/experimental/packages/otlp-grpc-exporter-base/src/TraceExportServiceClient.ts b/experimental/packages/otlp-grpc-exporter-base/src/TraceExportServiceClient.ts deleted file mode 100644 index d332e4f4daa..00000000000 --- a/experimental/packages/otlp-grpc-exporter-base/src/TraceExportServiceClient.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import * as root from './generated/root'; -import * as grpc from '@grpc/grpc-js'; -import { - IExportTraceServiceRequest, - IExportTraceServiceResponse, -} from '@opentelemetry/otlp-transformer'; -import { ExportType } from './internal-types'; - -const responseType = root.opentelemetry.proto.collector.trace.v1 - .ExportTraceServiceResponse as ExportType; - -const requestType = root.opentelemetry.proto.collector.trace.v1 - .ExportTraceServiceRequest as ExportType; - -const traceServiceDefinition = { - export: { - path: '/opentelemetry.proto.collector.trace.v1.TraceService/Export', - requestStream: false, - responseStream: false, - requestSerialize: (arg: IExportTraceServiceRequest) => { - return Buffer.from(requestType.encode(arg).finish()); - }, - requestDeserialize: (arg: Buffer) => { - return requestType.decode(arg); - }, - responseSerialize: (arg: IExportTraceServiceResponse) => { - return Buffer.from(responseType.encode(arg).finish()); - }, - responseDeserialize: (arg: Buffer) => { - return responseType.decode(arg); - }, - }, -}; - -// Creates a new instance of a gRPC service client for exporting OTLP traces -export const TraceExportServiceClient: grpc.ServiceClientConstructor = - grpc.makeGenericClientConstructor( - traceServiceDefinition, - 'TraceExportService' - ); diff --git a/experimental/packages/otlp-grpc-exporter-base/src/create-service-client-constructor.ts b/experimental/packages/otlp-grpc-exporter-base/src/create-service-client-constructor.ts new file mode 100644 index 00000000000..9447e7b786b --- /dev/null +++ b/experimental/packages/otlp-grpc-exporter-base/src/create-service-client-constructor.ts @@ -0,0 +1,51 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as grpc from '@grpc/grpc-js'; + +/** + * Creates a unary service client constructor that, when instantiated, does not serialize/deserialize anything. + * Allows for passing in {@link Buffer} directly, serialization can be handled via protobufjs or custom implementations. + * + * @param path service path + * @param name service name + */ +export function createServiceClientConstructor( + path: string, + name: string +): grpc.ServiceClientConstructor { + const serviceDefinition = { + export: { + path: path, + requestStream: false, + responseStream: false, + requestSerialize: (arg: Buffer) => { + return arg; + }, + requestDeserialize: (arg: Buffer) => { + return arg; + }, + responseSerialize: (arg: Buffer) => { + return arg; + }, + responseDeserialize: (arg: Buffer) => { + return arg; + }, + }, + }; + + return grpc.makeGenericClientConstructor(serviceDefinition, name); +} diff --git a/experimental/packages/otlp-grpc-exporter-base/src/export-response.ts b/experimental/packages/otlp-grpc-exporter-base/src/export-response.ts new file mode 100644 index 00000000000..c13af631e13 --- /dev/null +++ b/experimental/packages/otlp-grpc-exporter-base/src/export-response.ts @@ -0,0 +1,27 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface ExportResponseSuccess { + status: 'success'; + data?: Uint8Array; +} + +export interface ExportResponseFailure { + status: 'failure'; + error: Error; +} + +export type ExportResponse = ExportResponseSuccess | ExportResponseFailure; diff --git a/experimental/packages/otlp-grpc-exporter-base/src/exporter-transport.ts b/experimental/packages/otlp-grpc-exporter-base/src/exporter-transport.ts new file mode 100644 index 00000000000..bb9deac834d --- /dev/null +++ b/experimental/packages/otlp-grpc-exporter-base/src/exporter-transport.ts @@ -0,0 +1,22 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ExportResponse } from './export-response'; + +export interface IExporterTransport { + send(data: Uint8Array): Promise; + shutdown(): void; +} diff --git a/experimental/packages/otlp-grpc-exporter-base/src/grpc-exporter-transport.ts b/experimental/packages/otlp-grpc-exporter-base/src/grpc-exporter-transport.ts new file mode 100644 index 00000000000..77038648ce8 --- /dev/null +++ b/experimental/packages/otlp-grpc-exporter-base/src/grpc-exporter-transport.ts @@ -0,0 +1,173 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// NOTE: do not change these type imports to actual imports. Doing so WILL break `@opentelemetry/instrumentation-http`, +// as they'd be imported before the http/https modules can be wrapped. +import type { Metadata, ServiceError, ChannelCredentials } from '@grpc/grpc-js'; +import { ExportResponse } from './export-response'; +import { IExporterTransport } from './exporter-transport'; + +// values taken from '@grpc/grpc-js` so that we don't need to require/import it. +const GRPC_COMPRESSION_NONE = 0; +const GRPC_COMPRESSION_GZIP = 2; + +function toGrpcCompression(compression: 'gzip' | 'none'): number { + return compression === 'gzip' ? GRPC_COMPRESSION_GZIP : GRPC_COMPRESSION_NONE; +} + +export function createInsecureCredentials(): ChannelCredentials { + // Lazy-load so that we don't need to require/import '@grpc/grpc-js' before it can be wrapped by instrumentation. + const { + credentials, + // eslint-disable-next-line @typescript-eslint/no-var-requires + } = require('@grpc/grpc-js'); + return credentials.createInsecure(); +} + +export function createSslCredentials( + rootCert?: Buffer, + privateKey?: Buffer, + certChain?: Buffer +): ChannelCredentials { + // Lazy-load so that we don't need to require/import '@grpc/grpc-js' before it can be wrapped by instrumentation. + const { + credentials, + // eslint-disable-next-line @typescript-eslint/no-var-requires + } = require('@grpc/grpc-js'); + return credentials.createSsl(rootCert, privateKey, certChain); +} + +export function createEmptyMetadata(): Metadata { + // Lazy-load so that we don't need to require/import '@grpc/grpc-js' before it can be wrapped by instrumentation. + const { + Metadata, + // eslint-disable-next-line @typescript-eslint/no-var-requires + } = require('@grpc/grpc-js'); + return new Metadata(); +} + +export interface GrpcExporterTransportParameters { + grpcPath: string; + grpcName: string; + address: string; + /** + * NOTE: Ensure that you're only importing/requiring gRPC inside the function providing the channel credentials, + * otherwise, gRPC and http/https instrumentations may break. + * + * For common cases, you can avoid to import/require gRPC your function by using + * - {@link createSslCredentials} + * - {@link createInsecureCredentials} + */ + credentials: () => ChannelCredentials; + /** + * NOTE: Ensure that you're only importing/requiring gRPC inside the function providing the metadata, + * otherwise, gRPC and http/https instrumentations may break. + * + * To avoid having to import/require gRPC from your function to create a new Metadata object, + * use {@link createEmptyMetadata} + */ + metadata: () => Metadata; + compression: 'gzip' | 'none'; + timeoutMillis: number; +} + +export class GrpcExporterTransport implements IExporterTransport { + private _client?: any; + private _metadata?: Metadata; + + constructor(private _parameters: GrpcExporterTransportParameters) {} + + shutdown() { + this._client?.shutdown(); + } + + send(data: Uint8Array): Promise { + // We need to make a for gRPC + const buffer = Buffer.from(data); + + if (this._client == null) { + // Lazy require to ensure that grpc is not loaded before instrumentations can wrap it + const { + createServiceClientConstructor, + // eslint-disable-next-line @typescript-eslint/no-var-requires + } = require('./create-service-client-constructor'); + + try { + this._metadata = this._parameters.metadata(); + } catch (error) { + return Promise.resolve({ + status: 'failure', + error: error, + }); + } + + const clientConstructor = createServiceClientConstructor( + this._parameters.grpcPath, + this._parameters.grpcName + ); + + try { + this._client = new clientConstructor( + this._parameters.address, + this._parameters.credentials(), + { + 'grpc.default_compression_algorithm': toGrpcCompression( + this._parameters.compression + ), + } + ); + } catch (error) { + return Promise.resolve({ + status: 'failure', + error: error, + }); + } + } + + return new Promise(resolve => { + // this will always be defined + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const deadline = Date.now() + this._parameters.timeoutMillis; + + // this should never happen + if (this._metadata == null) { + return resolve({ + error: new Error('metadata was null'), + status: 'failure', + }); + } + + this._client.export( + buffer, + this._metadata, + { deadline: deadline }, + (err: ServiceError, response: Buffer) => { + if (err) { + resolve({ + status: 'failure', + error: err, + }); + } else { + resolve({ + data: response, + status: 'success', + }); + } + } + ); + }); + } +} diff --git a/experimental/packages/otlp-grpc-exporter-base/src/index.ts b/experimental/packages/otlp-grpc-exporter-base/src/index.ts index 2669033a460..0e7f487e2be 100644 --- a/experimental/packages/otlp-grpc-exporter-base/src/index.ts +++ b/experimental/packages/otlp-grpc-exporter-base/src/index.ts @@ -14,10 +14,12 @@ * limitations under the License. */ -export * from './OTLPGRPCExporterNodeBase'; -export { ServiceClientType, OTLPGRPCExporterConfigNode } from './types'; +export { OTLPGRPCExporterNodeBase } from './OTLPGRPCExporterNodeBase'; +export { OTLPGRPCExporterConfigNode } from './types'; +export { DEFAULT_COLLECTOR_URL, validateAndNormalizeUrl } from './util'; export { - DEFAULT_COLLECTOR_URL, - validateAndNormalizeUrl, - GrpcCompressionAlgorithm, -} from './util'; + MetricsSerializer, + TraceSerializer, + LogsSerializer, + ISerializer, +} from './serializers'; diff --git a/experimental/packages/otlp-grpc-exporter-base/src/serializers.ts b/experimental/packages/otlp-grpc-exporter-base/src/serializers.ts new file mode 100644 index 00000000000..3e8b22f6053 --- /dev/null +++ b/experimental/packages/otlp-grpc-exporter-base/src/serializers.ts @@ -0,0 +1,88 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as root from './generated/root'; +import { + IExportLogsServiceRequest, + IExportLogsServiceResponse, + IExportMetricsServiceRequest, + IExportMetricsServiceResponse, + IExportTraceServiceRequest, + IExportTraceServiceResponse, +} from '@opentelemetry/otlp-transformer'; +import { ExportType } from './internal-types'; + +const logsResponseType = root.opentelemetry.proto.collector.logs.v1 + .ExportLogsServiceResponse as ExportType; + +const logsRequestType = root.opentelemetry.proto.collector.logs.v1 + .ExportLogsServiceRequest as ExportType; + +const metricsResponseType = root.opentelemetry.proto.collector.metrics.v1 + .ExportMetricsServiceResponse as ExportType; + +const metricsRequestType = root.opentelemetry.proto.collector.metrics.v1 + .ExportMetricsServiceRequest as ExportType; + +const traceResponseType = root.opentelemetry.proto.collector.trace.v1 + .ExportTraceServiceResponse as ExportType; + +const traceRequestType = root.opentelemetry.proto.collector.trace.v1 + .ExportTraceServiceRequest as ExportType; + +/** + * Serializes and deserializes the OTLP request/response to and from {@link Uint8Array} + */ +export interface ISerializer { + serializeRequest(request: Request): Uint8Array | undefined; + deserializeResponse(data: Uint8Array): Response; +} + +export const LogsSerializer: ISerializer< + IExportLogsServiceRequest, + IExportLogsServiceResponse +> = { + serializeRequest: (arg: IExportLogsServiceRequest) => { + return Buffer.from(logsRequestType.encode(arg).finish()); + }, + deserializeResponse: (arg: Buffer) => { + return logsResponseType.decode(arg); + }, +}; + +export const TraceSerializer: ISerializer< + IExportTraceServiceRequest, + IExportTraceServiceResponse +> = { + serializeRequest: (arg: IExportTraceServiceRequest) => { + return Buffer.from(traceRequestType.encode(arg).finish()); + }, + deserializeResponse: (arg: Buffer) => { + return traceResponseType.decode(arg); + }, +}; + +export const MetricsSerializer: ISerializer< + IExportMetricsServiceRequest, + IExportMetricsServiceResponse +> = { + serializeRequest: (arg: IExportMetricsServiceRequest) => { + return Buffer.from(metricsRequestType.encode(arg).finish()); + }, + deserializeResponse: (arg: Buffer) => { + return metricsResponseType.decode(arg); + }, +}; diff --git a/experimental/packages/otlp-grpc-exporter-base/src/types.ts b/experimental/packages/otlp-grpc-exporter-base/src/types.ts index 7ecedff1baf..43caad1371a 100644 --- a/experimental/packages/otlp-grpc-exporter-base/src/types.ts +++ b/experimental/packages/otlp-grpc-exporter-base/src/types.ts @@ -14,7 +14,8 @@ * limitations under the License. */ -import * as grpc from '@grpc/grpc-js'; +import type { ChannelCredentials, Metadata } from '@grpc/grpc-js'; + import { CompressionAlgorithm, OTLPExporterConfigBase, @@ -31,31 +32,11 @@ export interface GRPCQueueItem { onError: (error: OTLPExporterError) => void; } -/** - * Service Client for sending spans/metrics/logs - */ -export interface ServiceClient { - export: ( - request: any, - metadata: grpc.Metadata, - options: grpc.CallOptions, - callback: Function - ) => {}; - - close(): void; -} - /** * OTLP Exporter Config for Node */ export interface OTLPGRPCExporterConfigNode extends OTLPExporterConfigBase { - credentials?: grpc.ChannelCredentials; - metadata?: grpc.Metadata; + credentials?: ChannelCredentials; + metadata?: Metadata; compression?: CompressionAlgorithm; } - -export enum ServiceClientType { - SPANS, - METRICS, - LOGS, -} diff --git a/experimental/packages/otlp-grpc-exporter-base/src/util.ts b/experimental/packages/otlp-grpc-exporter-base/src/util.ts index 78809466c96..4386b341694 100644 --- a/experimental/packages/otlp-grpc-exporter-base/src/util.ts +++ b/experimental/packages/otlp-grpc-exporter-base/src/util.ts @@ -14,111 +14,23 @@ * limitations under the License. */ -import * as grpc from '@grpc/grpc-js'; import { diag } from '@opentelemetry/api'; -import { getEnv, globalErrorHandler } from '@opentelemetry/core'; +import { getEnv } from '@opentelemetry/core'; import * as path from 'path'; -import { OTLPGRPCExporterNodeBase } from './OTLPGRPCExporterNodeBase'; import { URL } from 'url'; import * as fs from 'fs'; +import { CompressionAlgorithm } from '@opentelemetry/otlp-exporter-base'; import { - GRPCQueueItem, - OTLPGRPCExporterConfigNode, - ServiceClientType, -} from './types'; -import { - ExportServiceError, - OTLPExporterError, - CompressionAlgorithm, -} from '@opentelemetry/otlp-exporter-base'; + createInsecureCredentials, + createSslCredentials, +} from './grpc-exporter-transport'; -import { MetricExportServiceClient } from './MetricsExportServiceClient'; -import { TraceExportServiceClient } from './TraceExportServiceClient'; -import { LogsExportServiceClient } from './LogsExportServiceClient'; +// NOTE: do not change these type imports to actual imports. Doing so WILL break `@opentelemetry/instrumentation-http`, +// as they'd be imported before the http/https modules can be wrapped. +import type { ChannelCredentials } from '@grpc/grpc-js'; export const DEFAULT_COLLECTOR_URL = 'http://localhost:4317'; -export function onInit( - collector: OTLPGRPCExporterNodeBase, - config: OTLPGRPCExporterConfigNode -): void { - collector.grpcQueue = []; - - const credentials: grpc.ChannelCredentials = configureSecurity( - config.credentials, - collector.getUrlFromConfig(config) - ); - - try { - if (collector.getServiceClientType() === ServiceClientType.SPANS) { - const client = new TraceExportServiceClient(collector.url, credentials, { - 'grpc.default_compression_algorithm': collector.compression.valueOf(), - }); - - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - collector.serviceClient = client; - } else if (collector.getServiceClientType() === ServiceClientType.METRICS) { - const client = new MetricExportServiceClient(collector.url, credentials, { - 'grpc.default_compression_algorithm': collector.compression.valueOf(), - }); - - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - collector.serviceClient = client; - } else if (collector.getServiceClientType() === ServiceClientType.LOGS) { - const client = new LogsExportServiceClient(collector.url, credentials, { - 'grpc.default_compression_algorithm': collector.compression.valueOf(), - }); - - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - collector.serviceClient = client; - } - } catch (err) { - globalErrorHandler(err); - } - if (collector.grpcQueue.length > 0) { - const queue = collector.grpcQueue.splice(0); - queue.forEach((item: GRPCQueueItem) => { - collector.send(item.objects, item.onSuccess, item.onError); - }); - } -} - -export function send( - collector: OTLPGRPCExporterNodeBase, - objects: ExportItem[], - onSuccess: () => void, - onError: (error: OTLPExporterError) => void -): void { - if (collector.serviceClient) { - const serviceRequest = collector.convert(objects); - const deadline = Date.now() + collector.timeoutMillis; - - collector.serviceClient.export( - serviceRequest, - collector.metadata || new grpc.Metadata(), - { deadline: deadline }, - (err: ExportServiceError) => { - if (err) { - diag.error('Service request', serviceRequest); - onError(err); - } else { - diag.debug('Objects sent'); - onSuccess(); - } - } - ); - } else { - collector.grpcQueue.push({ - objects, - onSuccess, - onError, - }); - } -} - export function validateAndNormalizeUrl(url: string): string { const hasProtocol = url.match(/^([\w]{1,8}):\/\//); if (!hasProtocol) { @@ -139,10 +51,10 @@ export function validateAndNormalizeUrl(url: string): string { return target.host; } -export function configureSecurity( - credentials: grpc.ChannelCredentials | undefined, +export function configureCredentials( + credentials: ChannelCredentials | undefined, endpoint: string -): grpc.ChannelCredentials { +): ChannelCredentials { let insecure: boolean; if (credentials) { @@ -159,9 +71,9 @@ export function configureSecurity( } if (insecure) { - return grpc.credentials.createInsecure(); + return createInsecureCredentials(); } else { - return useSecureConnection(); + return getCredentialsFromEnvironment(); } } @@ -177,16 +89,15 @@ function getSecurityFromEnv(): boolean { } } -export function useSecureConnection(): grpc.ChannelCredentials { - const rootCertPath = retrieveRootCert(); - const privateKeyPath = retrievePrivateKey(); - const certChainPath = retrieveCertChain(); +/** + * Exported for testing + */ +export function getCredentialsFromEnvironment(): ChannelCredentials { + const rootCert = retrieveRootCert(); + const privateKey = retrievePrivateKey(); + const certChain = retrieveCertChain(); - return grpc.credentials.createSsl( - rootCertPath, - privateKeyPath, - certChainPath - ); + return createSslCredentials(rootCert, privateKey, certChain); } function retrieveRootCert(): Buffer | undefined { @@ -240,36 +151,25 @@ function retrieveCertChain(): Buffer | undefined { } } -function toGrpcCompression( - compression: CompressionAlgorithm -): GrpcCompressionAlgorithm { - if (compression === CompressionAlgorithm.NONE) - return GrpcCompressionAlgorithm.NONE; - else if (compression === CompressionAlgorithm.GZIP) - return GrpcCompressionAlgorithm.GZIP; - return GrpcCompressionAlgorithm.NONE; -} - -/** - * These values are defined by grpc client - */ -export enum GrpcCompressionAlgorithm { - NONE = 0, - GZIP = 2, -} - export function configureCompression( compression: CompressionAlgorithm | undefined -): GrpcCompressionAlgorithm { - if (compression) { - return toGrpcCompression(compression); - } else { - const definedCompression = - getEnv().OTEL_EXPORTER_OTLP_TRACES_COMPRESSION || - getEnv().OTEL_EXPORTER_OTLP_COMPRESSION; +): CompressionAlgorithm { + if (compression != null) { + return compression; + } + + const envCompression = + getEnv().OTEL_EXPORTER_OTLP_TRACES_COMPRESSION || + getEnv().OTEL_EXPORTER_OTLP_COMPRESSION; - return definedCompression === 'gzip' - ? GrpcCompressionAlgorithm.GZIP - : GrpcCompressionAlgorithm.NONE; + if (envCompression === 'gzip') { + return CompressionAlgorithm.GZIP; + } else if (envCompression === 'none') { + return CompressionAlgorithm.NONE; } + + diag.warn( + 'Unknown compression "' + envCompression + '", falling back to "none"' + ); + return CompressionAlgorithm.NONE; } diff --git a/experimental/packages/otlp-grpc-exporter-base/test/OTLPGRPCExporterNodeBase.test.ts b/experimental/packages/otlp-grpc-exporter-base/test/OTLPGRPCExporterNodeBase.test.ts index ec28b53011d..f70ffd08cd3 100644 --- a/experimental/packages/otlp-grpc-exporter-base/test/OTLPGRPCExporterNodeBase.test.ts +++ b/experimental/packages/otlp-grpc-exporter-base/test/OTLPGRPCExporterNodeBase.test.ts @@ -17,55 +17,33 @@ import { ReadableSpan } from '@opentelemetry/sdk-trace-base'; import * as assert from 'assert'; import { OTLPGRPCExporterNodeBase } from '../src/OTLPGRPCExporterNodeBase'; -import { OTLPGRPCExporterConfigNode, ServiceClientType } from '../src/types'; +import { OTLPGRPCExporterConfigNode } from '../src/types'; import { mockedReadableSpan } from './traceHelper'; -import { OTLPExporterError } from '@opentelemetry/otlp-exporter-base'; +import { ExportResponse, ExportResponseSuccess } from '../src/export-response'; +import { IExporterTransport } from '../src/exporter-transport'; +import { ISerializer } from '../src'; +import sinon = require('sinon'); class MockCollectorExporter extends OTLPGRPCExporterNodeBase< ReadableSpan, - ReadableSpan[] + ReadableSpan[], + any > { - /** - * Callbacks passed to _send() - */ - sendCallbacks: { - onSuccess: () => void; - onError: (error: OTLPExporterError) => void; - }[] = []; - getDefaultUrl(config: OTLPGRPCExporterConfigNode): string { return ''; } - getDefaultServiceName(config: OTLPGRPCExporterConfigNode): string { - return ''; - } - convert(spans: ReadableSpan[]): ReadableSpan[] { return spans; } - getServiceClientType() { - return ServiceClientType.SPANS; - } - - getServiceProtoPath(): string { - return 'opentelemetry/proto/collector/trace/v1/trace_service.proto'; - } - getUrlFromConfig(config: OTLPGRPCExporterConfigNode): string { return ''; } } -// Mocked _send which just saves the callbacks for later -MockCollectorExporter.prototype['_send'] = function _sendMock( - self: MockCollectorExporter, - objects: ReadableSpan[], - onSuccess: () => void, - onError: (error: OTLPExporterError) => void -): void { - self.sendCallbacks.push({ onSuccess, onError }); +const successfulResponse: ExportResponseSuccess = { + status: 'success', }; describe('OTLPGRPCExporterNodeBase', () => { @@ -73,12 +51,54 @@ describe('OTLPGRPCExporterNodeBase', () => { const concurrencyLimit = 5; beforeEach(done => { - exporter = new MockCollectorExporter({ concurrencyLimit }); + const transportStubs = { + // make transport succeed + send: sinon.stub().resolves(successfulResponse), + shutdown: sinon.stub(), + }; + const mockTransport = transportStubs; + const signalSpecificMetadata: Record = { + key: 'signal-specific-metadata', + }; + + const serializerStubs = { + serializeRequest: sinon.stub().resolves(Buffer.from([1, 2, 3])), + deserializeResponse: sinon + .stub() + .resolves({ responseKey: 'responseValue' }), + }; + + const serializer = >serializerStubs; + + exporter = new MockCollectorExporter( + { concurrencyLimit }, + signalSpecificMetadata, + 'grpcName', + 'grpcPath', + serializer + ); + + exporter['_transport'] = mockTransport; done(); }); + afterEach(function () { + sinon.restore(); + }); + describe('export', () => { it('should export requests concurrently', async () => { + const sendResolveFunctions: ((response: ExportResponse) => void)[] = []; + const transportStubs = { + send: sinon.stub().returns( + new Promise(resolve => { + sendResolveFunctions.push(resolve); + }) + ), + shutdown: sinon.stub(), + }; + exporter['_transport'] = transportStubs; + const spans = [Object.assign({}, mockedReadableSpan)]; const numToExport = concurrencyLimit; @@ -89,7 +109,7 @@ describe('OTLPGRPCExporterNodeBase', () => { assert.strictEqual(exporter['_sendingPromises'].length, numToExport); const promisesAllDone = Promise.all(exporter['_sendingPromises']); // Mock that all requests finish sending - exporter.sendCallbacks.forEach(({ onSuccess }) => onSuccess()); + sendResolveFunctions.forEach(resolve => resolve(successfulResponse)); // All finished promises should be popped off await promisesAllDone; @@ -97,6 +117,17 @@ describe('OTLPGRPCExporterNodeBase', () => { }); it('should drop new export requests when already sending at concurrencyLimit', async () => { + const sendResolveFunctions: ((response: ExportResponse) => void)[] = []; + const transportStubs = { + send: sinon.stub().returns( + new Promise(resolve => { + sendResolveFunctions.push(resolve); + }) + ), + shutdown: sinon.stub(), + }; + exporter['_transport'] = transportStubs; + const spans = [Object.assign({}, mockedReadableSpan)]; const numToExport = concurrencyLimit + 5; @@ -107,7 +138,7 @@ describe('OTLPGRPCExporterNodeBase', () => { assert.strictEqual(exporter['_sendingPromises'].length, concurrencyLimit); const promisesAllDone = Promise.all(exporter['_sendingPromises']); // Mock that all requests finish sending - exporter.sendCallbacks.forEach(({ onSuccess }) => onSuccess()); + sendResolveFunctions.forEach(resolve => resolve(successfulResponse)); // All finished promises should be popped off await promisesAllDone; @@ -115,45 +146,65 @@ describe('OTLPGRPCExporterNodeBase', () => { }); it('should pop export request promises even if they failed', async () => { + const sendRejectFunctions: ((error: Error) => void)[] = []; + const transportStubs = { + send: sinon.stub().returns( + new Promise((_, reject) => { + sendRejectFunctions.push(reject); + }) + ), + shutdown: sinon.stub(), + }; + exporter['_transport'] = transportStubs; + const spans = [Object.assign({}, mockedReadableSpan)]; exporter.export(spans, () => {}); assert.strictEqual(exporter['_sendingPromises'].length, 1); const promisesAllDone = Promise.all(exporter['_sendingPromises']); // Mock that all requests fail sending - exporter.sendCallbacks.forEach(({ onError }) => - onError(new Error('Failed to send!!')) - ); + sendRejectFunctions.forEach(reject => reject(new Error('export failed'))); // All finished promises should be popped off await promisesAllDone; assert.strictEqual(exporter['_sendingPromises'].length, 0); }); - it('should pop export request promises even if success callback throws error', async () => { - const spans = [Object.assign({}, mockedReadableSpan)]; + it('should pop export request promises even if resolve throws error', async () => { + const transportStubs = { + send: sinon.stub().returns( + new Promise(_ => { + throw new Error('this failed'); + }) + ), + shutdown: sinon.stub(), + }; + exporter['_transport'] = transportStubs; - exporter['_sendPromise']( - spans, - () => { - throw new Error('Oops'); - }, - () => {} - ); + const spans = [Object.assign({}, mockedReadableSpan)]; + exporter.export(spans, () => {}); assert.strictEqual(exporter['_sendingPromises'].length, 1); + const promisesAllDone = Promise.all(exporter['_sendingPromises']) // catch expected unhandled exception .catch(() => {}); - // Mock that the request finishes sending - exporter.sendCallbacks.forEach(({ onSuccess }) => { - onSuccess(); - }); - // All finished promises should be popped off await promisesAllDone; assert.strictEqual(exporter['_sendingPromises'].length, 0); }); }); + + describe('shutdown', function () { + it('calls shutdown on transport', function () { + const transportStubs = { + send: sinon.stub(), + shutdown: sinon.stub(), + }; + exporter['_transport'] = transportStubs; + exporter.shutdown(); + sinon.assert.calledOnce(transportStubs.shutdown); + }); + }); }); diff --git a/experimental/packages/otlp-grpc-exporter-base/test/grpc-exporter-transport.test.ts b/experimental/packages/otlp-grpc-exporter-base/test/grpc-exporter-transport.test.ts new file mode 100644 index 00000000000..965c01607ce --- /dev/null +++ b/experimental/packages/otlp-grpc-exporter-base/test/grpc-exporter-transport.test.ts @@ -0,0 +1,311 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + createEmptyMetadata, + createInsecureCredentials, + createSslCredentials, + GrpcExporterTransport, + GrpcExporterTransportParameters, +} from '../src/grpc-exporter-transport'; +import * as assert from 'assert'; +import * as fs from 'fs'; +import sinon = require('sinon'); +import { Metadata, Server, ServerCredentials } from '@grpc/grpc-js'; +import { types } from 'util'; +import { + ExportResponseFailure, + ExportResponseSuccess, +} from '../src/export-response'; + +const testServiceDefinition = { + export: { + path: '/test/Export', + requestStream: false, + responseStream: false, + requestSerialize: (arg: Buffer) => { + return arg; + }, + requestDeserialize: (arg: Buffer) => { + return arg; + }, + responseSerialize: (arg: Buffer) => { + return arg; + }, + responseDeserialize: (arg: Buffer) => { + return arg; + }, + }, +}; + +const simpleClientConfig: GrpcExporterTransportParameters = { + metadata: () => { + const metadata = createEmptyMetadata(); + metadata.set('foo', 'bar'); + return metadata; + }, + timeoutMillis: 100, + grpcPath: '/test/Export', + grpcName: 'name', + credentials: createInsecureCredentials, + compression: 'none', + address: 'localhost:1234', +}; + +interface ExportedData { + request: Buffer; + metadata: Metadata; +} + +interface ServerTestContext { + requests: ExportedData[]; + serverResponseProvider: () => { error: Error | null; buffer?: Buffer }; +} + +/** + * Starts a customizable server that saves all responses to context.responses + * Returns data as defined in context.ServerResponseProvider + * + * @return shutdown handle, needs to be called to ensure that mocha exits + * @param context context for storing responses and to define server behavior. + */ +function startServer(context: ServerTestContext): Promise<() => void> { + const server = new Server(); + server.addService(testServiceDefinition, { + export: (data: ExportedData, callback: any) => { + context.requests.push(data); + const response = context.serverResponseProvider(); + callback(response.error, response.buffer); + }, + }); + + return new Promise<() => void>((resolve, reject) => { + server.bindAsync( + 'localhost:1234', + ServerCredentials.createInsecure(), + (error, port) => { + server.start(); + if (error != null) { + reject(error); + } + resolve(() => { + server.forceShutdown(); + }); + } + ); + }); +} + +describe('GrpcExporterTransport', function () { + describe('utilities', function () { + describe('createEmptyMetadata', function () { + it('returns new empty Metadata', function () { + const metadata = createEmptyMetadata(); + assert.strictEqual(Object.keys(metadata.getMap()).length, 0); + }); + }); + + describe('createInsecureCredentials', function () { + it('creates insecure grpc credentials', function () { + const credentials = createInsecureCredentials(); + assert.ok(!credentials._isSecure()); + }); + }); + + describe('createSslCredentials', function () { + it('creates SSL grpc credentials', function () { + const credentials = createSslCredentials( + Buffer.from(fs.readFileSync('./test/certs/ca.crt')), + Buffer.from(fs.readFileSync('./test/certs/client.key')), + Buffer.from(fs.readFileSync('./test/certs/client.crt')) + ); + assert.ok(credentials._isSecure()); + }); + }); + }); + describe('shutdown', function () { + afterEach(function () { + sinon.restore(); + }); + it('before send() does not error', function () { + const transport = new GrpcExporterTransport(simpleClientConfig); + transport.shutdown(); + + // no assertions, just checking that it does not throw any errors. + }); + + it('calls client shutdown if client is defined', function () { + // arrange + const transport = new GrpcExporterTransport({ + metadata: createEmptyMetadata, + timeoutMillis: 100, + grpcPath: 'path', + grpcName: 'name', + credentials: createInsecureCredentials, + compression: 'gzip', + address: 'localhost:1234', + }); + const shutdownStub = sinon.stub(); + transport['_client'] = { + shutdown: shutdownStub, + }; + + // act + transport.shutdown(); + + // assert + sinon.assert.calledOnce(shutdownStub); + }); + }); + describe('send', function () { + let shutdownHandle: () => void | undefined; + const serverTestContext: ServerTestContext = { + requests: [], + serverResponseProvider: () => { + return { error: null, buffer: Buffer.from([]) }; + }, + }; + + beforeEach(async function () { + shutdownHandle = await startServer(serverTestContext); + }); + + afterEach(function () { + shutdownHandle(); + + // clear context + serverTestContext.requests = []; + serverTestContext.serverResponseProvider = () => { + return { error: null, buffer: Buffer.from([]) }; + }; + }); + + it('sends data', async function () { + const transport = new GrpcExporterTransport(simpleClientConfig); + + const result = (await transport.send( + Buffer.from([1, 2, 3]) + )) as ExportResponseSuccess; + + assert.strictEqual(result.status, 'success'); + assert.deepEqual(result.data, Buffer.from([])); + assert.strictEqual(serverTestContext.requests.length, 1); + assert.deepEqual( + serverTestContext.requests[0].request, + Buffer.from([1, 2, 3]) + ); + assert.deepEqual( + serverTestContext.requests[0].metadata.get('foo'), + simpleClientConfig.metadata().get('foo') + ); + }); + + it('forwards response', async function () { + const expectedResponseData = Buffer.from([1, 2, 3]); + serverTestContext.serverResponseProvider = () => { + return { + buffer: expectedResponseData, + error: null, + }; + }; + const transport = new GrpcExporterTransport(simpleClientConfig); + + const result = (await transport.send( + Buffer.from([]) + )) as ExportResponseSuccess; + + assert.strictEqual(result.status, 'success'); + assert.deepEqual(result.data, expectedResponseData); + }); + + it('forwards handled server error as failure', async function () { + serverTestContext.serverResponseProvider = () => { + return { + buffer: Buffer.from([]), + error: new Error('handled server error'), + }; + }; + const transport = new GrpcExporterTransport(simpleClientConfig); + + const result = (await transport.send( + Buffer.from([]) + )) as ExportResponseFailure; + + assert.strictEqual(result.status, 'failure'); + assert.ok(types.isNativeError(result.error)); + }); + + it('forwards unhandled server error as failure', async function () { + serverTestContext.serverResponseProvider = () => { + throw new Error('unhandled server error'); + }; + const transport = new GrpcExporterTransport(simpleClientConfig); + + const result = (await transport.send( + Buffer.from([]) + )) as ExportResponseFailure; + assert.strictEqual(result.status, 'failure'); + assert.ok(types.isNativeError(result.error)); + }); + + it('forwards metadataProvider error as failure', async function () { + const expectedError = new Error('metadata provider error'); + const config = Object.assign({}, simpleClientConfig); + config.metadata = () => { + throw expectedError; + }; + + const transport = new GrpcExporterTransport(config); + + const result = (await transport.send( + Buffer.from([]) + )) as ExportResponseFailure; + assert.strictEqual(result.status, 'failure'); + assert.strictEqual(result.error, expectedError); + }); + + it('forwards metadataProvider returns null value as failure', async function () { + const expectedError = new Error('metadata was null'); + const config = Object.assign({}, simpleClientConfig); + config.metadata = () => { + return null as unknown as Metadata; + }; + + const transport = new GrpcExporterTransport(config); + + const result = (await transport.send( + Buffer.from([]) + )) as ExportResponseFailure; + assert.strictEqual(result.status, 'failure'); + assert.deepEqual(result.error, expectedError); + }); + + it('forwards credential error as failure', async function () { + const expectedError = new Error('credential provider error'); + const config = Object.assign({}, simpleClientConfig); + config.credentials = () => { + throw expectedError; + }; + + const transport = new GrpcExporterTransport(config); + + const result = (await transport.send( + Buffer.from([]) + )) as ExportResponseFailure; + assert.strictEqual(result.status, 'failure'); + assert.strictEqual(result.error, expectedError); + }); + }); +}); diff --git a/experimental/packages/otlp-grpc-exporter-base/test/util.test.ts b/experimental/packages/otlp-grpc-exporter-base/test/util.test.ts index 967ece67169..1da067b559c 100644 --- a/experimental/packages/otlp-grpc-exporter-base/test/util.test.ts +++ b/experimental/packages/otlp-grpc-exporter-base/test/util.test.ts @@ -22,9 +22,8 @@ import * as grpc from '@grpc/grpc-js'; import { validateAndNormalizeUrl, configureCompression, - GrpcCompressionAlgorithm, - configureSecurity, - useSecureConnection, + configureCredentials, + getCredentialsFromEnvironment, DEFAULT_COLLECTOR_URL, } from '../src/util'; import { CompressionAlgorithm } from '@opentelemetry/otlp-exporter-base'; @@ -94,15 +93,15 @@ describe('validateAndNormalizeUrl()', () => { }); }); -describe('utils - configureSecurity', () => { +describe('utils - configureCredentials', () => { const envSource = process.env; it('should return insecure channel when using all defaults', () => { - const credentials = configureSecurity(undefined, DEFAULT_COLLECTOR_URL); + const credentials = configureCredentials(undefined, DEFAULT_COLLECTOR_URL); assert.ok(credentials._isSecure() === false); }); it('should return user defined channel credentials', () => { const userDefinedCredentials = grpc.credentials.createSsl(); - const credentials = configureSecurity( + const credentials = configureCredentials( userDefinedCredentials, 'http://foo.bar' ); @@ -112,40 +111,40 @@ describe('utils - configureSecurity', () => { }); it('should return secure channel when endpoint contains https scheme - no matter insecure env settings,', () => { envSource.OTEL_EXPORTER_OTLP_TRACES_INSECURE = 'true'; - const credentials = configureSecurity(undefined, 'https://foo.bar'); + const credentials = configureCredentials(undefined, 'https://foo.bar'); assert.ok(credentials._isSecure() === true); delete envSource.OTEL_EXPORTER_OTLP_TRACES_INSECURE; }); it('should return insecure channel when endpoint contains http scheme and no insecure env settings', () => { - const credentials = configureSecurity(undefined, 'http://foo.bar'); + const credentials = configureCredentials(undefined, 'http://foo.bar'); assert.ok(credentials._isSecure() === false); }); it('should return secure channel when endpoint does not contain scheme and no insecure env settings', () => { - const credentials = configureSecurity(undefined, 'foo.bar'); + const credentials = configureCredentials(undefined, 'foo.bar'); assert.ok(credentials._isSecure() === true); }); it('should return insecure channel when endpoint contains http scheme and insecure env set to false', () => { envSource.OTEL_EXPORTER_OTLP_TRACES_INSECURE = 'false'; - const credentials = configureSecurity(undefined, 'http://foo.bar'); + const credentials = configureCredentials(undefined, 'http://foo.bar'); assert.ok(credentials._isSecure() === false); delete envSource.OTEL_EXPORTER_OTLP_TRACES_INSECURE; }); it('should return insecure channel when endpoint contains http scheme and insecure env set to true', () => { envSource.OTEL_EXPORTER_OTLP_INSECURE = 'true'; - const credentials = configureSecurity(undefined, 'http://localhost'); + const credentials = configureCredentials(undefined, 'http://localhost'); assert.ok(credentials._isSecure() === false); delete envSource.OTEL_EXPORTER_OTLP_INSECURE; }); it('should return secure channel when endpoint does not contain scheme and insecure env set to false', () => { envSource.OTEL_EXPORTER_OTLP_TRACES_INSECURE = 'false'; - const credentials = configureSecurity(undefined, 'foo.bar'); + const credentials = configureCredentials(undefined, 'foo.bar'); assert.ok(credentials._isSecure() === true); delete envSource.OTEL_EXPORTER_OTLP_TRACES_INSECURE; }); it('should return insecure channel when endpoint does not contain scheme and insecure env set to true', () => { envSource.OTEL_EXPORTER_OTLP_INSECURE = 'true'; - const credentials = configureSecurity(undefined, 'foo.bar'); + const credentials = configureCredentials(undefined, 'foo.bar'); assert.ok(credentials._isSecure() === false); delete envSource.OTEL_EXPORTER_OTLP_INSECURE; }); @@ -159,7 +158,7 @@ describe('useSecureConnection', () => { envSource.OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE = './test/certs/client.crt'; - const credentials = useSecureConnection(); + const credentials = getCredentialsFromEnvironment(); assert.ok(credentials._isSecure() === true); delete envSource.OTEL_EXPORTER_OTLP_CERTIFICATE; @@ -168,14 +167,14 @@ describe('useSecureConnection', () => { }); it('should return secure connection using only root certificate', () => { envSource.OTEL_EXPORTER_OTLP_CERTIFICATE = './test/certs/ca.crt'; - const credentials = useSecureConnection(); + const credentials = getCredentialsFromEnvironment(); assert.ok(credentials._isSecure() === true); delete envSource.OTEL_EXPORTER_OTLP_CERTIFICATE; }); it('should warn user when file cannot be read and use default root certificate', () => { envSource.OTEL_EXPORTER_OTLP_CERTIFICATE = './wrongpath/test/certs/ca.crt'; const diagWarn = sinon.stub(diag, 'warn'); - const credentials = useSecureConnection(); + const credentials = getCredentialsFromEnvironment(); const args = diagWarn.args[0]; assert.strictEqual(args[0], 'Failed to read root certificate file'); @@ -193,14 +192,14 @@ describe('configureCompression', () => { const compression = CompressionAlgorithm.NONE; assert.strictEqual( configureCompression(compression), - GrpcCompressionAlgorithm.NONE + CompressionAlgorithm.NONE ); }); it('should return gzip compression defined via env', () => { envSource.OTEL_EXPORTER_OTLP_TRACES_COMPRESSION = 'gzip'; assert.strictEqual( configureCompression(undefined), - GrpcCompressionAlgorithm.GZIP + CompressionAlgorithm.GZIP ); delete envSource.OTEL_EXPORTER_OTLP_TRACES_COMPRESSION; }); @@ -208,14 +207,14 @@ describe('configureCompression', () => { envSource.OTEL_EXPORTER_OTLP_TRACES_COMPRESSION = 'none'; assert.strictEqual( configureCompression(undefined), - GrpcCompressionAlgorithm.NONE + CompressionAlgorithm.NONE ); delete envSource.OTEL_EXPORTER_OTLP_TRACES_COMPRESSION; }); it('should return none for compression when no compression is set', () => { assert.strictEqual( configureCompression(undefined), - GrpcCompressionAlgorithm.NONE + CompressionAlgorithm.NONE ); }); }); diff --git a/experimental/packages/shim-opencensus/src/ShimTracer.ts b/experimental/packages/shim-opencensus/src/ShimTracer.ts index c276b99e53e..0f479e93d12 100644 --- a/experimental/packages/shim-opencensus/src/ShimTracer.ts +++ b/experimental/packages/shim-opencensus/src/ShimTracer.ts @@ -77,7 +77,7 @@ export class ShimTracer implements oc.Tracer { onStartSpan(): void {} onEndSpan(): void {} setCurrentRootSpan() { - // This can't be correctly overriden since OTel context does not provide a way to set + // This can't be correctly overridden since OTel context does not provide a way to set // context without a callback. Leave noop for now. } diff --git a/package-lock.json b/package-lock.json index 8ad74075f89..160db7842d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14079,9 +14079,10 @@ } }, "node_modules/bonjour/node_modules/ip": { - "version": "1.1.8", - "dev": true, - "license": "MIT" + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", + "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==", + "dev": true }, "node_modules/bonjour/node_modules/multicast-dns": { "version": "6.2.3", @@ -20480,9 +20481,10 @@ } }, "node_modules/ip": { - "version": "2.0.0", - "dev": true, - "license": "MIT" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", + "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==", + "dev": true }, "node_modules/ip-regex": { "version": "4.3.0", @@ -26687,9 +26689,9 @@ } }, "node_modules/pac-resolver/node_modules/ip": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", - "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", + "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==", "dev": true }, "node_modules/package-hash": { @@ -47400,7 +47402,9 @@ } }, "ip": { - "version": "1.1.8", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", + "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==", "dev": true }, "multicast-dns": { @@ -51753,7 +51757,9 @@ } }, "ip": { - "version": "2.0.0", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", + "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==", "dev": true }, "ip-regex": { @@ -55932,9 +55938,9 @@ }, "dependencies": { "ip": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", - "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", + "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==", "dev": true } } diff --git a/packages/opentelemetry-context-async-hooks/src/AsyncHooksContextManager.ts b/packages/opentelemetry-context-async-hooks/src/AsyncHooksContextManager.ts index 5a2a3abae5f..c7b5a214118 100644 --- a/packages/opentelemetry-context-async-hooks/src/AsyncHooksContextManager.ts +++ b/packages/opentelemetry-context-async-hooks/src/AsyncHooksContextManager.ts @@ -73,7 +73,7 @@ export class AsyncHooksContextManager extends AbstractAsyncHooksContextManager { private _init(uid: number, type: string) { // ignore TIMERWRAP as they combine timers with same timeout which can lead to // false context propagation. TIMERWRAP has been removed in node 11 - // every timer has it's own `Timeout` resource anyway which is used to propagete + // every timer has it's own `Timeout` resource anyway which is used to propagate // context. if (type === 'TIMERWRAP') return; diff --git a/packages/opentelemetry-core/src/internal/exporter.ts b/packages/opentelemetry-core/src/internal/exporter.ts index 28061f5ee7f..2121d7cb784 100644 --- a/packages/opentelemetry-core/src/internal/exporter.ts +++ b/packages/opentelemetry-core/src/internal/exporter.ts @@ -24,7 +24,7 @@ export interface Exporter { /** * @internal - * Shared functionality used by Exporters while exporting data, including suppresion of Traces. + * Shared functionality used by Exporters while exporting data, including suppression of Traces. */ export function _export( exporter: Exporter, diff --git a/packages/opentelemetry-core/test/common/time.test.ts b/packages/opentelemetry-core/test/common/time.test.ts index bf691c70717..1c8df7016ed 100644 --- a/packages/opentelemetry-core/test/common/time.test.ts +++ b/packages/opentelemetry-core/test/common/time.test.ts @@ -191,7 +191,7 @@ describe('time', () => { }); }); - describe('#hrTimeToMicroeconds', () => { + describe('#hrTimeToMicroseconds', () => { it('should return microseconds', () => { const output = hrTimeToMicroseconds([1, 200000000]); assert.deepStrictEqual(output, 1200000); diff --git a/packages/opentelemetry-exporter-jaeger/src/types.ts b/packages/opentelemetry-exporter-jaeger/src/types.ts index d7d62f4daae..c67b524657e 100644 --- a/packages/opentelemetry-exporter-jaeger/src/types.ts +++ b/packages/opentelemetry-exporter-jaeger/src/types.ts @@ -25,7 +25,7 @@ export interface ExporterConfig { /** Time to wait for an onShutdown flush to finish before closing the sender */ flushTimeout?: number; // default: 2000 //The HTTP endpoint for sending spans directly to a collector, i.e. http://jaeger-collector:14268/api/traces - //If setten will override host and port + //If set, will override host and port endpoint?: string; //Username to send as part of "Basic" authentication to the collector endpoint username?: string; diff --git a/packages/opentelemetry-exporter-jaeger/test/transform.test.ts b/packages/opentelemetry-exporter-jaeger/test/transform.test.ts index 35a11c7ffd8..6aade85f5a2 100644 --- a/packages/opentelemetry-exporter-jaeger/test/transform.test.ts +++ b/packages/opentelemetry-exporter-jaeger/test/transform.test.ts @@ -381,7 +381,7 @@ describe('transform', () => { assert.strictEqual( thriftSpan.tags.find(tag => tag.key === 'error'), undefined, - 'If span status USET, no error tag' + 'If span status UNSET, no error tag' ); readableSpan.status.code = SpanStatusCode.ERROR; diff --git a/packages/opentelemetry-exporter-zipkin/README.md b/packages/opentelemetry-exporter-zipkin/README.md index 8dcb1e229af..dc9d57ad75c 100644 --- a/packages/opentelemetry-exporter-zipkin/README.md +++ b/packages/opentelemetry-exporter-zipkin/README.md @@ -51,7 +51,7 @@ You can use built-in `SimpleSpanProcessor` or `BatchSpanProcessor` or write your ### Options -- **getExportRequestHeaders** - optional interceptor that allows adding new headers everytime time the exporter is going to send spans. +- **getExportRequestHeaders** - optional interceptor that allows adding new headers every time time the exporter is going to send spans. This is optional and can be used if headers are changing over time. This is a sync callback. ## Viewing your traces diff --git a/packages/opentelemetry-exporter-zipkin/src/platform/node/util.ts b/packages/opentelemetry-exporter-zipkin/src/platform/node/util.ts index 5032b0c978e..8835177ccda 100644 --- a/packages/opentelemetry-exporter-zipkin/src/platform/node/util.ts +++ b/packages/opentelemetry-exporter-zipkin/src/platform/node/util.ts @@ -71,7 +71,7 @@ export function prepareSend( // Consider 2xx and 3xx as success. if (statusCode < 400) { return done({ code: ExportResultCode.SUCCESS }); - // Consider 4xx as failed non-retriable. + // Consider 4xx as failed non-retryable. } else { return done({ code: ExportResultCode.FAILED, diff --git a/packages/opentelemetry-exporter-zipkin/src/zipkin.ts b/packages/opentelemetry-exporter-zipkin/src/zipkin.ts index ca52806d39c..4787dc38df3 100644 --- a/packages/opentelemetry-exporter-zipkin/src/zipkin.ts +++ b/packages/opentelemetry-exporter-zipkin/src/zipkin.ts @@ -118,7 +118,7 @@ export class ZipkinExporter implements SpanExporter { /** * if user defines getExportRequestHeaders in config then this will be called - * everytime before send, otherwise it will be replaced with noop in + * every time before send, otherwise it will be replaced with noop in * constructor * @default noop */ diff --git a/packages/opentelemetry-resources/src/detectors/EnvDetectorSync.ts b/packages/opentelemetry-resources/src/detectors/EnvDetectorSync.ts index 1230657e193..8572323a09d 100644 --- a/packages/opentelemetry-resources/src/detectors/EnvDetectorSync.ts +++ b/packages/opentelemetry-resources/src/detectors/EnvDetectorSync.ts @@ -83,10 +83,10 @@ class EnvDetectorSync implements DetectorSync { * OTEL_RESOURCE_ATTRIBUTES: A comma-separated list of attributes describing * the source in more detail, e.g. “key1=val1,key2=val2”. Domain names and * paths are accepted as attribute keys. Values may be quoted or unquoted in - * general. If a value contains whitespaces, =, or " characters, it must + * general. If a value contains whitespace, =, or " characters, it must * always be quoted. * - * @param rawEnvAttributes The resource attributes as a comma-seperated list + * @param rawEnvAttributes The resource attributes as a comma-separated list * of key/value pairs. * @returns The sanitized resource attributes. */ diff --git a/packages/opentelemetry-sdk-trace-base/src/Span.ts b/packages/opentelemetry-sdk-trace-base/src/Span.ts index 2a00be5d8a1..06b7056a179 100644 --- a/packages/opentelemetry-sdk-trace-base/src/Span.ts +++ b/packages/opentelemetry-sdk-trace-base/src/Span.ts @@ -357,7 +357,7 @@ export class Span implements APISpan, ReadableSpan { /** * If the given attribute value is of type string and has more characters than given {@code attributeValueLengthLimit} then - * return string with trucated to {@code attributeValueLengthLimit} characters + * return string with truncated to {@code attributeValueLengthLimit} characters * * If the given attribute value is array of strings then * return new array of strings with each element truncated to {@code attributeValueLengthLimit} characters diff --git a/packages/opentelemetry-sdk-trace-base/test/common/BasicTracerProvider.test.ts b/packages/opentelemetry-sdk-trace-base/test/common/BasicTracerProvider.test.ts index 7a0483ffa9a..edb32cc2b0d 100644 --- a/packages/opentelemetry-sdk-trace-base/test/common/BasicTracerProvider.test.ts +++ b/packages/opentelemetry-sdk-trace-base/test/common/BasicTracerProvider.test.ts @@ -247,7 +247,7 @@ describe('BasicTracerProvider', () => { delete envSource.OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT; delete envSource.OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT; }); - it('should have span attribute value length limit as deafult of Infinity', () => { + it('should have span attribute value length limit as default of Infinity', () => { envSource.OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT = '125'; envSource.OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT = 'Infinity'; const tracer = new BasicTracerProvider().getTracer('default'); diff --git a/packages/opentelemetry-sdk-trace-base/test/common/export/InMemorySpanExporter.test.ts b/packages/opentelemetry-sdk-trace-base/test/common/export/InMemorySpanExporter.test.ts index 585610514ee..5bf4ddb8ff8 100644 --- a/packages/opentelemetry-sdk-trace-base/test/common/export/InMemorySpanExporter.test.ts +++ b/packages/opentelemetry-sdk-trace-base/test/common/export/InMemorySpanExporter.test.ts @@ -94,18 +94,18 @@ describe('InMemorySpanExporter', () => { }); it('should return the success result', () => { - const exorter = new InMemorySpanExporter(); - exorter.export([], (result: ExportResult) => { + const exporter = new InMemorySpanExporter(); + exporter.export([], (result: ExportResult) => { assert.strictEqual(result.code, ExportResultCode.SUCCESS); }); }); it('should return the FailedNotRetryable result after shutdown', () => { - const exorter = new InMemorySpanExporter(); - exorter.shutdown(); + const exporter = new InMemorySpanExporter(); + exporter.shutdown(); // after shutdown export should fail - exorter.export([], (result: ExportResult) => { + exporter.export([], (result: ExportResult) => { assert.strictEqual(result.code, ExportResultCode.FAILED); }); }); diff --git a/packages/opentelemetry-sdk-trace-web/README.md b/packages/opentelemetry-sdk-trace-web/README.md index 8eccf7fc135..44ed1ffbe0a 100644 --- a/packages/opentelemetry-sdk-trace-web/README.md +++ b/packages/opentelemetry-sdk-trace-web/README.md @@ -17,7 +17,7 @@ See the example how to use it. OpenTelemetry comes with a growing number of instrumentations for well know modules (see [supported modules](https://github.com/open-telemetry/opentelemetry-js#plugins)) and an API to create custom instrumentations (see [the instrumentation developer guide](https://github.com/open-telemetry/opentelemetry-js/blob/main/doc/instrumentation-guide.md)). Web Tracer currently supports one plugin for document load. -Unlike Node Tracer (`NodeTracerProvider`), the plugins needs to be initialised and passed in configuration. +Unlike Node Tracer (`NodeTracerProvider`), the plugins needs to be initialized and passed in configuration. The reason is to give user full control over which plugin will be bundled into web page. You can choose to use the `ZoneContextManager` if you want to trace asynchronous operations. Please note that the `ZoneContextManager` does not work with JS code targeting `ES2017+`. In order to use the `ZoneContextManager`, please transpile back to `ES2015`. diff --git a/packages/sdk-metrics/src/aggregator/ExponentialHistogram.ts b/packages/sdk-metrics/src/aggregator/ExponentialHistogram.ts index d13042c3305..a0c438ee173 100644 --- a/packages/sdk-metrics/src/aggregator/ExponentialHistogram.ts +++ b/packages/sdk-metrics/src/aggregator/ExponentialHistogram.ts @@ -160,7 +160,7 @@ export class ExponentialHistogramAccumulation implements Accumulation { } /** - * @returns {Number} The scale used by thie accumulation + * @returns {Number} The scale used by this accumulation */ get scale(): number { if (this._count === this._zeroCount) { @@ -171,7 +171,7 @@ export class ExponentialHistogramAccumulation implements Accumulation { } /** - * positive holds the postive values + * positive holds the positive values * @returns {Buckets} */ get positive(): Buckets { @@ -187,7 +187,7 @@ export class ExponentialHistogramAccumulation implements Accumulation { } /** - * uppdateByIncr supports updating a histogram with a non-negative + * updateByIncr supports updating a histogram with a non-negative * increment. * @param value * @param increment @@ -253,7 +253,7 @@ export class ExponentialHistogramAccumulation implements Accumulation { } /** - * diff substracts other from self + * diff subtracts other from self * @param {ExponentialHistogramAccumulation} other */ diff(other: ExponentialHistogramAccumulation) { @@ -512,7 +512,7 @@ export class ExponentialHistogramAccumulation implements Accumulation { } /** - * Aggregator for ExponentialHistogramAccumlations + * Aggregator for ExponentialHistogramAccumulations */ export class ExponentialHistogramAggregator implements Aggregator diff --git a/packages/sdk-metrics/src/exemplar/ExemplarReservoir.ts b/packages/sdk-metrics/src/exemplar/ExemplarReservoir.ts index 1fcad673393..ef1e8d14e6b 100644 --- a/packages/sdk-metrics/src/exemplar/ExemplarReservoir.ts +++ b/packages/sdk-metrics/src/exemplar/ExemplarReservoir.ts @@ -40,7 +40,7 @@ export interface ExemplarReservoir { * * @param pointAttributes The attributes associated with metric point. * - * @returns a list of {@link Exemplar}s. Retuned exemplars contain the attributes that were filtered out by the + * @returns a list of {@link Exemplar}s. Returned exemplars contain the attributes that were filtered out by the * aggregator, but recorded alongside the original measurement. */ collect(pointAttributes: MetricAttributes): Exemplar[]; diff --git a/packages/sdk-metrics/src/export/ConsoleMetricExporter.ts b/packages/sdk-metrics/src/export/ConsoleMetricExporter.ts index 36c8b48806b..62973d8805f 100644 --- a/packages/sdk-metrics/src/export/ConsoleMetricExporter.ts +++ b/packages/sdk-metrics/src/export/ConsoleMetricExporter.ts @@ -71,11 +71,14 @@ export class ConsoleMetricExporter implements PushMetricExporter { ): void { for (const scopeMetrics of metrics.scopeMetrics) { for (const metric of scopeMetrics.metrics) { - console.dir({ - descriptor: metric.descriptor, - dataPointType: metric.dataPointType, - dataPoints: metric.dataPoints, - }); + console.dir( + { + descriptor: metric.descriptor, + dataPointType: metric.dataPointType, + dataPoints: metric.dataPoints, + }, + { depth: null } + ); } } diff --git a/renovate.json b/renovate.json index 1257c688d1a..4c5698cf22e 100644 --- a/renovate.json +++ b/renovate.json @@ -5,7 +5,7 @@ "groupName": "all patch versions", "groupSlug": "all-patch", "matchUpdateTypes": ["patch"], - "excludePackageNames": ["prettier"], + "excludePackageNames": ["prettier", "import-in-the-middle"], "schedule": ["before 3am every weekday"] }, { diff --git a/scripts/semconv/templates/SemanticAttributes.ts.j2 b/scripts/semconv/templates/SemanticAttributes.ts.j2 index 0bbb4ebf7ce..6f50e287e31 100644 --- a/scripts/semconv/templates/SemanticAttributes.ts.j2 +++ b/scripts/semconv/templates/SemanticAttributes.ts.j2 @@ -38,7 +38,7 @@ import { createConstMap } from '../internal/utils'; //---------------------------------------------------------------------------------------------------------- // Temporary local constants to assign to the individual exports and the namespaced version -// Required to avoid the namespace exports using the unminifable export names for some package types +// Required to avoid the namespace exports using the unminifiable export names for some package types {%- for attribute in attributes if attribute.is_local and not attribute.ref %} const TMP_{{attribute.fqn | to_const_name}} = {{ print_value ("string", attribute.fqn) }}; {%- endfor %} @@ -117,7 +117,7 @@ export const {{class}}:{{class}} = /*#__PURE__*/createConstMap<{{class}}>([ * ---------------------------------------------------------------------------------------------------------- */ // Temporary local constants to assign to the individual exports and the namespaced version -// Required to avoid the namespace exports using the unminifable export names for some package types +// Required to avoid the namespace exports using the unminifiable export names for some package types {%- for member in attribute.attr_type.members if attribute.is_local and not attribute.ref %} const TMP_{{class_name|upper}}_{{ member.member_id | to_const_name }} = {{ print_value(type, member.value) }}; {%- endfor %}