diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 5c9b3f0faa..3bd874bed0 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -10,16 +10,18 @@ jobs: strategy: fail-fast: false matrix: - container: ["node:8", "node:10", "node:12", "node:14", "node:16"] + node_version: ["8", "10", "12", "14", "16"] runs-on: ubuntu-latest - container: - image: ${{ matrix.container }} env: NPM_CONFIG_UNSAFE_PERM: true steps: - name: Checkout uses: actions/checkout@v1 + - uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node_version }} + - name: restore lock files uses: actions/cache@master # must use unreleased master to cache multiple paths id: cache @@ -33,12 +35,11 @@ jobs: metapackages/*/package-lock.json packages/*/package-lock.json integration-tests/*/package-lock.json - key: ${{ runner.os }}-unit_test-${{ matrix.container }}-${{ hashFiles('**/package.json') }} + key: ${{ runner.os }}-unit_test-${{ matrix.node_version }}-${{ hashFiles('**/package.json') }} - name: Install and Build (cache miss) 🔧 if: steps.cache.outputs.cache-hit != 'true' run: | npm install --ignore-scripts - chown -R 1001:121 "/github/home/.npm" # fix npm cache permissions for npm v7 npx lerna bootstrap --no-ci npm run compile @@ -53,7 +54,7 @@ jobs: run: npm run test - name: Report Coverage run: npm run codecov - if: ${{ matrix.container == 'node:14' }} + if: ${{ matrix.node_version == '14' }} browser-tests: runs-on: ubuntu-latest container: diff --git a/examples/basic-tracer-node/index.js b/examples/basic-tracer-node/index.js index c43657e96e..839f8a2744 100644 --- a/examples/basic-tracer-node/index.js +++ b/examples/basic-tracer-node/index.js @@ -1,14 +1,19 @@ 'use strict'; const opentelemetry = require('@opentelemetry/api'); +const { Resource } = require('@opentelemetry/resources'); +const { ResourceAttributes } = require('@opentelemetry/semantic-conventions'); const { BasicTracerProvider, ConsoleSpanExporter, SimpleSpanProcessor } = require('@opentelemetry/tracing'); const { JaegerExporter } = require('@opentelemetry/exporter-jaeger'); -const provider = new BasicTracerProvider(); +const provider = new BasicTracerProvider({ + resource: new Resource({ + [ResourceAttributes.SERVICE_NAME]: 'basic-service', + }), +}); // Configure span processor to send spans to the exporter const exporter = new JaegerExporter({ - serviceName: 'basic-service', endpoint: 'http://localhost:14268/api/traces', }); provider.addSpanProcessor(new SimpleSpanProcessor(exporter)); diff --git a/examples/collector-exporter-node/metrics.js b/examples/collector-exporter-node/metrics.js index 36182a54ce..bcd5b886c2 100644 --- a/examples/collector-exporter-node/metrics.js +++ b/examples/collector-exporter-node/metrics.js @@ -5,18 +5,22 @@ const { CollectorMetricExporter } = require('@opentelemetry/exporter-collector') // const { CollectorMetricExporter } = require('@opentelemetry/exporter-collector-grpc'); // const { CollectorMetricExporter } = require('@opentelemetry/exporter-collector-proto'); const { MeterProvider } = require('@opentelemetry/metrics'); +const { Resource } = require('@opentelemetry/resources'); +const { ResourceAttributes } = require('@opentelemetry/semantic-conventions'); // Optional and only needed to see the internal diagnostic logging (during development) diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG); const metricExporter = new CollectorMetricExporter({ - serviceName: 'basic-metric-service', // url: 'http://localhost:55681/v1/metrics', }); const meter = new MeterProvider({ exporter: metricExporter, interval: 1000, + resource: new Resource({ + [ResourceAttributes.SERVICE_NAME]: 'basic-metric-service', + }), }).getMeter('example-exporter-collector'); const requestCounter = meter.createCounter('requests', { diff --git a/examples/collector-exporter-node/tracing.js b/examples/collector-exporter-node/tracing.js index 5f0bd39e17..03b968407e 100644 --- a/examples/collector-exporter-node/tracing.js +++ b/examples/collector-exporter-node/tracing.js @@ -3,6 +3,8 @@ const opentelemetry = require('@opentelemetry/api'); const { BasicTracerProvider, ConsoleSpanExporter, SimpleSpanProcessor } = require('@opentelemetry/tracing'); const { CollectorTraceExporter } = require('@opentelemetry/exporter-collector'); +const { Resource } = require('@opentelemetry/resources'); +const { ResourceAttributes } = require('@opentelemetry/semantic-conventions'); // const { CollectorTraceExporter } = require('@opentelemetry/exporter-collector-grpc'); // const { CollectorTraceExporter } = require('@opentelemetry/exporter-collector-proto'); @@ -12,13 +14,16 @@ const { CollectorTraceExporter } = require('@opentelemetry/exporter-collector'); // ); const exporter = new CollectorTraceExporter({ - serviceName: 'basic-service', // headers: { // foo: 'bar' // }, }); -const provider = new BasicTracerProvider(); +const provider = new BasicTracerProvider({ + resource: new Resource({ + [ResourceAttributes.SERVICE_NAME]: 'basic-service', + }), +}); provider.addSpanProcessor(new SimpleSpanProcessor(exporter)); provider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter())); provider.register(); diff --git a/examples/grpc-js/tracer.js b/examples/grpc-js/tracer.js index 7710dff1d8..b42f510973 100644 --- a/examples/grpc-js/tracer.js +++ b/examples/grpc-js/tracer.js @@ -3,6 +3,8 @@ const opentelemetry = require('@opentelemetry/api'); const { registerInstrumentations } = require('@opentelemetry/instrumentation'); const { NodeTracerProvider } = require('@opentelemetry/node'); +const { Resource } = require('@opentelemetry/resources'); +const { ResourceAttributes } = require('@opentelemetry/semantic-conventions'); const { SimpleSpanProcessor } = require('@opentelemetry/tracing'); const { JaegerExporter } = require('@opentelemetry/exporter-jaeger'); const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin'); @@ -11,17 +13,17 @@ const { GrpcInstrumentation } = require('@opentelemetry/instrumentation-grpc'); const EXPORTER = process.env.EXPORTER || ''; module.exports = (serviceName) => { - const provider = new NodeTracerProvider(); + const provider = new NodeTracerProvider({ + resource: new Resource({ + [ResourceAttributes.SERVICE_NAME]: serviceName, + }), + }); let exporter; if (EXPORTER.toLowerCase().startsWith('z')) { - exporter = new ZipkinExporter({ - serviceName, - }); + exporter = new ZipkinExporter(); } else { - exporter = new JaegerExporter({ - serviceName, - }); + exporter = new JaegerExporter(); } provider.addSpanProcessor(new SimpleSpanProcessor(exporter)); diff --git a/examples/grpc/tracer.js b/examples/grpc/tracer.js index 5c108de48d..e2cbf08e4a 100644 --- a/examples/grpc/tracer.js +++ b/examples/grpc/tracer.js @@ -3,6 +3,8 @@ const opentelemetry = require('@opentelemetry/api'); const { registerInstrumentations } = require('@opentelemetry/instrumentation'); const { NodeTracerProvider } = require('@opentelemetry/node'); +const { Resource } = require('@opentelemetry/resources'); +const { ResourceAttributes } = require('@opentelemetry/semantic-conventions'); const { SimpleSpanProcessor } = require('@opentelemetry/tracing'); const { JaegerExporter } = require('@opentelemetry/exporter-jaeger'); const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin'); @@ -11,17 +13,17 @@ const { GrpcInstrumentation } = require('@opentelemetry/instrumentation-grpc'); const EXPORTER = process.env.EXPORTER || ''; module.exports = (serviceName) => { - const provider = new NodeTracerProvider(); + const provider = new NodeTracerProvider({ + resource: new Resource({ + [ResourceAttributes.SERVICE_NAME]: serviceName, + }), + }); let exporter; if (EXPORTER.toLowerCase().startsWith('z')) { - exporter = new ZipkinExporter({ - serviceName, - }); + exporter = new ZipkinExporter(); } else { - exporter = new JaegerExporter({ - serviceName, - }); + exporter = new JaegerExporter(); } provider.addSpanProcessor(new SimpleSpanProcessor(exporter)); diff --git a/examples/http/tracer.js b/examples/http/tracer.js index 25e97ccc06..d73db897c2 100644 --- a/examples/http/tracer.js +++ b/examples/http/tracer.js @@ -3,6 +3,8 @@ const opentelemetry = require('@opentelemetry/api'); const { registerInstrumentations } = require('@opentelemetry/instrumentation'); const { NodeTracerProvider } = require('@opentelemetry/node'); +const { Resource } = require('@opentelemetry/resources'); +const { ResourceAttributes } = require('@opentelemetry/semantic-conventions'); const { SimpleSpanProcessor } = require('@opentelemetry/tracing'); const { JaegerExporter } = require('@opentelemetry/exporter-jaeger'); const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin'); @@ -11,17 +13,17 @@ const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http'); const EXPORTER = process.env.EXPORTER || ''; module.exports = (serviceName) => { - const provider = new NodeTracerProvider(); + const provider = new NodeTracerProvider({ + resource: new Resource({ + [ResourceAttributes.SERVICE_NAME]: serviceName, + }), + }); let exporter; if (EXPORTER.toLowerCase().startsWith('z')) { - exporter = new ZipkinExporter({ - serviceName, - }); + exporter = new ZipkinExporter(); } else { - exporter = new JaegerExporter({ - serviceName, - }); + exporter = new JaegerExporter(); } provider.addSpanProcessor(new SimpleSpanProcessor(exporter)); diff --git a/examples/https/tracer.js b/examples/https/tracer.js index dbee05b099..1f7be3fb10 100644 --- a/examples/https/tracer.js +++ b/examples/https/tracer.js @@ -3,6 +3,8 @@ const opentelemetry = require('@opentelemetry/api'); const { registerInstrumentations } = require('@opentelemetry/instrumentation'); const { NodeTracerProvider } = require('@opentelemetry/node'); +const { Resource } = require('@opentelemetry/resources'); +const { ResourceAttributes } = require('@opentelemetry/semantic-conventions'); const { SimpleSpanProcessor } = require('@opentelemetry/tracing'); const { JaegerExporter } = require('@opentelemetry/exporter-jaeger'); const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin'); @@ -13,16 +15,16 @@ process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; module.exports = (serviceName) => { let exporter; - const provider = new NodeTracerProvider(); + const provider = new NodeTracerProvider({ + resource: new Resource({ + [ResourceAttributes.SERVICE_NAME]: serviceName, + }), + }); if (EXPORTER.toLowerCase().startsWith('z')) { - exporter = new ZipkinExporter({ - serviceName, - }); + exporter = new ZipkinExporter(); } else { - exporter = new JaegerExporter({ - serviceName, - }); + exporter = new JaegerExporter(); } provider.addSpanProcessor(new SimpleSpanProcessor(exporter)); diff --git a/examples/opentracing-shim/package.json b/examples/opentracing-shim/package.json index b517f09eeb..d346ef26e7 100644 --- a/examples/opentracing-shim/package.json +++ b/examples/opentracing-shim/package.json @@ -33,6 +33,8 @@ "@opentelemetry/exporter-zipkin": "0.22.0", "@opentelemetry/instrumentation": "0.22.0", "@opentelemetry/node": "0.22.0", + "@opentelemetry/resources": "0.22.0", + "@opentelemetry/semantic-conventions": "0.22.0", "@opentelemetry/shim-opentracing": "0.22.0", "@opentelemetry/tracing": "0.22.0", "opentracing": "^0.14.4" diff --git a/examples/opentracing-shim/server.js b/examples/opentracing-shim/server.js index cf54c646fb..a21f93a149 100644 --- a/examples/opentracing-shim/server.js +++ b/examples/opentracing-shim/server.js @@ -37,7 +37,7 @@ async function handleRequest(req, res) { res.writeHead(200, { 'Content-Type': 'application/json' }); res.write( - JSON.stringify({ status: 'OK', traceId: span.spanContext().toTraceId() }), + JSON.stringify({ status: 'OK', traceId: span.context().toTraceId() }), ); res.end(); diff --git a/examples/opentracing-shim/shim.js b/examples/opentracing-shim/shim.js index 31a71d9a21..f6c325a649 100644 --- a/examples/opentracing-shim/shim.js +++ b/examples/opentracing-shim/shim.js @@ -1,6 +1,7 @@ 'use strict'; -const { registerInstrumentations } = require('@opentelemetry/instrumentation'); +const { ResourceAttributes } = require('@opentelemetry/semantic-conventions'); +const { Resource } = require('@opentelemetry/resources'); const { NodeTracerProvider } = require('@opentelemetry/node'); const { SimpleSpanProcessor } = require('@opentelemetry/tracing'); const { JaegerExporter } = require('@opentelemetry/exporter-jaeger'); @@ -8,26 +9,25 @@ const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin'); const { TracerShim } = require('@opentelemetry/shim-opentracing'); function shim(serviceName) { - const provider = new NodeTracerProvider(); + const provider = new NodeTracerProvider({ + resource: new Resource({ [ResourceAttributes.SERVICE_NAME]: serviceName }), + }); provider.addSpanProcessor(new SimpleSpanProcessor(getExporter(serviceName))); // Initialize the OpenTelemetry APIs to use the NodeTracerProvider bindings provider.register(); - registerInstrumentations({ - }); - return new TracerShim(provider.getTracer('opentracing-shim')); } -function getExporter(serviceName) { +function getExporter() { const type = process.env.EXPORTER.toLowerCase() || 'jaeger'; if (type.startsWith('z')) { - return new ZipkinExporter({ serviceName }); + return new ZipkinExporter(); } - return new JaegerExporter({ serviceName, flushInterval: 100 }); + return new JaegerExporter(); } exports.shim = shim; diff --git a/examples/tracer-web/examples/metrics/index.js b/examples/tracer-web/examples/metrics/index.js index 3e546ac3fd..91c8fe4610 100644 --- a/examples/tracer-web/examples/metrics/index.js +++ b/examples/tracer-web/examples/metrics/index.js @@ -3,13 +3,13 @@ const { DiagConsoleLogger, DiagLogLevel, diag } = require('@opentelemetry/api'); const { CollectorMetricExporter } = require('@opentelemetry/exporter-collector'); const { MeterProvider } = require('@opentelemetry/metrics'); +const { Resource } = require('@opentelemetry/resources'); +const { ResourceAttributes } = require('@opentelemetry/semantic-conventions'); // Optional and only needed to see the internal diagnostic logging (during development) diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG); -const metricExporter = new CollectorMetricExporter({ - serviceName: 'basic-metric-service', -}); +const metricExporter = new CollectorMetricExporter(); let interval; let meter; @@ -25,6 +25,9 @@ function startMetrics() { meter = new MeterProvider({ exporter: metricExporter, interval: 1000, + resource: new Resource({ + [ResourceAttributes.SERVICE_NAME]: 'basic-metric-service', + }), }).getMeter('example-exporter-collector'); const requestCounter = meter.createCounter('requests', { diff --git a/getting-started/README.md b/getting-started/README.md index e3abdd231a..a217dd429b 100644 --- a/getting-started/README.md +++ b/getting-started/README.md @@ -58,10 +58,11 @@ To create traces on NodeJS, you need `@opentelemetry/node`, `@opentelemetry/core ```sh $ npm install \ - @opentelemetry/core \ + @opentelemetry/api \ @opentelemetry/node \ @opentelemetry/instrumentation-http \ - @opentelemetry/instrumentation-express + @opentelemetry/instrumentation-express \ + @opentelemetry/instrumentation-grpc ``` #### Initialize a global tracer @@ -124,20 +125,25 @@ After you install these dependencies, initialize and register them. Modify `trac const { diag, DiagConsoleLogger, DiagLogLevel } = require("@opentelemetry/api"); const { NodeTracerProvider } = require("@opentelemetry/node"); +const { Resource } = require('@opentelemetry/resources'); +const { ResourceAttributes } = require('@opentelemetry/semantic-conventions'); const { SimpleSpanProcessor } = require("@opentelemetry/tracing"); const { ZipkinExporter } = require("@opentelemetry/exporter-zipkin"); const { registerInstrumentations } = require("@opentelemetry/instrumentation"); const { HttpInstrumentation } = require("@opentelemetry/instrumentation-http"); const { GrpcInstrumentation } = require("@opentelemetry/instrumentation-grpc"); -const provider = new NodeTracerProvider(); +const provider = new NodeTracerProvider({ + resource: new Resource({ + [ResourceAttributes.SERVICE_NAME]: "getting-started", + }) +}); diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.ALL); provider.addSpanProcessor( new SimpleSpanProcessor( new ZipkinExporter({ - serviceName: "getting-started", // If you are running your tracing backend on another host, // you can point to it using the `url` parameter of the // exporter config. diff --git a/getting-started/traced-example/tracing.js b/getting-started/traced-example/tracing.js index 5c3172682d..e1ac8b3264 100644 --- a/getting-started/traced-example/tracing.js +++ b/getting-started/traced-example/tracing.js @@ -2,17 +2,22 @@ const { NodeTracerProvider } = require("@opentelemetry/node"); const { SimpleSpanProcessor } = require("@opentelemetry/tracing"); +const { Resource } = require('@opentelemetry/resources'); +const { ResourceAttributes } = require('@opentelemetry/semantic-conventions'); const { ZipkinExporter } = require("@opentelemetry/exporter-zipkin"); const { registerInstrumentations } = require('@opentelemetry/instrumentation'); const { ExpressInstrumentation } = require('@opentelemetry/instrumentation-express'); const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http'); -const provider = new NodeTracerProvider(); +const provider = new NodeTracerProvider({ + resource: new Resource({ + [ResourceAttributes.SERVICE_NAME]: "getting-started", + }) +}); provider.addSpanProcessor( new SimpleSpanProcessor( new ZipkinExporter({ - serviceName: "getting-started" // If you are running your tracing backend on another host, // you can point to it using the `url` parameter of the // exporter config. diff --git a/getting-started/ts-example/README.md b/getting-started/ts-example/README.md index 7c2b28d8f4..b4c75a5407 100644 --- a/getting-started/ts-example/README.md +++ b/getting-started/ts-example/README.md @@ -134,6 +134,9 @@ import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'; const provider: NodeTracerProvider = new NodeTracerProvider({ logLevel: LogLevel.ERROR, + resource: new Resource({ + [ResourceAttributes.SERVICE_NAME]: 'getting-started', + }), }); provider.addSpanProcessor( @@ -141,7 +144,6 @@ provider.addSpanProcessor( new ZipkinExporter({ // For Jaeger, use the following line instead: // new JaegerExporter({ - serviceName: 'getting-started', // If you are running your tracing backend on another host, // you can point to it using the `url` parameter of the // exporter config. diff --git a/getting-started/ts-example/traced-example/tracing.ts b/getting-started/ts-example/traced-example/tracing.ts index 4598578ef2..1d0f400fe4 100644 --- a/getting-started/ts-example/traced-example/tracing.ts +++ b/getting-started/ts-example/traced-example/tracing.ts @@ -1,4 +1,6 @@ import { NodeTracerProvider } from '@opentelemetry/node'; +const { Resource } = require('@opentelemetry/resources'); +const { ResourceAttributes } = require('@opentelemetry/semantic-conventions'); import { SimpleSpanProcessor } from '@opentelemetry/tracing'; import { ZipkinExporter } from '@opentelemetry/exporter-zipkin'; @@ -9,13 +11,16 @@ const { registerInstrumentations } = require('@opentelemetry/instrumentation'); const { ExpressInstrumentation } = require('@opentelemetry/instrumentation-express'); const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http'); -const provider: NodeTracerProvider = new NodeTracerProvider(); +const provider: NodeTracerProvider = new NodeTracerProvider({ + resource: new Resource({ + [ResourceAttributes.SERVICE_NAME]: 'getting-started', + }), +}); provider.addSpanProcessor( new SimpleSpanProcessor( new ZipkinExporter({ // For Jaeger, use the following line instead: // new JaegerExporter({ - serviceName: 'getting-started', // If you are running your tracing backend on another host, // you can point to it using the `url` parameter of the // exporter config. diff --git a/packages/opentelemetry-context-async-hooks/package.json b/packages/opentelemetry-context-async-hooks/package.json index b40f630371..58eb1baf63 100644 --- a/packages/opentelemetry-context-async-hooks/package.json +++ b/packages/opentelemetry-context-async-hooks/package.json @@ -44,7 +44,7 @@ "@types/mocha": "8.2.2", "@types/node": "14.14.43", "@types/shimmer": "1.0.1", - "codecov": "^3.8.2", + "codecov": "3.8.2", "mocha": "7.2.0", "nyc": "15.1.0", "rimraf": "3.0.2", diff --git a/packages/opentelemetry-core/src/utils/url.ts b/packages/opentelemetry-core/src/utils/url.ts index a6122ae784..9db9725b82 100644 --- a/packages/opentelemetry-core/src/utils/url.ts +++ b/packages/opentelemetry-core/src/utils/url.ts @@ -17,7 +17,7 @@ export function urlMatches(url: string, urlToMatch: string | RegExp): boolean { if (typeof urlToMatch === 'string') { return url === urlToMatch; } else { - return !!url.match(urlToMatch); + return urlToMatch.test(url); } } /** diff --git a/packages/opentelemetry-exporter-collector-grpc/src/CollectorExporterNodeBase.ts b/packages/opentelemetry-exporter-collector-grpc/src/CollectorExporterNodeBase.ts index c01264cb94..0c14f0063b 100644 --- a/packages/opentelemetry-exporter-collector-grpc/src/CollectorExporterNodeBase.ts +++ b/packages/opentelemetry-exporter-collector-grpc/src/CollectorExporterNodeBase.ts @@ -19,13 +19,14 @@ import { CollectorExporterBase, collectorTypes, } from '@opentelemetry/exporter-collector'; -import type { Metadata } from '@grpc/grpc-js'; +import { Metadata } from '@grpc/grpc-js'; import { CollectorExporterConfigNode, GRPCQueueItem, ServiceClientType, } from './types'; import { ServiceClient } from './types'; +import { getEnv, baggageUtils } from "@opentelemetry/core"; /** * Collector Metric Exporter abstract base class @@ -48,8 +49,13 @@ export abstract class CollectorExporterNodeBase< if (config.headers) { diag.warn('Headers cannot be set when using grpc'); } - this.metadata = config.metadata; + const headers = 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) + } } + private _sendPromise( objects: ExportItem[], onSuccess: () => void, diff --git a/packages/opentelemetry-exporter-collector-grpc/src/CollectorMetricExporter.ts b/packages/opentelemetry-exporter-collector-grpc/src/CollectorMetricExporter.ts index 84a880c16d..c909241a73 100644 --- a/packages/opentelemetry-exporter-collector-grpc/src/CollectorMetricExporter.ts +++ b/packages/opentelemetry-exporter-collector-grpc/src/CollectorMetricExporter.ts @@ -21,8 +21,9 @@ import { import { MetricRecord, MetricExporter } from '@opentelemetry/metrics'; import { CollectorExporterConfigNode, ServiceClientType } from './types'; import { CollectorExporterNodeBase } from './CollectorExporterNodeBase'; -import { getEnv } from '@opentelemetry/core'; +import { baggageUtils, getEnv } from '@opentelemetry/core'; import { validateAndNormalizeUrl } from './util'; +import { Metadata } from "@grpc/grpc-js"; const DEFAULT_COLLECTOR_URL = 'localhost:4317'; @@ -38,6 +39,15 @@ export class CollectorMetricExporter // Converts time to nanoseconds protected readonly _startTime = new Date().getTime() * 1000000; + constructor(config: CollectorExporterConfigNode = {}) { + super(config); + const headers = baggageUtils.parseKeyPairsIntoRecord(getEnv().OTEL_EXPORTER_OTLP_METRICS_HEADERS); + this.metadata ||= new Metadata(); + for (const [k, v] of Object.entries(headers)) { + this.metadata.set(k, v) + } + } + convert( metrics: MetricRecord[] ): collectorTypes.opentelemetryProto.collector.metrics.v1.ExportMetricsServiceRequest { diff --git a/packages/opentelemetry-exporter-collector-grpc/src/CollectorTraceExporter.ts b/packages/opentelemetry-exporter-collector-grpc/src/CollectorTraceExporter.ts index aae731d10c..f0b63cbb79 100644 --- a/packages/opentelemetry-exporter-collector-grpc/src/CollectorTraceExporter.ts +++ b/packages/opentelemetry-exporter-collector-grpc/src/CollectorTraceExporter.ts @@ -21,8 +21,9 @@ import { toCollectorExportTraceServiceRequest, } from '@opentelemetry/exporter-collector'; import { CollectorExporterConfigNode, ServiceClientType } from './types'; -import { getEnv } from '@opentelemetry/core'; +import { baggageUtils, getEnv } from '@opentelemetry/core'; import { validateAndNormalizeUrl } from './util'; +import { Metadata } from "@grpc/grpc-js"; const DEFAULT_COLLECTOR_URL = 'localhost:4317'; @@ -35,6 +36,16 @@ export class CollectorTraceExporter collectorTypes.opentelemetryProto.collector.trace.v1.ExportTraceServiceRequest > implements SpanExporter { + + constructor(config: CollectorExporterConfigNode = {}) { + super(config); + const headers = 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) + } + } + convert( spans: ReadableSpan[] ): collectorTypes.opentelemetryProto.collector.trace.v1.ExportTraceServiceRequest { diff --git a/packages/opentelemetry-exporter-collector-grpc/test/CollectorMetricExporter.test.ts b/packages/opentelemetry-exporter-collector-grpc/test/CollectorMetricExporter.test.ts index a0cb218a7e..f765c250cb 100644 --- a/packages/opentelemetry-exporter-collector-grpc/test/CollectorMetricExporter.test.ts +++ b/packages/opentelemetry-exporter-collector-grpc/test/CollectorMetricExporter.test.ts @@ -278,6 +278,25 @@ describe('when configuring via environment', () => { envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; envSource.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT = ''; }); + it('should use headers defined via env', () => { + envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar'; + const collectorExporter = new CollectorMetricExporter(); + assert.deepStrictEqual(collectorExporter.metadata?.get('foo'), ['bar']); + envSource.OTEL_EXPORTER_OTLP_HEADERS = ''; + }); + it('should override global headers config with signal 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_METRICS_HEADERS = 'foo=boo'; + const collectorExporter = new CollectorMetricExporter({ 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_METRICS_HEADERS = ''; + envSource.OTEL_EXPORTER_OTLP_HEADERS = ''; + }); }); testCollectorMetricExporter({ useTLS: true }); diff --git a/packages/opentelemetry-exporter-collector-grpc/test/CollectorTraceExporter.test.ts b/packages/opentelemetry-exporter-collector-grpc/test/CollectorTraceExporter.test.ts index 1c91cae6a7..bd498b6de3 100644 --- a/packages/opentelemetry-exporter-collector-grpc/test/CollectorTraceExporter.test.ts +++ b/packages/opentelemetry-exporter-collector-grpc/test/CollectorTraceExporter.test.ts @@ -240,6 +240,25 @@ describe('when configuring via environment', () => { 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 CollectorTraceExporter(); + assert.deepStrictEqual(collectorExporter.metadata?.get('foo'), ['bar']); + envSource.OTEL_EXPORTER_OTLP_HEADERS = ''; + }); + it('should override global headers config with signal 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 CollectorTraceExporter({ 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 = ''; + }); }); testCollectorExporter({ useTLS: true }); diff --git a/packages/opentelemetry-exporter-jaeger/README.md b/packages/opentelemetry-exporter-jaeger/README.md index 575689d9af..1c8bd0886a 100644 --- a/packages/opentelemetry-exporter-jaeger/README.md +++ b/packages/opentelemetry-exporter-jaeger/README.md @@ -64,7 +64,6 @@ set, the value set by the option in code is authoritative. import { JaegerExporter } from '@opentelemetry/exporter-jaeger'; const options = { - serviceName: 'my-service', tags: [], // optional // You can use the default UDPSender host: 'localhost', // optional diff --git a/packages/opentelemetry-exporter-zipkin/README.md b/packages/opentelemetry-exporter-zipkin/README.md index b5f8d58a85..91e321eb8a 100644 --- a/packages/opentelemetry-exporter-zipkin/README.md +++ b/packages/opentelemetry-exporter-zipkin/README.md @@ -30,7 +30,6 @@ const options = { 'my-header': 'header-value', }, url: 'your-zipkin-url', - serviceName: 'your-application-name', // optional interceptor getExportRequestHeaders: () => { return { diff --git a/packages/opentelemetry-instrumentation-fetch/src/fetch.ts b/packages/opentelemetry-instrumentation-fetch/src/fetch.ts index a8860947ba..5d107027d7 100644 --- a/packages/opentelemetry-instrumentation-fetch/src/fetch.ts +++ b/packages/opentelemetry-instrumentation-fetch/src/fetch.ts @@ -34,16 +34,6 @@ import { VERSION } from './version'; // safe enough const OBSERVER_WAIT_TIME_MS = 300; -// Used to normalize relative URLs -let a: HTMLAnchorElement | undefined; -const getUrlNormalizingAnchor = () => { - if (!a) { - a = document.createElement('a'); - } - - return a; -}; - export interface FetchCustomAttributeFunction { ( span: api.Span, @@ -438,7 +428,7 @@ export class FetchInstrumentation extends InstrumentationBase< const observer: PerformanceObserver = new PerformanceObserver(list => { const perfObsEntries = list.getEntries() as PerformanceResourceTiming[]; - const urlNormalizingAnchor = getUrlNormalizingAnchor(); + const urlNormalizingAnchor = web.getUrlNormalizingAnchor(); urlNormalizingAnchor.href = spanUrl; perfObsEntries.forEach(entry => { if ( diff --git a/packages/opentelemetry-instrumentation-xml-http-request/src/xhr.ts b/packages/opentelemetry-instrumentation-xml-http-request/src/xhr.ts index 1b5ade2660..eabab83d1c 100644 --- a/packages/opentelemetry-instrumentation-xml-http-request/src/xhr.ts +++ b/packages/opentelemetry-instrumentation-xml-http-request/src/xhr.ts @@ -29,6 +29,7 @@ import { parseUrl, PerformanceTimingNames as PTN, shouldPropagateTraceHeaders, + getUrlNormalizingAnchor } from '@opentelemetry/web'; import { EventNames } from './enums/EventNames'; import { @@ -216,10 +217,13 @@ export class XMLHttpRequestInstrumentation extends InstrumentationBase { const entries = list.getEntries() as PerformanceResourceTiming[]; + const urlNormalizingAnchor = getUrlNormalizingAnchor(); + urlNormalizingAnchor.href = spanUrl; + entries.forEach(entry => { if ( entry.initiatorType === 'xmlhttprequest' && - entry.name === spanUrl + entry.name === urlNormalizingAnchor.href ) { if (xhrMem.createdResources) { xhrMem.createdResources.entries.push(entry); diff --git a/packages/opentelemetry-instrumentation-xml-http-request/test/xhr.test.ts b/packages/opentelemetry-instrumentation-xml-http-request/test/xhr.test.ts index 624ec33671..87896e8437 100644 --- a/packages/opentelemetry-instrumentation-xml-http-request/test/xhr.test.ts +++ b/packages/opentelemetry-instrumentation-xml-http-request/test/xhr.test.ts @@ -125,6 +125,36 @@ function createMainResource(resource = {}): PerformanceResourceTiming { return mainResource; } +function createFakePerformanceObs(url: string) { + class FakePerfObs implements PerformanceObserver { + constructor(private readonly cb: PerformanceObserverCallback) {} + observe() { + const absoluteUrl = url.startsWith('http') ? url : location.origin + url; + const resources: PerformanceObserverEntryList = { + getEntries(): PerformanceEntryList { + return [ + createResource({ name: absoluteUrl }) as any, + createMainResource({ name: absoluteUrl }) as any, + ]; + }, + getEntriesByName(): PerformanceEntryList { + return []; + }, + getEntriesByType(): PerformanceEntryList { + return []; + }, + }; + this.cb(resources, this); + } + disconnect() {} + takeRecords(): PerformanceEntryList { + return []; + } + } + + return FakePerfObs; +} + describe('xhr', () => { const asyncTests = [{ async: true }, { async: false }]; asyncTests.forEach(test => { @@ -200,6 +230,11 @@ describe('xhr', () => { 'getEntriesByType' ); spyEntries.withArgs('resource').returns(resources); + + sinon + .stub(window, 'PerformanceObserver') + .value(createFakePerformanceObs(fileUrl)); + xmlHttpRequestInstrumentation = new XMLHttpRequestInstrumentation( config ); @@ -221,7 +256,7 @@ describe('xhr', () => { rootSpan = webTracerWithZone.startSpan('root'); api.context.with(api.trace.setSpan(api.context.active(), rootSpan), () => { - getData( + void getData( new XMLHttpRequest(), fileUrl, () => { @@ -635,20 +670,11 @@ describe('xhr', () => { beforeEach(done => { requests = []; - const resources: PerformanceResourceTiming[] = []; - resources.push( - createResource({ - name: firstUrl, - }), - createResource({ - name: secondUrl, - }) - ); const reusableReq = new XMLHttpRequest(); api.context.with( api.trace.setSpan(api.context.active(), rootSpan), () => { - getData( + void getData( reusableReq, firstUrl, () => { @@ -665,7 +691,7 @@ describe('xhr', () => { api.context.with( api.trace.setSpan(api.context.active(), rootSpan), () => { - getData( + void getData( reusableReq, secondUrl, () => { @@ -728,6 +754,35 @@ describe('xhr', () => { assert.ok(attributes['xhr-custom-attribute'] === 'bar'); }); }); + + describe('when using relative url', () => { + beforeEach(done => { + clearData(); + const propagateTraceHeaderCorsUrls = [window.location.origin]; + prepareData(done, '/get', { propagateTraceHeaderCorsUrls }); + }); + + it('should create correct span with events', () => { + // no prefetch span because mock observer uses location.origin as url when relative + // and prefetch span finding compares url origins + const span: tracing.ReadableSpan = exportSpy.args[0][0][0]; + const events = span.events; + + assert.strictEqual( + exportSpy.args.length, + 1, + `Wrong number of spans: ${exportSpy.args.length}` + ); + + assert.strictEqual(events.length, 12, `number of events is wrong: ${events.length}`); + assert.strictEqual( + events[8].name, + PTN.REQUEST_START, + `event ${PTN.REQUEST_START} is not defined` + ); + }); + }); + }); describe('when request is NOT successful', () => { diff --git a/packages/opentelemetry-propagator-b3/src/B3Propagator.ts b/packages/opentelemetry-propagator-b3/src/B3Propagator.ts index 597873e36d..760752d84b 100644 --- a/packages/opentelemetry-propagator-b3/src/B3Propagator.ts +++ b/packages/opentelemetry-propagator-b3/src/B3Propagator.ts @@ -61,7 +61,10 @@ export class B3Propagator implements TextMapPropagator { } extract(context: Context, carrier: unknown, getter: TextMapGetter): Context { - if (getter.get(carrier, B3_CONTEXT_HEADER)) { + const header = getter.get(carrier, B3_CONTEXT_HEADER); + const b3Context = Array.isArray(header) ? header[0] : header; + + if (b3Context) { return this._b3SinglePropagator.extract(context, carrier, getter); } else { return this._b3MultiPropagator.extract(context, carrier, getter); diff --git a/packages/opentelemetry-propagator-b3/test/B3Propagator.test.ts b/packages/opentelemetry-propagator-b3/test/B3Propagator.test.ts index 6efec8336a..6e5a7b4a7c 100644 --- a/packages/opentelemetry-propagator-b3/test/B3Propagator.test.ts +++ b/packages/opentelemetry-propagator-b3/test/B3Propagator.test.ts @@ -152,6 +152,31 @@ describe('B3Propagator', () => { }); }); + it('extracts multi header b3 using array getter', () => { + const context = propagator.extract( + ROOT_CONTEXT, + b3MultiCarrier, + { + get(carrier, key) { + if (carrier == null || carrier[key] === undefined) { + return []; + } + return [carrier[key]]; + }, + + keys: defaultTextMapGetter.keys + } + ); + + const extractedSpanContext = trace.getSpanContext(context); + assert.deepStrictEqual(extractedSpanContext, { + spanId: '6e0c63257de34c92', + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + isRemote: true, + traceFlags: TraceFlags.SAMPLED, + }); + }); + it('extracts single header over multi', () => { const context = propagator.extract( ROOT_CONTEXT, diff --git a/packages/opentelemetry-resources/README.md b/packages/opentelemetry-resources/README.md index b6506a3583..b29aded79a 100644 --- a/packages/opentelemetry-resources/README.md +++ b/packages/opentelemetry-resources/README.md @@ -7,7 +7,7 @@ The OpenTelemetry Resource is an immutable representation of the entity producing telemetry. For example, a process producing telemetry that is running in a container on Kubernetes has a Pod name, it is in a namespace and possibly is part of a Deployment which also has a name. All three of these attributes can be included in the `Resource`. -[This document][resource-semantic_conventions] defines standard attributes for resources. +[This document][resource-semantic_conventions] defines standard attributes for resources which are accessible via [`@opentelemetry/semantic-conventions`](https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-semantic-conventions). ## Installation @@ -18,9 +18,11 @@ npm install --save @opentelemetry/resources ## Usage ```typescript -import { Resource, SERVICE_RESOURCE } from '@opentelemetry/resources'; +import { ResourceAttributes } from '@opentelemetry/semantic-conventions'; +import { Resource } from '@opentelemetry/resources'; + const resource = new Resource({ - [SERVICE_RESOURCE.NAME]: 'api-service', + [ResourceAttributes.SERVICE_NAME]: 'api-service', }); const another_resource = new Resource({ diff --git a/packages/opentelemetry-sdk-node/README.md b/packages/opentelemetry-sdk-node/README.md index 81a3bacc4a..312181551c 100644 --- a/packages/opentelemetry-sdk-node/README.md +++ b/packages/opentelemetry-sdk-node/README.md @@ -45,9 +45,7 @@ const { getNodeAutoInstrumentations, } = require("@opentelemetry/auto-instrumentations-node"); -const jaegerExporter = new JaegerExporter({ - serviceName: 'my-service', -}); +const jaegerExporter = new JaegerExporter(); const prometheusExporter = new PrometheusExporter({ startServer: true }); const sdk = new opentelemetry.NodeSDK({ @@ -126,7 +124,7 @@ Configure a custom sampler. By default all traces will be sampled. ### traceExporter -Configure a trace exporter. If an exporter OR span processor is not configured, the tracing SDK will not be initialized and registered. If an exporter is configured, it will be used with a [BatchSpanProcessor](../opentelemetry-tracing/src/export/BatchSpanProcessor.ts). +Configure a trace exporter. If an exporter OR span processor is not configured, the tracing SDK will not be initialized and registered. If an exporter is configured, it will be used with a [BatchSpanProcessor](../opentelemetry-tracing/src/platform/node/export/BatchSpanProcessor.ts). ### spanLimits diff --git a/packages/opentelemetry-shim-opentracing/package.json b/packages/opentelemetry-shim-opentracing/package.json index dc11565523..5d1e3c011c 100644 --- a/packages/opentelemetry-shim-opentracing/package.json +++ b/packages/opentelemetry-shim-opentracing/package.json @@ -57,6 +57,7 @@ }, "dependencies": { "@opentelemetry/core": "0.22.0", + "@opentelemetry/semantic-conventions": "0.22.0", "opentracing": "^0.14.4" } } diff --git a/packages/opentelemetry-shim-opentracing/src/shim.ts b/packages/opentelemetry-shim-opentracing/src/shim.ts index 0e4a890b6b..482fe9c56c 100644 --- a/packages/opentelemetry-shim-opentracing/src/shim.ts +++ b/packages/opentelemetry-shim-opentracing/src/shim.ts @@ -15,13 +15,9 @@ */ import * as api from '@opentelemetry/api'; +import { SpanAttributes, SpanAttributeValue, SpanStatusCode, TextMapPropagator } from '@opentelemetry/api'; import * as opentracing from 'opentracing'; -import { - SpanAttributes, - SpanAttributeValue, - SpanStatusCode, - TextMapPropagator, -} from '@opentelemetry/api'; +import { SemanticAttributes } from "@opentelemetry/semantic-conventions"; function translateReferences(references: opentracing.Reference[]): api.Link[] { const links: api.Link[] = []; @@ -96,14 +92,14 @@ export class SpanContextShim extends opentracing.SpanContext { /** * Returns the trace ID as a string. */ - override toTraceId(): string { + override toTraceId(): string { return this._spanContext.traceId; } /** * Returns the span ID as a string. */ - override toSpanId(): string { + override toSpanId(): string { return this._spanContext.spanId; } @@ -281,19 +277,62 @@ export class SpanShim extends opentracing.Span { * @param payload an arbitrary object to be attached to the event. */ override logEvent(eventName: string, payload?: SpanAttributes): void { - this._span.addEvent(eventName, payload); + this._logInternal(eventName, payload); } /** * Logs a set of key value pairs. Since OpenTelemetry only supports events, - * the KV pairs are used as attributes on an event named "log". + * the KV pairs are used as attributes on a Span event. + * @param keyValuePairs a set of key-value pairs to be used as event attributes + * @param timestamp optional timestamp for the event */ - override log(keyValuePairs: SpanAttributes, _timestamp?: number): this { - // @todo: Handle timestamp - this._span.addEvent('log', keyValuePairs); + override log(keyValuePairs: SpanAttributes, timestamp?: number): this { + const entries = Object.entries(keyValuePairs); + const eventEntry = entries.find(([key, _]) => key === 'event'); + const eventName = eventEntry?.[1] || 'log'; + const name = eventName.toString(); + + this._logInternal(name, keyValuePairs, timestamp); return this; } + private _logInternal(eventName: string, attributes: SpanAttributes | undefined, timestamp?: number): void { + if (attributes && eventName === 'error') { + const entries = Object.entries(attributes); + const errorEntry = entries.find(([key]) => key === 'error.object'); + const error = errorEntry?.[1]; + if (typeof error === "string") { + this._span.recordException(error, timestamp); + return; + } + + const mappedAttributes: api.SpanAttributes = {}; + for (const [k, v] of entries) { + switch (k) { + case "error.kind": { + mappedAttributes[SemanticAttributes.EXCEPTION_TYPE] = v; + break; + } + case "message": { + mappedAttributes[SemanticAttributes.EXCEPTION_MESSAGE] = v; + break; + } + case "stack": { + mappedAttributes[SemanticAttributes.EXCEPTION_STACKTRACE] = v; + break; + } + default: { + mappedAttributes[k] = v; + break; + } + } + } + this._span.addEvent('exception', mappedAttributes, timestamp); + return; + } + this._span.addEvent(eventName, attributes, timestamp); + } + /** * Adds a set of tags to the span. * @param keyValueMap set of KV pairs representing tags diff --git a/packages/opentelemetry-shim-opentracing/test/Shim.test.ts b/packages/opentelemetry-shim-opentracing/test/Shim.test.ts index c8bcd2f92b..882ebe3ca1 100644 --- a/packages/opentelemetry-shim-opentracing/test/Shim.test.ts +++ b/packages/opentelemetry-shim-opentracing/test/Shim.test.ts @@ -36,6 +36,7 @@ import { import { performance } from 'perf_hooks'; import { B3Propagator } from '@opentelemetry/propagator-b3'; import { JaegerPropagator } from '@opentelemetry/propagator-jaeger'; +import { SemanticAttributes } from "@opentelemetry/semantic-conventions"; describe('OpenTracing Shim', () => { const compositePropagator = new CompositePropagator({ @@ -355,18 +356,92 @@ describe('OpenTracing Shim', () => { }); }); - it('logs KV pairs', () => { - const kvLogs = { key: 'value', error: 'not a valid span' }; - span.log(kvLogs); - assert.strictEqual(otSpan.events[0].name, 'log'); - assert.strictEqual(otSpan.events[0].attributes, kvLogs); - }); + describe('logging', () => { + describe('event with payload', () => { + it('logs an event with a payload', () => { + const payload = { user: 'payload', request: 1 }; + span.logEvent('some log', payload); + assert.strictEqual(otSpan.events[0].name, 'some log'); + assert.deepStrictEqual(otSpan.events[0].attributes, payload); + }); + + it('records an exception', () => { + const payload = { + 'error.object': 'boom', fault: 'meow' + }; + span.logEvent('error', payload); + assert.strictEqual(otSpan.events[0].name, 'exception'); + const expectedAttributes = { + [SemanticAttributes.EXCEPTION_MESSAGE]: 'boom', + }; + assert.deepStrictEqual(otSpan.events[0].attributes, expectedAttributes); + }); + + it('maps to exception semantic conventions', () => { + const payload = { + fault: 'meow', 'error.kind': 'boom', message: 'oh no!', stack: 'pancakes' + }; + span.logEvent('error', payload); + assert.strictEqual(otSpan.events[0].name, 'exception'); + const expectedAttributes = { + fault: 'meow', + [SemanticAttributes.EXCEPTION_TYPE]: 'boom', + [SemanticAttributes.EXCEPTION_MESSAGE]: 'oh no!', + [SemanticAttributes.EXCEPTION_STACKTRACE]: 'pancakes' + }; + assert.deepStrictEqual(otSpan.events[0].attributes, expectedAttributes); + }); + }); + + describe('key-value pairs', () => { + const tomorrow = new Date().setDate(new Date().getDate() + 1); - it('logs an event with a payload', () => { - const payload = { user: 'payload', request: 1 }; - span.logEvent('some log', payload); - assert.strictEqual(otSpan.events[0].name, 'some log'); - assert.deepStrictEqual(otSpan.events[0].attributes, payload); + it('names event after event attribute', () => { + const kvLogs = { event: 'fun-time', user: 'meow', value: 123 }; + span.log(kvLogs, tomorrow); + assert.strictEqual(otSpan.events[0].name, 'fun-time'); + assert.strictEqual(otSpan.events[0].time[0], Math.trunc(tomorrow / 1000)); + assert.strictEqual(otSpan.events[0].attributes, kvLogs); + }); + + it('names event log, as a fallback', () => { + const kvLogs = { user: 'meow', value: 123 }; + span.log(kvLogs, tomorrow); + assert.strictEqual(otSpan.events[0].name, 'log'); + assert.strictEqual(otSpan.events[0].time[0], Math.trunc(tomorrow / 1000)); + assert.strictEqual(otSpan.events[0].attributes, kvLogs); + }); + + it('records an exception', () => { + const kvLogs = { + event: 'error', 'error.object': 'boom', fault: 'meow' + }; + span.log(kvLogs, tomorrow); + assert.strictEqual(otSpan.events[0].name, 'exception'); + assert.strictEqual(otSpan.events[0].time[0], Math.trunc(tomorrow / 1000)); + const expectedAttributes = { + [SemanticAttributes.EXCEPTION_MESSAGE]: 'boom', + }; + assert.deepStrictEqual(otSpan.events[0].attributes, expectedAttributes); + }); + + it('maps to exception semantic conventions', () => { + const kvLogs = { + event: 'error', fault: 'meow', 'error.kind': 'boom', message: 'oh no!', stack: 'pancakes' + }; + span.log(kvLogs, tomorrow); + assert.strictEqual(otSpan.events[0].name, 'exception'); + assert.strictEqual(otSpan.events[0].time[0], Math.trunc(tomorrow / 1000)); + const expectedAttributes = { + event: 'error', + fault: 'meow', + [SemanticAttributes.EXCEPTION_TYPE]: 'boom', + [SemanticAttributes.EXCEPTION_MESSAGE]: 'oh no!', + [SemanticAttributes.EXCEPTION_STACKTRACE]: 'pancakes' + }; + assert.deepStrictEqual(otSpan.events[0].attributes, expectedAttributes); + }); + }); }); it('updates the name', () => { diff --git a/packages/opentelemetry-shim-opentracing/tsconfig.json b/packages/opentelemetry-shim-opentracing/tsconfig.json index 343988c19f..8fbd0e4307 100644 --- a/packages/opentelemetry-shim-opentracing/tsconfig.json +++ b/packages/opentelemetry-shim-opentracing/tsconfig.json @@ -18,6 +18,9 @@ { "path": "../opentelemetry-propagator-jaeger" }, + { + "path": "../opentelemetry-semantic-conventions" + }, { "path": "../opentelemetry-tracing" } diff --git a/packages/opentelemetry-web/src/utils.ts b/packages/opentelemetry-web/src/utils.ts index af9d15c9d3..ccfb34d7d8 100644 --- a/packages/opentelemetry-web/src/utils.ts +++ b/packages/opentelemetry-web/src/utils.ts @@ -30,13 +30,13 @@ import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; // Used to normalize relative URLs let a: HTMLAnchorElement | undefined; -const getUrlNormalizingAnchor = () => { +export function getUrlNormalizingAnchor(): HTMLAnchorElement { if (!a) { a = document.createElement('a'); } return a; -}; +} /** * Helper function to be able to use enum as typed key in type and in interface when using forEach @@ -155,7 +155,7 @@ export function getResource( mainRequest: filteredResources[0], }; } - const sorted = sortResources(filteredResources.slice()); + const sorted = sortResources(filteredResources); const parsedSpanUrl = parseUrl(spanUrl); if (parsedSpanUrl.origin !== window.location.origin && sorted.length > 1) { diff --git a/renovate.json b/renovate.json index debd9bceb4..208c80be86 100644 --- a/renovate.json +++ b/renovate.json @@ -9,6 +9,11 @@ { "matchPackageNames": ["@opentelemetry/api"], "rangeStrategy": "bump" + }, + { + "matchPaths": ["backwards-compatibility/**"], + "matchPackageNames": ["@types/node"], + "enabled": false } ], "ignoreDeps": ["gcp-metadata", "got", "mocha", "husky", "karma-webpack"], diff --git a/website_docs/_index.md b/website_docs/_index.md index 8124c87cea..1f8bed9e7d 100644 --- a/website_docs/_index.md +++ b/website_docs/_index.md @@ -12,9 +12,10 @@ export data. ## Status and Releases -| Tracing | Metrics | -| ------- | ------- | -| Beta | Alpha | +| | Tracing | Metrics | +| ----- | ------- | ------- | +| API | GA | Alpha | +| SDK | Beta | Alpha | You can find release information [here](https://github.com/open-telemetry/opentelemetry-js/releases) diff --git a/website_docs/getting_started/browser.md b/website_docs/getting_started/browser.md index 876a776f12..42a451e526 100644 --- a/website_docs/getting_started/browser.md +++ b/website_docs/getting_started/browser.md @@ -69,17 +69,24 @@ Add the following code to the `document-load.js` to create a tracer provider, wh ```javascript // This is necessary for "parcel" to work OOTB. It is not needed for other build tools. import 'regenerator-runtime/runtime' -import { LogLevel } from "@opentelemetry/core"; import { WebTracerProvider } from '@opentelemetry/web'; import { DocumentLoad } from '@opentelemetry/plugin-document-load'; +import { ZoneContextManager } from '@opentelemetry/context-zone'; +import { registerInstrumentations } from '@opentelemetry/instrumentation'; -// Minimum required setup - supports only synchronous operations -const provider = new WebTracerProvider({ - plugins: [ - new DocumentLoad() - ] +const provider = new WebTracerProvider(); + +provider.register({ + // Changing default contextManager to use ZoneContextManager - supports asynchronous operations - optional + contextManager: new ZoneContextManager(), +}); + +// Registering instrumentations / plugins +registerInstrumentations({ + instrumentations: [ + new DocumentLoad(), + ], }); -provider.register(); ``` Run `parcel index.html` and open the development webserver (e.g. at `http://localhost:1234`) to see if your code works. @@ -93,19 +100,26 @@ To export traces, modify `document-load.js` so that it matches the following cod ```javascript // This is necessary for "parcel" to work OOTB. It is not needed for other build tools. import 'regenerator-runtime/runtime' -import { LogLevel } from "@opentelemetry/core"; +import { ConsoleSpanExporter, SimpleSpanProcessor } from '@opentelemetry/tracing'; import { WebTracerProvider } from '@opentelemetry/web'; import { DocumentLoad } from '@opentelemetry/plugin-document-load'; -import { ConsoleSpanExporter, SimpleSpanProcessor } from '@opentelemetry/tracing'; +import { ZoneContextManager } from '@opentelemetry/context-zone'; +import { registerInstrumentations } from '@opentelemetry/instrumentation'; -// Minimum required setup - supports only synchronous operations -const provider = new WebTracerProvider({ - plugins: [ - new DocumentLoad() - ] +const provider = new WebTracerProvider(); +provider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter())); + +provider.register({ + // Changing default contextManager to use ZoneContextManager - supports asynchronous operations - optional + contextManager: new ZoneContextManager(), +}); + +// Registering instrumentations / plugins +registerInstrumentations({ + instrumentations: [ + new DocumentLoad(), + ], }); -provider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter())) -provider.register(); ``` Now, rebuild your application and open the browser again. In the console of the developer toolbar you should see some traces being exporterd: diff --git a/website_docs/getting_started/nodejs.md b/website_docs/getting_started/nodejs.md index 9bd45090c9..0cc5278071 100644 --- a/website_docs/getting_started/nodejs.md +++ b/website_docs/getting_started/nodejs.md @@ -3,21 +3,41 @@ title: "Node.JS" weight: 2 --- -This guide uses the example application in node.js provided below, but the steps to instrument your own application should be broadly the same. Here is an overview of what we will be doing. +This guide will show you how to get started with tracing in Node.js. -- Install the required OpenTelemetry libraries -- Initialize a global [tracer](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#tracer) -- Initialize and register a [span exporter](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#span-exporter) +- [Example Application](#example-application) + - [Dependencies](#dependencies) + - [Code](#code) +- [Tracing](#tracing) + - [Dependencies](#dependencies-1) + - [Core Dependencies](#core-dependencies) + - [Exporter](#exporter) + - [Instrumentation Modules](#instrumentation-modules) + - [Setup](#setup) + - [Run Application](#run-application) -Copy the following file into an empty directory and call it `app.js`. +## Example Application -```javascript -"use strict"; +This is a small example application we will monitor in this guide. -const PORT = process.env.PORT || "8080"; +### Dependencies + +Install dependencies used by the example. + +```sh +npm install express +``` + +### Code + +Please save the following code as `app.js`. + +```javascript +/* app.js */ const express = require("express"); +const PORT = process.env.PORT || "8080"; const app = express(); app.get("/", (req, res) => { @@ -29,172 +49,104 @@ app.listen(parseInt(PORT, 10), () => { }); ``` -Run `npm install express` to have all dependencies available. - -## Installation - -To create traces on NodeJS, you will need `@opentelemetry/node`, `@opentelemetry/core`, and any plugins required by your application such as gRPC, or HTTP. If you are using the example application, you will need to install `@opentelemetry/plugin-http`, `@opentelemetry/plugin-https` and `@opentelemetry/plugin-express`. +Run the application with the following request and open in your web browser to ensure it is working. ```sh -$ npm install \ - @opentelemetry/core \ - @opentelemetry/node \ - @opentelemetry/plugin-http \ - @opentelemetry/plugin-https \ - @opentelemetry/plugin-express \ - @opentelemetry/metrics \ - @opentelemetry/tracing +$ node app.js +Listening for requests on http://localhost:8080 ``` -## Initialization and Configuration +## Tracing -All tracing initialization should happen before your application’s code runs. The easiest way to do this is to initialize tracing in a separate file that is required using node’s `-r` option before application code runs. +### Dependencies -## Creating a Tracer Provider +The following dependencies are required to trace a Node.js application. -Create a file named `tracing.js` and add the following code to create a tracer provider: +#### Core Dependencies -```javascript -'use strict'; +These dependencies are required to configure the tracing SDK and create spans. -const { LogLevel } = require("@opentelemetry/core"); -const { NodeTracerProvider } = require("@opentelemetry/node"); +- `@opentelemetry/api` +- `@opentelemetry/node` +- `@opentelemetry/tracing` -const provider = new NodeTracerProvider({ - logLevel: LogLevel.ERROR -}); +#### Exporter -provider.register(); -``` +In order to visualize and analyze your traces, you will need to export them to a tracing backend such as Jaeger. OpenTelemetry JS provides exporters for some common open source tracing backends. -If you run your application now with `node -r ./tracing.js app.js`, your application will create and propagate traces over HTTP. If an already instrumented service that supports [Trace Context](https://www.w3.org/TR/trace-context/) headers calls your application using HTTP, and you call another application using HTTP, the Trace Context headers will be correctly propagated. +- Jaeger - `@opentelemetry/exporter-jaeger` +- Zipkin - `@opentelemetry/exporter-zipkin` +- OpenTelemetry Protocol + - GRPC - `@opentelemetry/exporter-collector-grpc` + - Protobuf/HTTP - `@opentelemetry/exporter-collector-proto` + - JSON/HTTP - `@opentelemetry/exporter-collector` -If you wish to see a completed trace, however, there is one more step. You must register an exporter. +#### Instrumentation Modules -## Creating a Metric Provider +Many common modules such as the `http` standard library module, `express`, and others can be automatically instrumented using autoinstrumentation modules. To find autoinstrumenatation modules, you can look at the [registry](https://opentelemetry.io/registry/?language=js&component=instrumentation#). -In order to create and monitor metrics, we will need a `Meter`. In OpenTelemetry, a `Meter` is the mechanism used to create and manage metrics, labels, and metric exporters. +You can also install all instrumentations maintained by the OpenTelemetry authors by using the `@opentelemetry/auto-instrumentations-node` module. -Create a file named `monitoring.js` and add the following code: +### Setup -```javascript -'use strict'; +The tracing setup and configuration should be run before your application code. One tool commonly used for this task is the [`-r, --require module`](https://nodejs.org/api/cli.html#cli_r_require_module) flag. -const { MeterProvider } = require('@opentelemetry/metrics'); - -const meter = new MeterProvider().getMeter('your-meter-name'); -``` - -Now, you can require this file from your application code and use the `Meter` to create and manage metrics. The simplest of these metrics is a counter. Let's create and export from our `monitoring.js` file a middleware function that express can use to count all requests by route. Modify the `monitoring.js` file so that it looks like this: - -```javascript -'use strict'; - -const { MeterProvider } = require('@opentelemetry/metrics'); - -const meter = new MeterProvider().getMeter('your-meter-name'); - -const requestCount = meter.createCounter("requests", { - description: "Count all incoming requests" -}); - -const boundInstruments = new Map(); - -module.exports.countAllRequests = () => { - return (req, res, next) => { - if (!boundInstruments.has(req.path)) { - const labels = { route: req.path }; - const boundCounter = requestCount.bind(labels); - boundInstruments.set(req.path, boundCounter); - } - - boundInstruments.get(req.path).add(1); - next(); - }; -}; -``` - -Now let's import and use this middleware in our application code: +Create a file with a name like `tracing.js` which will contain your tracing setup code. In this example, we will use the `ConsoleSpanExporter` which prints all spans to the console. In your application, you should use the exporter which goes with the tracing backend of your choice. You may also want to use the `BatchSpanProcessor` to export spans in batches in order to more efficiently use resources. ```javascript -const { countAllRequests } = require("./monitoring"); -const app = express(); -app.use(countAllRequests()); -``` - -Now, when we make requests (e.g. `curl http://localhost:8080`) to our service our meter will count all requests. - -**Note**: Creating a new `labelSet` and `binding` on every request is not ideal as creating the `labelSet` can often be an expensive operation. This is why instruments are created and stored in a `Map` according to the route key. +/* tracing.js */ -## Creating a Console Exporter - -To export traces, modify `tracing.js` so that it matches the following code snippet: - -```javascript -'use strict'; - -const { LogLevel } = require("@opentelemetry/core"); +// Require dependencies const { NodeTracerProvider } = require("@opentelemetry/node"); const { SimpleSpanProcessor, ConsoleSpanExporter } = require("@opentelemetry/tracing"); +const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node'); +const { registerInstrumentations } = require('@opentelemetry/instrumentation'); -const provider = new NodeTracerProvider({ - logLevel: LogLevel.ERROR -}); - -provider.register(); - -provider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter())); - -console.log("tracing initialized"); -``` - -To export metrics, modify `monitoring.js` so that it matches the following code snippet: +// Create a tracer provider +const provider = new NodeTracerProvider(); -```javascript -'use strict'; +// The exporter handles sending spans to your tracing backend +const exporter = new ConsoleSpanExporter(); -const { MeterProvider, ConsoleMetricExporter } = require('@opentelemetry/metrics'); +// The simple span processor sends spans to the exporter as soon as they are ended. +const processor = new SimpleSpanProcessor(exporter); +provider.addSpanProcessor(processor); -const exporter = new ConsoleMetricExporter() - -const meter = new MeterProvider({ - exporter, - interval: 5000 -}).getMeter('your-meter-name'); +// The provider must be registered in order to +// be used by the OpenTelemetry API and instrumentations +provider.register(); -const requestCount = meter.createCounter("requests", { - description: "Count all incoming requests" +// This will automatically enable all instrumentations +registerInstrumentations({ + instrumentations: [getNodeAutoInstrumentations()], }); +``` -const boundInstruments = new Map(); +### Run Application -module.exports.countAllRequests = () => { - return (req, res, next) => { - if (!boundInstruments.has(req.path)) { - const labels = { route: req.path }; - const boundCounter = requestCount.bind(labels); - boundInstruments.set(req.path, boundCounter); - } +Now you can run your application as you normally would, but you can use the `--require` flag to load the tracing code before the application code. - boundInstruments.get(req.path).add(1); - next(); - }; -}; +```sh +$ node --require './tracing.js' app.js +Listening for requests on http://localhost:8080 ``` -Now, restart your application and add some load, you will see traces & metrics printed to your console: +Now, when you open in your web browser, you should see the spans printed in the console by the `ConsoleSpanExporter`. -```javascript +
+View example output + +```json { - traceId: 'f27805526b1c74293bbc9345cd48ff3b', - parentId: 'd6bdf2a18df04ef0', + traceId: '3f1fe6256ea46d19ec3ca97b3409ad6d', + parentId: 'f0b7b340dd6e08a7', name: 'middleware - query', - id: '36335b81de12cc4a', + id: '41a27f331c7bfed3', kind: 0, - timestamp: 1603789083744612, - duration: 365, + timestamp: 1624982589722992, + duration: 417, attributes: { - component: 'express', + 'http.route': '/', 'express.name': 'query', 'express.type': 'middleware' }, @@ -202,87 +154,65 @@ Now, restart your application and add some load, you will see traces & metrics p events: [] } { - name: 'requests', - description: 'Count all incoming requests', - unit: '1', - metricKind: 0, - valueType: 1 + traceId: '3f1fe6256ea46d19ec3ca97b3409ad6d', + parentId: 'f0b7b340dd6e08a7', + name: 'middleware - expressInit', + id: 'e0ed537a699f652a', + kind: 0, + timestamp: 1624982589725778, + duration: 673, + attributes: { + 'http.route': '/', + 'express.name': 'expressInit', + 'express.type': 'middleware' + }, + status: { code: 0 }, + events: [] +} +{ + traceId: '3f1fe6256ea46d19ec3ca97b3409ad6d', + parentId: 'f0b7b340dd6e08a7', + name: 'request handler - /', + id: '8614a81e1847b7ef', + kind: 0, + timestamp: 1624982589726941, + duration: 21, + attributes: { + 'http.route': '/', + 'express.name': '/', + 'express.type': 'request_handler' + }, + status: { code: 0 }, + events: [] +} +{ + traceId: '3f1fe6256ea46d19ec3ca97b3409ad6d', + parentId: undefined, + name: 'GET /', + id: 'f0b7b340dd6e08a7', + kind: 1, + timestamp: 1624982589720260, + duration: 11380, + attributes: { + 'http.url': 'http://localhost:8080/', + 'http.host': 'localhost:8080', + 'net.host.name': 'localhost', + 'http.method': 'GET', + 'http.route': '', + 'http.target': '/', + 'http.user_agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36', + 'http.flavor': '1.1', + 'net.transport': 'ip_tcp', + 'net.host.ip': '::1', + 'net.host.port': 8080, + 'net.peer.ip': '::1', + 'net.peer.port': 61520, + 'http.status_code': 304, + 'http.status_text': 'NOT MODIFIED' + }, + status: { code: 1 }, + events: [] } -{ route: '/' } -value: 1 ``` -If you'd like to write those traces and spanes to Zipkin or Prometheus follow the [complete guide](https://github.com/open-telemetry/opentelemetry-js/blob/main/getting-started/README.md). - -## Quick Start - -To have everything up and running in a few seconds, create an empty directory and create the following files: - -- package.json - - ```json - { - "dependencies": { - "@opentelemetry/core": "^0.12.0", - "@opentelemetry/metrics": "^0.12.0", - "@opentelemetry/node": "^0.12.0", - "@opentelemetry/plugin-express": "^0.10.0", - "@opentelemetry/plugin-http": "^0.12.0", - "@opentelemetry/plugin-https": "^0.12.0", - "express": "^4.17.1" - } - } - ``` - -- app.js - - ```javascript - "use strict"; - const PORT = process.env.PORT || "8080"; - const express = require("express"); - const app = express(); - const { countAllRequests } = require("./monitoring"); - app.use(countAllRequests()); - app.get("/", (req, res) => { res.send("Hello World"); }); - app.listen(parseInt(PORT, 10), () => { console.log(`Listening for requests on http://localhost:${PORT}`); }); - ``` - -- tracing.js - - ```javascript - 'use strict'; - const { LogLevel } = require("@opentelemetry/core"); - const { NodeTracerProvider } = require("@opentelemetry/node"); - const { SimpleSpanProcessor, ConsoleSpanExporter } = require("@opentelemetry/tracing"); - const provider = new NodeTracerProvider({ logLevel: LogLevel.ERROR }); - provider.register(); - provider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter())); - console.log("tracing initialized"); - ``` - -- monitoring.js - - ```javascript - 'use strict'; - const { MeterProvider, ConsoleMetricExporter } = require('@opentelemetry/metrics'); - const exporter = new ConsoleMetricExporter() - const meter = new MeterProvider({ - exporter, - interval: 5000 - }).getMeter('your-meter-name'); - const requestCount = meter.createCounter("requests", { description: "Count all incoming requests" }); - const boundInstruments = new Map(); - module.exports.countAllRequests = () => { - return (req, res, next) => { - if (!boundInstruments.has(req.path)) { - const labels = { route: req.path }; - const boundCounter = requestCount.bind(labels); - boundInstruments.set(req.path, boundCounter); - } - boundInstruments.get(req.path).add(1); - next(); - }; - }; - ``` - -Run `npm install` and `node -r ./tracing.js app.js` and add some load to the app, e.g. `curl http://localhost:8080` +