diff --git a/packages/opentelemetry-exporter-collector-grpc/README.md b/packages/opentelemetry-exporter-collector-grpc/README.md index cb472ee699..93b01689a2 100644 --- a/packages/opentelemetry-exporter-collector-grpc/README.md +++ b/packages/opentelemetry-exporter-collector-grpc/README.md @@ -23,7 +23,8 @@ const { CollectorTraceExporter } = require('@opentelemetry/exporter-collector-g const collectorOptions = { serviceName: 'basic-service', - url: '' // url is optional and can be omitted - default is localhost:4317 + // url is optional and can be omitted - default is localhost:4317 + url: ':', }; const provider = new BasicTracerProvider(); @@ -31,25 +32,25 @@ const exporter = new CollectorTraceExporter(collectorOptions); provider.addSpanProcessor(new SimpleSpanProcessor(exporter)); provider.register(); - +['SIGINT', 'SIGTERM'].forEach(signal => { + process.on(signal, () => provider.shutdown().catch(console.error)); +}); ``` By default, plaintext connection is used. In order to use TLS in Node.js, provide `credentials` option like so: ```js const fs = require('fs'); -const grpc = require('grpc'); +const grpc = require('@grpc/grpc-js'); + const { BasicTracerProvider, SimpleSpanProcessor } = require('@opentelemetry/tracing'); const { CollectorTraceExporter } = require('@opentelemetry/exporter-collector-grpc'); const collectorOptions = { serviceName: 'basic-service', - url: '', // url is optional and can be omitted - default is localhost:4317 - credentials: grpc.credentials.createSsl( - fs.readFileSync('./ca.crt'), - fs.readFileSync('./client.key'), - fs.readFileSync('./client.crt') - ) + // url is optional and can be omitted - default is localhost:4317 + url: ':', + credentials: grpc.credentials.createSsl(), }; const provider = new BasicTracerProvider(); @@ -57,23 +58,39 @@ const exporter = new CollectorTraceExporter(collectorOptions); provider.addSpanProcessor(new SimpleSpanProcessor(exporter)); provider.register(); +['SIGINT', 'SIGTERM'].forEach(signal => { + process.on(signal, () => provider.shutdown().catch(console.error)); +}); +``` + +To use mutual authentication, pass to the `createSsl()` constructor: + +```js + credentials: grpc.credentials.createSsl( + fs.readFileSync('./ca.crt'), + fs.readFileSync('./client.key'), + fs.readFileSync('./client.crt') + ), ``` -To see how to generate credentials, you can refer to the script used to generate certificates for tests [here](./test/certs/regenerate.sh) +To generate credentials for mutual authentication, you can refer to the script used to generate certificates for tests [here](./test/certs/regenerate.sh) The exporter can be configured to send custom metadata with each request as in the example below: ```js -const grpc = require('grpc'); +const grpc = require('@grpc/grpc-js'); + const { BasicTracerProvider, SimpleSpanProcessor } = require('@opentelemetry/tracing'); const { CollectorTraceExporter } = require('@opentelemetry/exporter-collector-grpc'); const metadata = new grpc.Metadata(); +// For instance, an API key or access token might go here. metadata.set('k', 'v'); const collectorOptions = { serviceName: 'basic-service', - url: '', // url is optional and can be omitted - default is localhost:4317 + // url is optional and can be omitted - default is localhost:4317 + url: ':', metadata, // // an optional grpc.Metadata object to be sent with each request }; @@ -82,6 +99,9 @@ const exporter = new CollectorTraceExporter(collectorOptions); provider.addSpanProcessor(new SimpleSpanProcessor(exporter)); provider.register(); +['SIGINT', 'SIGTERM'].forEach(signal => { + process.on(signal, () => provider.shutdown().catch(console.error)); +}); ``` Note, that this will only work if TLS is also configured on the server. @@ -95,20 +115,24 @@ const { MeterProvider } = require('@opentelemetry/metrics'); const { CollectorMetricExporter } = require('@opentelemetry/exporter-collector-grpc'); const collectorOptions = { serviceName: 'basic-service', - url: '', // url is optional and can be omitted - default is localhost:55681 + // url is optional and can be omitted - default is localhost:4317 + url: ':', }; const exporter = new CollectorMetricExporter(collectorOptions); // Register the exporter -const meter = new MeterProvider({ +const provider = new MeterProvider({ exporter, interval: 60000, -}).getMeter('example-meter'); +}) +['SIGINT', 'SIGTERM'].forEach(signal => { + process.on(signal, () => provider.shutdown().catch(console.error)); +}); // Now, start recording data +const meter = provider.getMeter('example-meter'); const counter = meter.createCounter('metric_name'); counter.add(10, { 'key': 'value' }); - ``` ## Running opentelemetry-collector locally to see the traces diff --git a/packages/opentelemetry-exporter-collector-grpc/src/CollectorExporterNodeBase.ts b/packages/opentelemetry-exporter-collector-grpc/src/CollectorExporterNodeBase.ts index c01264cb94..ea37c15eee 100644 --- a/packages/opentelemetry-exporter-collector-grpc/src/CollectorExporterNodeBase.ts +++ b/packages/opentelemetry-exporter-collector-grpc/src/CollectorExporterNodeBase.ts @@ -26,6 +26,7 @@ import { ServiceClientType, } from './types'; import { ServiceClient } from './types'; +import { validateAndNormalizeUrl } from './util'; /** * Collector Metric Exporter abstract base class @@ -41,6 +42,7 @@ export abstract class CollectorExporterNodeBase< grpcQueue: GRPCQueueItem[] = []; metadata?: Metadata; serviceClient?: ServiceClient = undefined; + serverAddress: string; private _send!: Function; constructor(config: CollectorExporterConfigNode = {}) { @@ -48,6 +50,8 @@ export abstract class CollectorExporterNodeBase< if (config.headers) { diag.warn('Headers cannot be set when using grpc'); } + + this.serverAddress = validateAndNormalizeUrl(this.url); this.metadata = config.metadata; } private _sendPromise( diff --git a/packages/opentelemetry-exporter-collector-grpc/src/util.ts b/packages/opentelemetry-exporter-collector-grpc/src/util.ts index 45ded6e4f5..16a2b62dfa 100644 --- a/packages/opentelemetry-exporter-collector-grpc/src/util.ts +++ b/packages/opentelemetry-exporter-collector-grpc/src/util.ts @@ -21,6 +21,7 @@ import { globalErrorHandler } from '@opentelemetry/core'; import { collectorTypes } from '@opentelemetry/exporter-collector'; import * as path from 'path'; import { CollectorExporterNodeBase } from './CollectorExporterNodeBase'; +import { URL } from 'url'; import { CollectorExporterConfigNode, GRPCQueueItem, @@ -32,7 +33,6 @@ export function onInit( config: CollectorExporterConfigNode ): void { collector.grpcQueue = []; - const serverAddress = removeProtocol(collector.url); const credentials: grpc.ChannelCredentials = config.credentials || grpc.credentials.createInsecure(); @@ -52,12 +52,12 @@ export function onInit( if (collector.getServiceClientType() === ServiceClientType.SPANS) { collector.serviceClient = new packageObject.opentelemetry.proto.collector.trace.v1.TraceService( - serverAddress, + collector.serverAddress, credentials ); } else { collector.serviceClient = new packageObject.opentelemetry.proto.collector.metrics.v1.MetricsService( - serverAddress, + collector.serverAddress, credentials ); } @@ -105,6 +105,17 @@ export function send( } } -function removeProtocol(url: string): string { - return url.replace(/^https?:\/\//, ''); +export function validateAndNormalizeUrl(url: string): string { + const target = new URL(url); + if (target.pathname !== '/') { + diag.warn( + 'URL path should not be set when using grpc, the path part of the URL will be ignored.' + ); + } + if (target.protocol !== '' && !target.protocol?.match(/(http|grpc)s?/)) { + diag.warn( + 'URL protocol should be http(s):// or grpc(s)://. Using grpc://.' + ); + } + return target.host; } diff --git a/packages/opentelemetry-exporter-collector-grpc/test/CollectorMetricExporter.test.ts b/packages/opentelemetry-exporter-collector-grpc/test/CollectorMetricExporter.test.ts index 59677eb525..1426a30df9 100644 --- a/packages/opentelemetry-exporter-collector-grpc/test/CollectorMetricExporter.test.ts +++ b/packages/opentelemetry-exporter-collector-grpc/test/CollectorMetricExporter.test.ts @@ -129,7 +129,7 @@ const testCollectorMetricExporter = (params: TestParams) => ) : undefined; collectorExporter = new CollectorMetricExporter({ - url: address, + url: 'grpcs://' + address, credentials, serviceName: 'basic-service', metadata: params.metadata, @@ -166,7 +166,7 @@ const testCollectorMetricExporter = (params: TestParams) => describe('instance', () => { it('should warn about headers', () => { - // Need to stub/spy on the underlying logger as the "diag" instance is global + // Need to stub/spy on the underlying logger as the 'diag' instance is global const spyLoggerWarn = sinon.stub(diag, 'warn'); collectorExporter = new CollectorMetricExporter({ serviceName: 'basic-service', @@ -178,6 +178,18 @@ const testCollectorMetricExporter = (params: TestParams) => const args = spyLoggerWarn.args[0]; assert.strictEqual(args[0], 'Headers cannot be set when using grpc'); }); + it('should warn about path in url', () => { + const spyLoggerWarn = sinon.stub(diag, 'warn'); + collectorExporter = new CollectorMetricExporter({ + serviceName: 'basic-service', + url: address + '/v1/metrics', + }); + const args = spyLoggerWarn.args[0]; + assert.strictEqual( + args[0], + 'URL path should not be set when using grpc, the path part of the URL will be ignored.' + ); + }); }); describe('export', () => { diff --git a/packages/opentelemetry-exporter-collector-grpc/test/CollectorTraceExporter.test.ts b/packages/opentelemetry-exporter-collector-grpc/test/CollectorTraceExporter.test.ts index a1a38db5f3..ce0163219d 100644 --- a/packages/opentelemetry-exporter-collector-grpc/test/CollectorTraceExporter.test.ts +++ b/packages/opentelemetry-exporter-collector-grpc/test/CollectorTraceExporter.test.ts @@ -125,7 +125,7 @@ const testCollectorExporter = (params: TestParams) => : undefined; collectorExporter = new CollectorTraceExporter({ serviceName: 'basic-service', - url: address, + url: 'grpcs://' + address, credentials, metadata: params.metadata, }); @@ -143,7 +143,7 @@ const testCollectorExporter = (params: TestParams) => describe('instance', () => { it('should warn about headers when using grpc', () => { - // Need to stub/spy on the underlying logger as the "diag" instance is global + // Need to stub/spy on the underlying logger as the 'diag' instance is global const spyLoggerWarn = sinon.stub(diag, 'warn'); collectorExporter = new CollectorTraceExporter({ serviceName: 'basic-service', @@ -155,6 +155,18 @@ const testCollectorExporter = (params: TestParams) => const args = spyLoggerWarn.args[0]; assert.strictEqual(args[0], 'Headers cannot be set when using grpc'); }); + it('should warn about path in url', () => { + const spyLoggerWarn = sinon.stub(diag, 'warn'); + collectorExporter = new CollectorTraceExporter({ + serviceName: 'basic-service', + url: address + '/v1/trace', + }); + const args = spyLoggerWarn.args[0]; + assert.strictEqual( + args[0], + 'URL path should not be set when using grpc, the path part of the URL will be ignored.' + ); + }); }); describe('export', () => { diff --git a/packages/opentelemetry-node/README.md b/packages/opentelemetry-node/README.md index 374261c635..7cc7a333a8 100644 --- a/packages/opentelemetry-node/README.md +++ b/packages/opentelemetry-node/README.md @@ -15,7 +15,7 @@ For manual instrumentation see the This package exposes a `NodeTracerProvider`. For loading instrumentations please use `registerInstrumentations` function from [opentelemetry-instrumentation](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-instrumentation) -OpenTelemetry comes with a growing number of instrumentation plugins for well know modules (see [supported modules](https://github.com/open-telemetry/opentelemetry-js#instrumentations)) and an API to create custom instrumentation (see [the instrumentation developer guide](https://github.com/open-telemetry/opentelemetry-js/blob/main/doc/instrumentation-guide.md)). +OpenTelemetry comes with a growing number of instrumentation plugins for well known modules (see [supported modules](https://github.com/open-telemetry/opentelemetry-js#instrumentations)) and an API to create custom instrumentation (see [the instrumentation developer guide](https://github.com/open-telemetry/opentelemetry-js/blob/main/doc/instrumentation-guide.md)). > **Please note:** This module does *not* bundle any plugins. They need to be installed separately.