diff --git a/.github/workflows/label-releases.yml b/.github/workflows/label-releases.yml new file mode 100644 index 0000000000..29c5357f71 --- /dev/null +++ b/.github/workflows/label-releases.yml @@ -0,0 +1,12 @@ +on: + pull_request_target: + branches: + - main + +jobs: + label-release: + if: ${{ startsWith(github.event.pull_request.title, 'release:') }} + runs-on: ubuntu-latest + steps: + - run: echo this is a release PR + - run: gh pr edit ${{ github.event.pull_request.number }} --add-label release diff --git a/.github/workflows/peer-api.yml b/.github/workflows/peer-api.yml index ac90efe981..e6f52cccd7 100644 --- a/.github/workflows/peer-api.yml +++ b/.github/workflows/peer-api.yml @@ -11,7 +11,7 @@ jobs: peer-api-check: runs-on: ubuntu-latest container: - image: node:18 + image: node:20 steps: - name: Checkout uses: actions/checkout@v3 diff --git a/CHANGELOG.md b/CHANGELOG.md index 07bd03fa7a..f827e31585 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,17 +11,25 @@ For experimental package changes, see the [experimental CHANGELOG](experimental/ ### :rocket: (Enhancement) -* feat(core): add environment variables for OTLP log exporters. [#3712](https://github.com/open-telemetry/opentelemetry-js/pull/3712/) @llc1123 +* feat(SpanExpoter): Add optional forceFlush to SpanExporter interface [#3753](https://github.com/open-telemetry/opentelemetry-js/pull/3753/) @sgracias1 @JacksonWeber ### :bug: (Bug Fix) -* fix(http-instrumentation): stop listening to `request`'s `close` event once it has emitted `response` [#3625](https://github.com/open-telemetry/opentelemetry-js/pull/3625) @SimenB -* fix(sdk-node): fix initialization in bundled environments by not loading @opentelemetry/exporter-jaeger [#3739](https://github.com/open-telemetry/opentelemetry-js/pull/3739) @pichlermarc - ### :books: (Refine Doc) ### :house: (Internal) +## 1.13.0 + +### :rocket: (Enhancement) + +* feat(core): add environment variables for OTLP log exporters. [#3712](https://github.com/open-telemetry/opentelemetry-js/pull/3712/) @llc1123 + +### :bug: (Bug Fix) + +* fix(http-instrumentation): stop listening to `request`'s `close` event once it has emitted `response` [#3625](https://github.com/open-telemetry/opentelemetry-js/pull/3625) @SimenB +* fix(sdk-node): fix initialization in bundled environments by not loading @opentelemetry/exporter-jaeger [#3739](https://github.com/open-telemetry/opentelemetry-js/pull/3739) @pichlermarc + ## 1.12.0 ### :rocket: (Enhancement) diff --git a/README.md b/README.md index 9daf9d7385..ca72796747 100644 --- a/README.md +++ b/README.md @@ -188,6 +188,8 @@ We have a weekly SIG meeting! See the [community page](https://github.com/open-t - [Gerhard Stöbich](https://github.com/Flarna), Dynatrace - [Haddas Bronfman](https://github.com/haddasbronfman), Cisco +- [Hector Hernandez](https://github.com/hectorhdzg), Microsoft +- [Jamie Danielson](https://github.com/JamieDanielson), Honeycomb - [John Bley](https://github.com/johnbley), Splunk - [Mark Wolff](https://github.com/markwolff), Microsoft - [Martin Kuba](https://github.com/martinkuba), Lightstep @@ -195,6 +197,7 @@ We have a weekly SIG meeting! See the [community page](https://github.com/open-t - [Naseem K. Ullah](https://github.com/naseemkullah), Transit - [Neville Wylie](https://github.com/MSNev), Microsoft - [Olivier Albertini](https://github.com/OlivierAlbertini), Ville de Montréal +- [Purvi Kanal](https://github.com/pkanal), Honeycomb - [Svetlana Brennan](https://github.com/svetlanabrennan), New Relic *Find more about the approver role in [community repository](https://github.com/open-telemetry/community/blob/main/community-membership.md#approver).* diff --git a/examples/README.md b/examples/README.md index c594e5f442..dcef5e8d8e 100644 --- a/examples/README.md +++ b/examples/README.md @@ -17,6 +17,7 @@ use the latest and greatest features, and best practices. | [grpc](grpc/) | gRPC Instrumentation to automatically collect trace data and export them to the backend of choice | Intermediate | | [otlp-exporter-node](otlp-exporter-node/) | This example shows how to use `@opentelemetry/exporter-otlp-http` to instrument a simple Node.js application | Intermediate | | [opentracing-shim](opentracing-shim/) | This is a simple example that demonstrates how existing OpenTracing instrumentation can be integrated with OpenTelemetry | Intermediate | +| [esm-http-ts](esm-http-ts/) | This is a simple example that demonstrates tracing HTTP request, with an app written in TypeScript and transpiled to ES Modules. | Intermediate | Examples of experimental packages can be found at [experimental/examples](../experimental/examples). diff --git a/examples/esm-http-ts/README.md b/examples/esm-http-ts/README.md new file mode 100644 index 0000000000..72f0e2dbf4 --- /dev/null +++ b/examples/esm-http-ts/README.md @@ -0,0 +1,25 @@ +# Overview + +This is a simple example that demonstrates tracing HTTP request, with an app written in TypeScript and transpiled to ES Modules. + +## Installation + +```sh +# from this directory +npm install +npm run build +npm start +``` + +In a separate terminal, `curl localhost:3000`. + +See two spans in the console (one manual, one for http instrumentation) + +## Useful links + +- For more information on OpenTelemetry, visit: +- For more information on OpenTelemetry for Node.js, visit: + +## LICENSE + +Apache License 2.0 diff --git a/examples/esm-http-ts/index.ts b/examples/esm-http-ts/index.ts new file mode 100644 index 0000000000..b4719a8bc7 --- /dev/null +++ b/examples/esm-http-ts/index.ts @@ -0,0 +1,43 @@ +import { registerInstrumentations } from '@opentelemetry/instrumentation'; +import { trace, DiagConsoleLogger, DiagLogLevel, diag } from '@opentelemetry/api'; +import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'; +import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'; +import { + ConsoleSpanExporter, + SimpleSpanProcessor, +} from '@opentelemetry/sdk-trace-base'; +import { Resource } from '@opentelemetry/resources'; +import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; +import http from 'http'; + +diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG); +const tracerProvider = new NodeTracerProvider({ + resource: new Resource({ + [SemanticResourceAttributes.SERVICE_NAME]: 'esm-http-ts-example', + }), +}); +const exporter = new ConsoleSpanExporter(); +const processor = new SimpleSpanProcessor(exporter); +tracerProvider.addSpanProcessor(processor); +tracerProvider.register(); + +registerInstrumentations({ + instrumentations: [new HttpInstrumentation()], +}); + +const hostname = '0.0.0.0'; +const port = 3000; + +const server = http.createServer((req, res) => { + res.statusCode = 200; + res.setHeader('Content-Type', 'text/plain'); + const tracer = trace.getTracer('esm-tracer'); + tracer.startActiveSpan('manual', span => { + span.end(); + }); + res.end('Hello, World!\n'); +}); + +server.listen(port, hostname, () => { + console.log(`Server running at http://${hostname}:${port}/`); +}); diff --git a/examples/esm-http-ts/package.json b/examples/esm-http-ts/package.json new file mode 100644 index 0000000000..49c0e2601f --- /dev/null +++ b/examples/esm-http-ts/package.json @@ -0,0 +1,42 @@ +{ + "name": "esm-http-ts", + "private": true, + "version": "0.38.0", + "description": "Example of HTTP integration with OpenTelemetry using ESM and TypeScript", + "main": "build/index.js", + "type": "module", + "scripts": { + "build": "tsc --build", + "start": "node --experimental-loader=@opentelemetry/instrumentation/hook.mjs ./build/index.js" + }, + "repository": { + "type": "git", + "url": "git+ssh://git@github.com/open-telemetry/opentelemetry-js.git" + }, + "keywords": [ + "opentelemetry", + "http", + "tracing", + "esm", + "typescript" + ], + "engines": { + "node": ">=14" + }, + "author": "OpenTelemetry Authors", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/open-telemetry/opentelemetry-js/issues" + }, + "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/examples/", + "dependencies": { + "@opentelemetry/api": "1.4.0", + "@opentelemetry/exporter-trace-otlp-proto": "0.38.0", + "@opentelemetry/instrumentation": "0.38.0", + "@opentelemetry/instrumentation-http": "0.38.0", + "@opentelemetry/resources": "1.9.1", + "@opentelemetry/sdk-trace-base": "1.9.1", + "@opentelemetry/sdk-trace-node": "1.9.1", + "@opentelemetry/semantic-conventions": "1.9.1" + } +} diff --git a/examples/esm-http-ts/tsconfig.json b/examples/esm-http-ts/tsconfig.json new file mode 100644 index 0000000000..5f821d66c7 --- /dev/null +++ b/examples/esm-http-ts/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + /* Language and Environment */ + "target": "ES2020" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + + /* Modules */ + "module": "ESNext" /* Specify what module code is generated. */, + "rootDir": "." /* Specify the root folder within your source files. */, + "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */, + "resolveJsonModule": true /* Enable importing .json files. */, + + /* Emit */ + "outDir": "build" /* Specify an output folder for all emitted files. */, + + /* Interop Constraints */ + "allowSyntheticDefaultImports": true /* Allow 'import x from y' when a module doesn't have a default export. */, + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, + + /* Completeness */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + }, + "include": ["**/*.ts", "**/*.js", "*.config.js"], + "exclude": ["node_modules"] +} diff --git a/examples/http/package.json b/examples/http/package.json index 50ea5401f1..e06dd1af2f 100644 --- a/examples/http/package.json +++ b/examples/http/package.json @@ -1,7 +1,7 @@ { "name": "http-example", "private": true, - "version": "0.37.0", + "version": "0.39.1", "description": "Example of HTTP integration with OpenTelemetry", "main": "index.js", "scripts": { @@ -29,14 +29,14 @@ }, "dependencies": { "@opentelemetry/api": "^1.3.0", - "@opentelemetry/exporter-jaeger": "1.9.1", - "@opentelemetry/exporter-zipkin": "1.9.1", - "@opentelemetry/instrumentation": "0.35.1", - "@opentelemetry/instrumentation-http": "0.35.1", - "@opentelemetry/resources": "1.9.1", - "@opentelemetry/sdk-trace-base": "1.9.1", - "@opentelemetry/sdk-trace-node": "1.9.1", - "@opentelemetry/semantic-conventions": "1.9.1" + "@opentelemetry/exporter-jaeger": "1.13.0", + "@opentelemetry/exporter-zipkin": "1.13.0", + "@opentelemetry/instrumentation": "0.39.1", + "@opentelemetry/instrumentation-http": "0.39.1", + "@opentelemetry/resources": "1.13.0", + "@opentelemetry/sdk-trace-base": "1.13.0", + "@opentelemetry/sdk-trace-node": "1.13.0", + "@opentelemetry/semantic-conventions": "1.13.0" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/examples/http", "devDependencies": { diff --git a/examples/https/package.json b/examples/https/package.json index b13422bea9..62b65273f2 100644 --- a/examples/https/package.json +++ b/examples/https/package.json @@ -1,7 +1,7 @@ { "name": "https-example", "private": true, - "version": "0.38.0", + "version": "0.39.1", "description": "Example of HTTPs integration with OpenTelemetry", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -33,14 +33,14 @@ }, "dependencies": { "@opentelemetry/api": "^1.0.0", - "@opentelemetry/exporter-jaeger": "1.12.0", - "@opentelemetry/exporter-zipkin": "1.12.0", - "@opentelemetry/instrumentation": "0.38.0", - "@opentelemetry/instrumentation-http": "0.38.0", - "@opentelemetry/resources": "1.12.0", - "@opentelemetry/sdk-trace-base": "1.12.0", - "@opentelemetry/sdk-trace-node": "1.12.0", - "@opentelemetry/semantic-conventions": "1.12.0" + "@opentelemetry/exporter-jaeger": "1.13.0", + "@opentelemetry/exporter-zipkin": "1.13.0", + "@opentelemetry/instrumentation": "0.39.1", + "@opentelemetry/instrumentation-http": "0.39.1", + "@opentelemetry/resources": "1.13.0", + "@opentelemetry/sdk-trace-base": "1.13.0", + "@opentelemetry/sdk-trace-node": "1.13.0", + "@opentelemetry/semantic-conventions": "1.13.0" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/examples/https", "devDependencies": { diff --git a/examples/opentelemetry-web/package.json b/examples/opentelemetry-web/package.json index 105d86b649..c4c75e460c 100644 --- a/examples/opentelemetry-web/package.json +++ b/examples/opentelemetry-web/package.json @@ -1,7 +1,7 @@ { "name": "web-opentelemetry-example", "private": true, - "version": "0.38.0", + "version": "0.39.1", "description": "Example of using @opentelemetry/sdk-trace-web and @opentelemetry/sdk-metrics in browser", "main": "index.js", "scripts": { @@ -43,20 +43,20 @@ }, "dependencies": { "@opentelemetry/api": "^1.3.0", - "@opentelemetry/context-zone": "1.12.0", - "@opentelemetry/core": "1.12.0", - "@opentelemetry/exporter-metrics-otlp-http": "0.38.0", - "@opentelemetry/exporter-trace-otlp-http": "0.38.0", - "@opentelemetry/exporter-trace-otlp-proto": "0.38.0", - "@opentelemetry/exporter-zipkin": "1.12.0", - "@opentelemetry/instrumentation": "0.38.0", - "@opentelemetry/instrumentation-fetch": "0.38.0", - "@opentelemetry/instrumentation-xml-http-request": "0.38.0", - "@opentelemetry/propagator-b3": "1.12.0", - "@opentelemetry/sdk-metrics": "1.12.0", - "@opentelemetry/sdk-trace-base": "1.12.0", - "@opentelemetry/sdk-trace-web": "1.12.0", - "@opentelemetry/semantic-conventions": "1.12.0" + "@opentelemetry/context-zone": "1.13.0", + "@opentelemetry/core": "1.13.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.39.1", + "@opentelemetry/exporter-trace-otlp-http": "0.39.1", + "@opentelemetry/exporter-trace-otlp-proto": "0.39.1", + "@opentelemetry/exporter-zipkin": "1.13.0", + "@opentelemetry/instrumentation": "0.39.1", + "@opentelemetry/instrumentation-fetch": "0.39.1", + "@opentelemetry/instrumentation-xml-http-request": "0.39.1", + "@opentelemetry/propagator-b3": "1.13.0", + "@opentelemetry/sdk-metrics": "1.13.0", + "@opentelemetry/sdk-trace-base": "1.13.0", + "@opentelemetry/sdk-trace-web": "1.13.0", + "@opentelemetry/semantic-conventions": "1.13.0" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/examples/tracer-web" } diff --git a/examples/otlp-exporter-node/package.json b/examples/otlp-exporter-node/package.json index e5e6b21974..c19ae9ea59 100644 --- a/examples/otlp-exporter-node/package.json +++ b/examples/otlp-exporter-node/package.json @@ -1,7 +1,7 @@ { "name": "example-otlp-exporter-node", "private": true, - "version": "0.38.0", + "version": "0.39.1", "description": "Example of using @opentelemetry/collector-exporter in Node.js", "main": "index.js", "scripts": { @@ -29,17 +29,17 @@ }, "dependencies": { "@opentelemetry/api": "^1.3.0", - "@opentelemetry/core": "1.12.0", - "@opentelemetry/exporter-metrics-otlp-grpc": "0.38.0", - "@opentelemetry/exporter-metrics-otlp-http": "0.38.0", - "@opentelemetry/exporter-metrics-otlp-proto": "0.38.0", - "@opentelemetry/exporter-trace-otlp-grpc": "0.38.0", - "@opentelemetry/exporter-trace-otlp-http": "0.38.0", - "@opentelemetry/exporter-trace-otlp-proto": "0.38.0", - "@opentelemetry/resources": "1.12.0", - "@opentelemetry/sdk-metrics": "1.12.0", - "@opentelemetry/sdk-trace-base": "1.12.0", - "@opentelemetry/semantic-conventions": "1.12.0" + "@opentelemetry/core": "1.13.0", + "@opentelemetry/exporter-metrics-otlp-grpc": "0.39.1", + "@opentelemetry/exporter-metrics-otlp-http": "0.39.1", + "@opentelemetry/exporter-metrics-otlp-proto": "0.39.1", + "@opentelemetry/exporter-trace-otlp-grpc": "0.39.1", + "@opentelemetry/exporter-trace-otlp-http": "0.39.1", + "@opentelemetry/exporter-trace-otlp-proto": "0.39.1", + "@opentelemetry/resources": "1.13.0", + "@opentelemetry/sdk-metrics": "1.13.0", + "@opentelemetry/sdk-trace-base": "1.13.0", + "@opentelemetry/semantic-conventions": "1.13.0" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/examples/otlp-exporter-node" } diff --git a/experimental/CHANGELOG.md b/experimental/CHANGELOG.md index 6d98dd5c31..b109e6f0d0 100644 --- a/experimental/CHANGELOG.md +++ b/experimental/CHANGELOG.md @@ -6,6 +6,39 @@ All notable changes to experimental packages in this project will be documented ### :boom: Breaking Change +* fix(exporter-logs-otlp-grpc): change OTLPLogsExporter to OTLPLogExporter [#3819](https://github.com/open-telemetry/opentelemetry-js/pull/3819) @fuaiyi +* chore(instrumentation-grpc): add 'grpc' deprecation notice postinstall script [#3833](https://github.com/open-telemetry/opentelemetry-js/pull/3833) @pichlermarc + * Support for telemetry generation for the [`grpc`](https://www.npmjs.com/package/grpc) module will be dropped in the next release as the package has been + deprecated for over 1 year, please migrate to [`@grpc/grpc-js`](https://www.npmjs.com/package/@grpc/grpc-js) to continue receiving telemetry. + +### :rocket: (Enhancement) + +* feat(api-logs): support map in log attributes. [#3821](https://github.com/open-telemetry/opentelemetry-js/pull/3821) @Abinet18 +* feat(instrumentation): add ESM support for instrumentation. [#3698](https://github.com/open-telemetry/opentelemetry-js/pull/3698) @JamieDanielson, @pkanal, @vmarchaud, @lizthegrey, @bengl +* feat(exporter-logs-otlp-http): otlp-http exporter for logs. [#3764](https://github.com/open-telemetry/opentelemetry-js/pull/3764/) @fuaiyi +* feat(otlp-trace-exporters): Add User-Agent header to OTLP trace exporters. [#3790](https://github.com/open-telemetry/opentelemetry-js/pull/3790) @JamieDanielson +* feat(otlp-metric-exporters): Add User-Agent header to OTLP metric exporters. [#3806](https://github.com/open-telemetry/opentelemetry-js/pull/3806) @JamieDanielson +* feat(opencensus-shim): add OpenCensus trace shim [#3809](https://github.com/open-telemetry/opentelemetry-js/pull/3809) @aabmass +* feat(exporter-logs-otlp-proto): protobuf exporter for logs. [#3779](https://github.com/open-telemetry/opentelemetry-js/pull/3779) @Abinet18 +* feat(otlp-exporter-base): Add fetch sender for WebWorker and ServiceWorker environment. [#3542](https://github.com/open-telemetry/opentelemetry-js/pull/3542) @sugi + +### :bug: (Bug Fix) + +* fix(sdk-node): use resource interface instead of concrete class [#3803](https://github.com/open-telemetry/opentelemetry-js/pull/3803) @blumamir +* fix(sdk-logs): remove includeTraceContext configuration and use LogRecord context when available [#3817](https://github.com/open-telemetry/opentelemetry-js/pull/3817) @hectorhdzg + +### :books: (Refine Doc) + +### :house: (Internal) + +## 0.39.1 + +### :bug: (Bug Fix) + +* fix(otlp-transformer): move api-logs to dependencies [#3798](https://github.com/open-telemetry/opentelemetry-js/pull/3798) @pichlermarc + +## 0.39.0 + ### :rocket: (Enhancement) * feat(otlp-transformer): support log records. [#3712](https://github.com/open-telemetry/opentelemetry-js/pull/3712/) @llc1123 @@ -13,7 +46,7 @@ All notable changes to experimental packages in this project will be documented * feat(exporter-logs-otlp-grpc): otlp-grpc exporter for logs. [#3712](https://github.com/open-telemetry/opentelemetry-js/pull/3712/) @llc1123 * feat(otlp-grpc-exporter-base): use statically generated protobuf code [#3705](https://github.com/open-telemetry/opentelemetry-js/pull/3705) @pichlermarc * refactor(otlp-transformer): refine metric transformers. [#3770](https://github.com/open-telemetry/opentelemetry-js/pull/3770/) @llc1123 -* feat(otlp-exporter-base): Add fetch sender for WebWorker and ServiceWorker environment. [#3542](https://github.com/open-telemetry/opentelemetry-js/pull/3542) @sugi +* feat(api-logs): add `ObservedTimestamp` to `LogRecord`. [#3787](https://github.com/open-telemetry/opentelemetry-js/pull/3787/) @llc1123 ### :bug: (Bug Fix) diff --git a/experimental/backwards-compatability/node14/package.json b/experimental/backwards-compatability/node14/package.json index 6b8f162584..fb30bd3215 100644 --- a/experimental/backwards-compatability/node14/package.json +++ b/experimental/backwards-compatability/node14/package.json @@ -1,6 +1,6 @@ { "name": "backcompat-node14", - "version": "0.38.0", + "version": "0.39.1", "private": true, "description": "Backwards compatability app for node 14 types and the OpenTelemetry Node.js SDK", "main": "index.js", @@ -9,8 +9,8 @@ "peer-api-check": "node ../../../scripts/peer-api-check.js" }, "dependencies": { - "@opentelemetry/sdk-node": "0.38.0", - "@opentelemetry/sdk-trace-base": "1.12.0" + "@opentelemetry/sdk-node": "0.39.1", + "@opentelemetry/sdk-trace-base": "1.13.0" }, "devDependencies": { "@types/node": "14.18.25", diff --git a/experimental/backwards-compatability/node16/package.json b/experimental/backwards-compatability/node16/package.json index 67caa211af..bf74118063 100644 --- a/experimental/backwards-compatability/node16/package.json +++ b/experimental/backwards-compatability/node16/package.json @@ -1,6 +1,6 @@ { "name": "backcompat-node16", - "version": "0.38.0", + "version": "0.39.1", "private": true, "description": "Backwards compatability app for node 16 types and the OpenTelemetry Node.js SDK", "main": "index.js", @@ -9,8 +9,8 @@ "peer-api-check": "node ../../../scripts/peer-api-check.js" }, "dependencies": { - "@opentelemetry/sdk-node": "0.38.0", - "@opentelemetry/sdk-trace-base": "1.12.0" + "@opentelemetry/sdk-node": "0.39.1", + "@opentelemetry/sdk-trace-base": "1.13.0" }, "devDependencies": { "@types/node": "16.11.52", diff --git a/experimental/examples/logs/package.json b/experimental/examples/logs/package.json index a857e59f11..3e10700d19 100644 --- a/experimental/examples/logs/package.json +++ b/experimental/examples/logs/package.json @@ -1,17 +1,17 @@ { "name": "logs-example", - "version": "0.1.0", + "version": "0.2.0", "private": true, "scripts": { "start": "ts-node index.ts" }, "dependencies": { "@opentelemetry/api": "^1.4.1", - "@opentelemetry/api-logs": "^0.38.0", - "@opentelemetry/sdk-logs": "^0.38.0" + "@opentelemetry/api-logs": "0.39.1", + "@opentelemetry/sdk-logs": "0.39.1" }, "devDependencies": { - "ts-node": "^10.9.1", - "@types/node": "18.6.5" + "@types/node": "18.6.5", + "ts-node": "^10.9.1" } } diff --git a/experimental/examples/prometheus/package.json b/experimental/examples/prometheus/package.json index 404a13af6f..d1c5cb9ce6 100644 --- a/experimental/examples/prometheus/package.json +++ b/experimental/examples/prometheus/package.json @@ -1,6 +1,6 @@ { "name": "prometheus-example", - "version": "0.38.0", + "version": "0.39.1", "private": true, "description": "Example of using @opentelemetry/sdk-metrics and @opentelemetry/exporter-prometheus", "main": "index.js", @@ -11,7 +11,7 @@ "license": "Apache-2.0", "dependencies": { "@opentelemetry/api": "^1.3.0", - "@opentelemetry/exporter-prometheus": "0.38.0", - "@opentelemetry/sdk-metrics": "1.12.0" + "@opentelemetry/exporter-prometheus": "0.39.1", + "@opentelemetry/sdk-metrics": "1.13.0" } } diff --git a/experimental/packages/api-events/package.json b/experimental/packages/api-events/package.json index 04b649499b..bc1a24a54b 100644 --- a/experimental/packages/api-events/package.json +++ b/experimental/packages/api-events/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/api-events", - "version": "0.38.0", + "version": "0.39.1", "description": "Public events API for OpenTelemetry", "main": "build/src/index.js", "module": "build/esm/index.js", diff --git a/experimental/packages/api-logs/package.json b/experimental/packages/api-logs/package.json index dfb6d9ab6a..9143ef36d0 100644 --- a/experimental/packages/api-logs/package.json +++ b/experimental/packages/api-logs/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/api-logs", - "version": "0.38.0", + "version": "0.39.1", "description": "Public logs API for OpenTelemetry", "main": "build/src/index.js", "module": "build/esm/index.js", diff --git a/experimental/packages/api-logs/src/types/LogRecord.ts b/experimental/packages/api-logs/src/types/LogRecord.ts index a2c7d25ad3..59718aa30b 100644 --- a/experimental/packages/api-logs/src/types/LogRecord.ts +++ b/experimental/packages/api-logs/src/types/LogRecord.ts @@ -14,7 +14,12 @@ * limitations under the License. */ -import { Attributes, Context } from '@opentelemetry/api'; +import { AttributeValue, Context } from '@opentelemetry/api'; + +export type LogAttributeValue = AttributeValue | LogAttributes; +export interface LogAttributes { + [attributeKey: string]: LogAttributeValue | undefined; +} export enum SeverityNumber { UNSPECIFIED = 0, @@ -50,6 +55,11 @@ export interface LogRecord { */ timestamp?: number; + /** + * Time when the event was observed by the collection system. + */ + observedTimestamp?: number; + /** * Numerical value of the severity. */ @@ -68,7 +78,7 @@ export interface LogRecord { /** * Attributes that define the log record. */ - attributes?: Attributes; + attributes?: LogAttributes; /** * The Context associated with the LogRecord. diff --git a/experimental/packages/api-logs/src/types/LoggerOptions.ts b/experimental/packages/api-logs/src/types/LoggerOptions.ts index a57d44a739..fdcedcb464 100644 --- a/experimental/packages/api-logs/src/types/LoggerOptions.ts +++ b/experimental/packages/api-logs/src/types/LoggerOptions.ts @@ -13,9 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - import { Attributes } from '@opentelemetry/api'; - export interface LoggerOptions { /** * The schemaUrl of the tracer or instrumentation library diff --git a/experimental/packages/exporter-logs-otlp-grpc/README.md b/experimental/packages/exporter-logs-otlp-grpc/README.md index dee62b496f..17c9573398 100644 --- a/experimental/packages/exporter-logs-otlp-grpc/README.md +++ b/experimental/packages/exporter-logs-otlp-grpc/README.md @@ -22,7 +22,7 @@ To see documentation and sample code for the metric exporter, see the [exporter- ## Logs in Node - GRPC -The OTLPLogsExporter in Node expects the URL to only be the hostname. It will not work with `/v1/logs`. All +The OTLPLogExporter in Node expects the URL to only be the hostname. It will not work with `/v1/logs`. All options that work with trace also work with logs. ```js @@ -30,14 +30,14 @@ import { LoggerProvider, BatchLogRecordProcessor, } from '@opentelemetry/sdk-logs'; -import { OTLPLogsExporter } from '@opentelemetry/exporter-logs-otlp-grpc'; +import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-grpc'; const collectorOptions = { // url is optional and can be omitted - default is http://localhost:4317 url: 'http://:', }; -const loggerExporter = new OTLPLogsExporter(collectorOptions); +const loggerExporter = new OTLPLogExporter(collectorOptions); const loggerProvider = new LoggerProvider(); loggerProvider.addLogRecordProcessor( diff --git a/experimental/packages/exporter-logs-otlp-grpc/package.json b/experimental/packages/exporter-logs-otlp-grpc/package.json index 661e8c7221..99cb19287e 100644 --- a/experimental/packages/exporter-logs-otlp-grpc/package.json +++ b/experimental/packages/exporter-logs-otlp-grpc/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/exporter-logs-otlp-grpc", - "version": "0.38.0", + "version": "0.39.1", "description": "OpenTelemetry Collector Exporter allows user to send collected log records to the OpenTelemetry Collector", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -51,9 +51,9 @@ "@babel/core": "7.16.0", "@grpc/proto-loader": "^0.7.3", "@opentelemetry/api": "1.4.1", - "@opentelemetry/api-logs": "0.38.0", - "@opentelemetry/otlp-exporter-base": "0.38.0", - "@opentelemetry/resources": "1.12.0", + "@opentelemetry/api-logs": "0.39.1", + "@opentelemetry/otlp-exporter-base": "0.39.1", + "@opentelemetry/resources": "1.13.0", "@types/mocha": "10.0.0", "@types/node": "18.6.5", "@types/sinon": "10.0.13", @@ -71,11 +71,11 @@ }, "dependencies": { "@grpc/grpc-js": "^1.7.1", - "@opentelemetry/core": "1.12.0", - "@opentelemetry/otlp-grpc-exporter-base": "0.38.0", - "@opentelemetry/otlp-transformer": "0.38.0", - "@opentelemetry/sdk-logs": "0.38.0" + "@opentelemetry/core": "1.13.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.39.1", + "@opentelemetry/otlp-transformer": "0.39.1", + "@opentelemetry/sdk-logs": "0.39.1" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/exporter-logs-otlp-grpc", "sideEffects": false -} \ No newline at end of file +} diff --git a/experimental/packages/exporter-logs-otlp-grpc/src/OTLPLogsExporter.ts b/experimental/packages/exporter-logs-otlp-grpc/src/OTLPLogExporter.ts similarity index 98% rename from experimental/packages/exporter-logs-otlp-grpc/src/OTLPLogsExporter.ts rename to experimental/packages/exporter-logs-otlp-grpc/src/OTLPLogExporter.ts index e4484fdb39..675ce83db0 100644 --- a/experimental/packages/exporter-logs-otlp-grpc/src/OTLPLogsExporter.ts +++ b/experimental/packages/exporter-logs-otlp-grpc/src/OTLPLogExporter.ts @@ -32,7 +32,7 @@ import { /** * OTLP Logs Exporter for Node */ -export class OTLPLogsExporter +export class OTLPLogExporter extends OTLPGRPCExporterNodeBase implements LogRecordExporter { diff --git a/experimental/packages/exporter-logs-otlp-grpc/src/index.ts b/experimental/packages/exporter-logs-otlp-grpc/src/index.ts index b35d852d90..b071c78ef5 100644 --- a/experimental/packages/exporter-logs-otlp-grpc/src/index.ts +++ b/experimental/packages/exporter-logs-otlp-grpc/src/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export * from './OTLPLogsExporter'; +export * from './OTLPLogExporter'; diff --git a/experimental/packages/exporter-logs-otlp-grpc/test/OTLPLogsExporter.test.ts b/experimental/packages/exporter-logs-otlp-grpc/test/OTLPLogExporter.test.ts similarity index 91% rename from experimental/packages/exporter-logs-otlp-grpc/test/OTLPLogsExporter.test.ts rename to experimental/packages/exporter-logs-otlp-grpc/test/OTLPLogExporter.test.ts index d413b8902d..48f1fa865e 100644 --- a/experimental/packages/exporter-logs-otlp-grpc/test/OTLPLogsExporter.test.ts +++ b/experimental/packages/exporter-logs-otlp-grpc/test/OTLPLogExporter.test.ts @@ -22,7 +22,7 @@ import * as fs from 'fs'; import * as grpc from '@grpc/grpc-js'; import * as path from 'path'; import * as sinon from 'sinon'; -import { OTLPLogsExporter } from '../src'; +import { OTLPLogExporter } from '../src'; import { ensureExportedLogRecordIsCorrect, @@ -44,7 +44,7 @@ const includeDirs = [ path.resolve(__dirname, '../../otlp-grpc-exporter-base/protos'), ]; -const address = 'localhost:1501'; +const address = 'localhost:1503'; type TestParams = { useTLS?: boolean; @@ -55,10 +55,10 @@ const metadata = new grpc.Metadata(); metadata.set('k', 'v'); const testCollectorExporter = (params: TestParams) => - describe(`OTLPLogsExporter - node ${ - params.useTLS ? 'with' : 'without' - } TLS, ${params.metadata ? 'with' : 'without'} metadata`, () => { - let collectorExporter: OTLPLogsExporter; + describe(`OTLPLogExporter - node ${params.useTLS ? 'with' : 'without'} TLS, ${ + params.metadata ? 'with' : 'without' + } metadata`, () => { + let collectorExporter: OTLPLogExporter; let server: grpc.Server; let exportedData: IResourceLogs | undefined; let reqMetadata: grpc.Metadata | undefined; @@ -122,7 +122,7 @@ const testCollectorExporter = (params: TestParams) => fs.readFileSync('./test/certs/client.crt') ) : grpc.credentials.createInsecure(); - collectorExporter = new OTLPLogsExporter({ + collectorExporter = new OTLPLogExporter({ url: 'https://' + address, credentials, metadata: params.metadata, @@ -140,7 +140,7 @@ const testCollectorExporter = (params: TestParams) => it('should warn about headers when using grpc', () => { // Need to stub/spy on the underlying logger as the 'diag' instance is global const spyLoggerWarn = sinon.stub(diag, 'warn'); - collectorExporter = new OTLPLogsExporter({ + collectorExporter = new OTLPLogExporter({ url: `http://${address}`, headers: { foo: 'bar', @@ -151,7 +151,7 @@ const testCollectorExporter = (params: TestParams) => }); it('should warn about path in url', () => { const spyLoggerWarn = sinon.stub(diag, 'warn'); - collectorExporter = new OTLPLogsExporter({ + collectorExporter = new OTLPLogExporter({ url: `http://${address}/v1/logs`, }); const args = spyLoggerWarn.args[0]; @@ -198,7 +198,7 @@ const testCollectorExporter = (params: TestParams) => ) : grpc.credentials.createInsecure(); - const collectorExporterWithTimeout = new OTLPLogsExporter({ + const collectorExporterWithTimeout = new OTLPLogExporter({ url: 'grpcs://' + address, credentials, metadata: params.metadata, @@ -229,7 +229,7 @@ const testCollectorExporter = (params: TestParams) => fs.readFileSync('./test/certs/client.crt') ) : grpc.credentials.createInsecure(); - collectorExporter = new OTLPLogsExporter({ + collectorExporter = new OTLPLogExporter({ url: 'https://' + address, credentials, metadata: params.metadata, @@ -272,7 +272,7 @@ const testCollectorExporter = (params: TestParams) => : grpc.credentials.createInsecure(); envSource.OTEL_EXPORTER_OTLP_COMPRESSION = 'gzip'; - collectorExporter = new OTLPLogsExporter({ + collectorExporter = new OTLPLogExporter({ url: 'https://' + address, credentials, metadata: params.metadata, @@ -286,9 +286,9 @@ const testCollectorExporter = (params: TestParams) => }); }); -describe('OTLPLogsExporter - node (getDefaultUrl)', () => { +describe('OTLPLogExporter - node (getDefaultUrl)', () => { it('should default to localhost', done => { - const collectorExporter = new OTLPLogsExporter({}); + const collectorExporter = new OTLPLogExporter({}); setTimeout(() => { assert.strictEqual(collectorExporter['url'], 'localhost:4317'); done(); @@ -296,7 +296,7 @@ describe('OTLPLogsExporter - node (getDefaultUrl)', () => { }); it('should keep the URL if included', done => { const url = 'http://foo.bar.com'; - const collectorExporter = new OTLPLogsExporter({ url }); + const collectorExporter = new OTLPLogExporter({ url }); setTimeout(() => { assert.strictEqual(collectorExporter['url'], 'foo.bar.com'); done(); @@ -308,21 +308,21 @@ describe('when configuring via environment', () => { const envSource = process.env; it('should use url defined in env', () => { envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar'; - const collectorExporter = new OTLPLogsExporter(); + 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 OTLPLogsExporter(); + const collectorExporter = new OTLPLogExporter(); assert.strictEqual(collectorExporter.url, 'foo.logs'); envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = ''; }); it('should use headers defined via env', () => { envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar'; - const collectorExporter = new OTLPLogsExporter(); + const collectorExporter = new OTLPLogExporter(); assert.deepStrictEqual(collectorExporter.metadata?.get('foo'), ['bar']); envSource.OTEL_EXPORTER_OTLP_HEADERS = ''; }); @@ -332,7 +332,7 @@ describe('when configuring via environment', () => { metadata.set('goo', 'lol'); envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=jar,bar=foo'; envSource.OTEL_EXPORTER_OTLP_LOGS_HEADERS = 'foo=boo'; - const collectorExporter = new OTLPLogsExporter({ metadata }); + 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']); diff --git a/experimental/packages/exporter-logs-otlp-grpc/test/logsHelper.ts b/experimental/packages/exporter-logs-otlp-grpc/test/logsHelper.ts index b57b2682bd..1dfd82675a 100644 --- a/experimental/packages/exporter-logs-otlp-grpc/test/logsHelper.ts +++ b/experimental/packages/exporter-logs-otlp-grpc/test/logsHelper.ts @@ -45,6 +45,7 @@ export const mockedReadableLogRecord: ReadableLogRecord = { schemaUrl: 'http://url.to.schema', }, hrTime: [1680253513, 123241635] as HrTime, + hrTimeObserved: [1683526948, 965142784] as HrTime, attributes: { 'some-attribute': 'some attribute value', }, @@ -92,7 +93,7 @@ export function ensureExportedLogRecordIsCorrect(logRecord: ILogRecord) { ); assert.strictEqual( logRecord.observedTimeUnixNano, - '1680253513123241728', + '1683526948965142784', 'observedTimeUnixNano is wrong' ); assert.strictEqual( diff --git a/experimental/packages/exporter-logs-otlp-http/.eslintignore b/experimental/packages/exporter-logs-otlp-http/.eslintignore new file mode 100644 index 0000000000..378eac25d3 --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-http/.eslintignore @@ -0,0 +1 @@ +build diff --git a/experimental/packages/exporter-logs-otlp-http/.eslintrc.js b/experimental/packages/exporter-logs-otlp-http/.eslintrc.js new file mode 100644 index 0000000000..f3f22e8617 --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-http/.eslintrc.js @@ -0,0 +1,9 @@ +module.exports = { + env: { + mocha: true, + commonjs: true, + node: true, + browser: true, + }, + ...require('../../../eslint.config.js'), +}; diff --git a/experimental/packages/exporter-logs-otlp-http/.npmignore b/experimental/packages/exporter-logs-otlp-http/.npmignore new file mode 100644 index 0000000000..9505ba9450 --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-http/.npmignore @@ -0,0 +1,4 @@ +/bin +/coverage +/doc +/test diff --git a/experimental/packages/exporter-logs-otlp-http/LICENSE b/experimental/packages/exporter-logs-otlp-http/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-http/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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 + + http://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. diff --git a/experimental/packages/exporter-logs-otlp-http/README.md b/experimental/packages/exporter-logs-otlp-http/README.md new file mode 100644 index 0000000000..256e1d88d8 --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-http/README.md @@ -0,0 +1,114 @@ +# OpenTelemetry Collector Logs Exporter for web and node with HTTP + +[![NPM Published Version][npm-img]][npm-url] +[![Apache License][license-image]][license-image] + +**Note: This is an experimental package under active development. New releases may include breaking changes.** + +This module provides an exporter for OTLP (http/json) logs using protocol version `v0.18`. + +## Installation + +```bash +npm install --save @opentelemetry/exporter-logs-otlp-http +``` + +## Further Documentation + +To see documentation and sample code for the traces exporter, as well as instructions for using TLS, visit the [Collector Trace Exporter for web and node][trace-exporter-url]. +To see documentation and sample code for the metric exporter, see the [exporter-metrics-otlp-grpc package][metrics-exporter-url] + +## Logs in Web + +The OTLPLogExporter in Web expects the endpoint to end in `/v1/logs`. + +```js +import { SeverityNumber } from '@opentelemetry/api-logs'; +import { + LoggerProvider, + BatchLogRecordProcessor, +} from '@opentelemetry/sdk-logs'; +import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http'; + +// exporter options. see all options in OTLPExporterConfigBase +const collectorOptions = { + url: '', // url is optional and can be omitted - default is http://localhost:4318/v1/logs + headers: {}, // an optional object containing custom headers to be sent with each request + concurrencyLimit: 1, // an optional limit on pending requests +}; +const logExporter = new OTLPLogExporter(collectorOptions); +const loggerProvider = new LoggerProvider(); + +loggerProvider.addLogRecordProcessor(new BatchLogRecordProcessor(logExporter)); + +const logger = loggerProvider.getLogger('default', '1.0.0'); +// Emit a log +logger.emit({ + severityNumber: SeverityNumber.INFO, + severityText: 'info', + body: 'this is a log body', + attributes: { 'log.type': 'custom' }, +}); +``` + +## Logs in Node + +```js +import { + LoggerProvider, + BatchLogRecordProcessor, +} from '@opentelemetry/sdk-logs'; +import { OTLPLogsExporter } from '@opentelemetry/exporter-logs-otlp-http'; + +// exporter options. see all options in OTLPExporterNodeConfigBase +const collectorOptions = { + url: '', // url is optional and can be omitted - default is http://localhost:4318/v1/logs + concurrencyLimit: 1, // an optional limit on pending requests +}; +const logExporter = new OTLPLogExporter(collectorOptions); +const loggerProvider = new LoggerProvider(); + +loggerProvider.addLogRecordProcessor(new BatchLogRecordProcessor(logExporter)); + +const logger = loggerProvider.getLogger('default', '1.0.0'); +// Emit a log +logger.emit({ + severityNumber: SeverityNumber.INFO, + severityText: 'info', + body: 'this is a log body', + attributes: { 'log.type': 'custom' }, +}); +``` + +## Environment Variable Configuration + +In addition to settings passed to the constructor, the exporter also supports configuration via environment variables: + +| Environment variable | Description | +| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| OTEL_EXPORTER_OTLP_ENDPOINT | The endpoint to send logs to. This will also be used for the traces exporter if `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` is not configured. By default `http://localhost:4318` will be used. `/v1/logs` will be automatically appended to configured values. | +| OTEL_EXPORTER_OTLP_LOGS_ENDPOINT | The endpoint to send logs to. By default `https://localhost:4318/v1/logs` will be used. `v1/logs` will not be appended automatically and has to be added explicitly. | +| OTEL_EXPORTER_OTLP_LOGS_TIMEOUT | The maximum waiting time, in milliseconds, allowed to send each OTLP log batch. Default is 10000. | +| OTEL_EXPORTER_OTLP_TIMEOUT | The maximum waiting time, in milliseconds, allowed to send each OTLP trace/metric/log batch. Default is 10000. | + +> Settings configured programmatically take precedence over environment variables. Per-signal environment variables take precedence over non-per-signal environment variables. + +## Useful links + +- For more information on OpenTelemetry, visit: +- For more about OpenTelemetry JavaScript: +- For help or feedback on this project, join us in [GitHub Discussions][discussions-url] + +## License + +Apache 2.0 - See [LICENSE][license-url] for more information. + +[discussions-url]: https://github.com/open-telemetry/opentelemetry-js/discussions +[license-url]: https://github.com/open-telemetry/opentelemetry-js/blob/main/LICENSE +[license-image]: https://img.shields.io/badge/license-Apache_2.0-green.svg?style=flat +[npm-url]: https://www.npmjs.com/package/@opentelemetry/exporter-logs-otlp-http +[npm-img]: https://badge.fury.io/js/%40opentelemetry%2Fexporter-logs-otlp-http.svg +[opentelemetry-collector-url]: https://github.com/open-telemetry/opentelemetry-collector +[semconv-resource-service-name]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/semantic_conventions/README.md#service +[trace-exporter-url]: https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/exporter-trace-otlp-http +[metrics-exporter-url]: https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-exporter-metrics-otlp-http diff --git a/experimental/packages/exporter-logs-otlp-http/karma.conf.js b/experimental/packages/exporter-logs-otlp-http/karma.conf.js new file mode 100644 index 0000000000..bfd7a03326 --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-http/karma.conf.js @@ -0,0 +1,28 @@ +/*! + * 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 + * + * http://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. + */ + +const karmaWebpackConfig = require('../../../karma.webpack'); +const karmaBaseConfig = require('../../../karma.base'); + +module.exports = config => { + config.set( + Object.assign({}, karmaBaseConfig, { + webpack: karmaWebpackConfig, + files: ['test/browser/index-webpack.ts'], + preprocessors: { 'test/browser/index-webpack.ts': ['webpack'] }, + }) + ); +}; diff --git a/experimental/packages/exporter-logs-otlp-http/package.json b/experimental/packages/exporter-logs-otlp-http/package.json new file mode 100644 index 0000000000..b6f4808b42 --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-http/package.json @@ -0,0 +1,109 @@ +{ + "name": "@opentelemetry/exporter-logs-otlp-http", + "version": "0.39.1", + "publishConfig": { + "access": "public" + }, + "description": "OpenTelemetry Collector Logs Exporter allows user to send collected logs to the OpenTelemetry Collector", + "author": "OpenTelemetry Authors", + "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/exporter-logs-otlp-http", + "license": "Apache-2.0", + "main": "build/src/index.js", + "module": "build/esm/index.js", + "esnext": "build/esnext/index.js", + "types": "build/src/index.d.ts", + "browser": { + "./src/platform/index.ts": "./src/platform/browser/index.ts", + "./build/esm/platform/index.js": "./build/esm/platform/browser/index.js", + "./build/esnext/platform/index.js": "./build/esnext/platform/browser/index.js", + "./build/src/platform/index.js": "./build/src/platform/browser/index.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/open-telemetry/opentelemetry-js.git" + }, + "bugs": { + "url": "https://github.com/open-telemetry/opentelemetry-js/issues" + }, + "engines": { + "node": ">=14" + }, + "scripts": { + "prepublishOnly": "npm run compile", + "compile": "tsc --build tsconfig.json tsconfig.esm.json tsconfig.esnext.json", + "clean": "tsc --build --clean tsconfig.json tsconfig.esm.json tsconfig.esnext.json", + "codecov:browser": "nyc report --reporter=json && codecov -f coverage/*.json -p ../../../", + "lint": "eslint . --ext .ts", + "lint:fix": "eslint . --ext .ts --fix", + "tdd": "npm run test -- --watch-extensions ts --watch", + "tdd:browser": "karma start", + "test": "nyc ts-mocha -p tsconfig.json 'test/**/*.test.ts' --exclude 'test/browser/**/*.ts'", + "test:browser": "nyc karma start --single-run", + "version": "node ../../../scripts/version-update.js", + "watch": "tsc --build --watch tsconfig.json tsconfig.esm.json tsconfig.esnext.json", + "precompile": "lerna run version --scope $(npm pkg get name) --include-dependencies", + "prewatch": "npm run precompile", + "peer-api-check": "node ../../../scripts/peer-api-check.js", + "codecov": "nyc report --reporter=json && codecov -f coverage/*.json -p ../../../" + }, + "keywords": [ + "opentelemetry", + "nodejs", + "browser", + "tracing", + "profiling", + "logs", + "stats" + ], + "files": [ + "build/esm/**/*.js", + "build/esm/**/*.js.map", + "build/esm/**/*.d.ts", + "build/esnext/**/*.js", + "build/esnext/**/*.js.map", + "build/esnext/**/*.d.ts", + "build/src/**/*.js", + "build/src/**/*.js.map", + "build/src/**/*.d.ts", + "doc", + "LICENSE", + "README.md" + ], + "sideEffects": false, + "devDependencies": { + "@babel/core": "7.16.0", + "@opentelemetry/api-logs": ">=0.38.0", + "@types/mocha": "10.0.0", + "@types/node": "18.6.5", + "@types/sinon": "10.0.13", + "@types/webpack-env": "1.16.3", + "babel-loader": "8.2.3", + "codecov": "3.8.3", + "cpx": "1.5.0", + "istanbul-instrumenter-loader": "3.0.1", + "karma": "6.3.16", + "karma-chrome-launcher": "3.1.0", + "karma-coverage-istanbul-reporter": "3.0.3", + "karma-mocha": "2.0.1", + "karma-spec-reporter": "0.0.32", + "karma-webpack": "4.0.2", + "mocha": "10.0.0", + "nyc": "15.1.0", + "sinon": "14.0.0", + "ts-loader": "8.4.0", + "ts-mocha": "10.0.0", + "typescript": "4.4.4", + "webpack": "4.46.0", + "webpack-cli": "4.9.1", + "webpack-merge": "5.8.0" + }, + "peerDependencies": { + "@opentelemetry/api-logs": ">=0.38.0" + }, + "dependencies": { + "@opentelemetry/core": "1.13.0", + "@opentelemetry/otlp-exporter-base": "0.39.1", + "@opentelemetry/otlp-transformer": "0.39.1", + "@opentelemetry/sdk-logs": "0.39.1" + } +} diff --git a/experimental/packages/exporter-logs-otlp-http/src/index.ts b/experimental/packages/exporter-logs-otlp-http/src/index.ts new file mode 100644 index 0000000000..89954d4784 --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-http/src/index.ts @@ -0,0 +1,17 @@ +/* + * 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 { OTLPLogExporter } from './platform'; diff --git a/experimental/packages/exporter-logs-otlp-http/src/platform/browser/OTLPLogExporter.ts b/experimental/packages/exporter-logs-otlp-http/src/platform/browser/OTLPLogExporter.ts new file mode 100644 index 0000000000..ef837614e2 --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-http/src/platform/browser/OTLPLogExporter.ts @@ -0,0 +1,57 @@ +/* + * 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 type { + ReadableLogRecord, + LogRecordExporter, +} from '@opentelemetry/sdk-logs'; +import type { OTLPExporterConfigBase } from '@opentelemetry/otlp-exporter-base'; +import type { IExportLogsServiceRequest } from '@opentelemetry/otlp-transformer'; +import { OTLPExporterBrowserBase } from '@opentelemetry/otlp-exporter-base'; +import { baggageUtils, getEnv } from '@opentelemetry/core'; +import { createExportLogsServiceRequest } from '@opentelemetry/otlp-transformer'; + +import { getDefaultUrl } from '../config'; + +/** + * Collector Logs Exporter for Web + */ +export class OTLPLogExporter + extends OTLPExporterBrowserBase + implements LogRecordExporter +{ + constructor(config: OTLPExporterConfigBase = {}) { + // load OTEL_EXPORTER_OTLP_LOGS_TIMEOUT env var + super({ + timeoutMillis: getEnv().OTEL_EXPORTER_OTLP_LOGS_TIMEOUT, + ...config, + }); + this._headers = { + ...this._headers, + ...baggageUtils.parseKeyPairsIntoRecord( + getEnv().OTEL_EXPORTER_OTLP_LOGS_HEADERS + ), + }; + } + + convert(logRecords: ReadableLogRecord[]): IExportLogsServiceRequest { + return createExportLogsServiceRequest(logRecords); + } + + getDefaultUrl(config: OTLPExporterConfigBase): string { + return getDefaultUrl(config); + } +} diff --git a/experimental/packages/exporter-logs-otlp-http/src/platform/browser/index.ts b/experimental/packages/exporter-logs-otlp-http/src/platform/browser/index.ts new file mode 100644 index 0000000000..2472e4a796 --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-http/src/platform/browser/index.ts @@ -0,0 +1,17 @@ +/* + * 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 { OTLPLogExporter } from './OTLPLogExporter'; diff --git a/experimental/packages/exporter-logs-otlp-http/src/platform/config.ts b/experimental/packages/exporter-logs-otlp-http/src/platform/config.ts new file mode 100644 index 0000000000..a41ee96468 --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-http/src/platform/config.ts @@ -0,0 +1,43 @@ +/* + * 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 { getEnv } from '@opentelemetry/core'; +import { + appendResourcePathToUrl, + appendRootPathToUrlIfNeeded, + OTLPExporterConfigBase, +} from '@opentelemetry/otlp-exporter-base'; + +const DEFAULT_COLLECTOR_RESOURCE_PATH = 'v1/logs'; +export const DEFAULT_COLLECTOR_URL = `http://localhost:4318/${DEFAULT_COLLECTOR_RESOURCE_PATH}`; + +/** + * common get default url + * @param config exporter config + * @returns url string + */ +export function getDefaultUrl(config: OTLPExporterConfigBase): string { + return typeof config.url === 'string' + ? config.url + : getEnv().OTEL_EXPORTER_OTLP_LOGS_ENDPOINT.length > 0 + ? appendRootPathToUrlIfNeeded(getEnv().OTEL_EXPORTER_OTLP_LOGS_ENDPOINT) + : getEnv().OTEL_EXPORTER_OTLP_ENDPOINT.length > 0 + ? appendResourcePathToUrl( + getEnv().OTEL_EXPORTER_OTLP_ENDPOINT, + DEFAULT_COLLECTOR_RESOURCE_PATH + ) + : DEFAULT_COLLECTOR_URL; +} diff --git a/experimental/packages/exporter-logs-otlp-http/src/platform/index.ts b/experimental/packages/exporter-logs-otlp-http/src/platform/index.ts new file mode 100644 index 0000000000..830bf10b27 --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-http/src/platform/index.ts @@ -0,0 +1,17 @@ +/* + * 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 { OTLPLogExporter } from './node'; 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 new file mode 100644 index 0000000000..7f7c538df7 --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-http/src/platform/node/OTLPLogExporter.ts @@ -0,0 +1,57 @@ +/* + * 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 type { + ReadableLogRecord, + LogRecordExporter, +} from '@opentelemetry/sdk-logs'; +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 { createExportLogsServiceRequest } from '@opentelemetry/otlp-transformer'; + +import { getDefaultUrl } from '../config'; + +/** + * Collector Logs Exporter for Node + */ +export class OTLPLogExporter + extends OTLPExporterNodeBase + implements LogRecordExporter +{ + constructor(config: OTLPExporterNodeConfigBase = {}) { + // load OTEL_EXPORTER_OTLP_LOGS_TIMEOUT env + super({ + timeoutMillis: getEnv().OTEL_EXPORTER_OTLP_LOGS_TIMEOUT, + ...config, + }); + this.headers = { + ...this.headers, + ...baggageUtils.parseKeyPairsIntoRecord( + getEnv().OTEL_EXPORTER_OTLP_LOGS_HEADERS + ), + }; + } + + convert(logRecords: ReadableLogRecord[]): IExportLogsServiceRequest { + return createExportLogsServiceRequest(logRecords); + } + + getDefaultUrl(config: OTLPExporterNodeConfigBase): string { + return getDefaultUrl(config); + } +} diff --git a/experimental/packages/exporter-logs-otlp-http/src/platform/node/index.ts b/experimental/packages/exporter-logs-otlp-http/src/platform/node/index.ts new file mode 100644 index 0000000000..2472e4a796 --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-http/src/platform/node/index.ts @@ -0,0 +1,17 @@ +/* + * 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 { OTLPLogExporter } from './OTLPLogExporter'; diff --git a/experimental/packages/exporter-logs-otlp-http/test/browser/OTLPLogExporter.test.ts b/experimental/packages/exporter-logs-otlp-http/test/browser/OTLPLogExporter.test.ts new file mode 100644 index 0000000000..66958a9fef --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-http/test/browser/OTLPLogExporter.test.ts @@ -0,0 +1,64 @@ +/* + * 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 assert from 'assert'; +import * as sinon from 'sinon'; + +import * as Config from '../../src/platform/config'; +import { OTLPLogExporter } from '../../src/platform/browser'; + +describe('OTLPLogExporter', () => { + let envSource: Record; + + if (typeof process === 'undefined') { + envSource = globalThis as unknown as Record; + } else { + envSource = process.env as Record; + } + + describe('constructor', () => { + it('should create an instance', () => { + const exporter = new OTLPLogExporter(); + assert.ok(exporter instanceof OTLPLogExporter); + }); + + it('should use headers defined via env', () => { + envSource.OTEL_EXPORTER_OTLP_LOGS_HEADERS = 'foo=bar'; + const exporter = new OTLPLogExporter(); + assert.strictEqual(exporter['_headers'].foo, 'bar'); + delete envSource.OTEL_EXPORTER_OTLP_LOGS_HEADERS; + }); + + it('should use timeout defined via env', () => { + envSource.OTEL_EXPORTER_OTLP_LOGS_HEADERS = ''; + envSource.OTEL_EXPORTER_OTLP_LOGS_TIMEOUT = 30000; + const exporter = new OTLPLogExporter(); + assert.strictEqual(exporter.timeoutMillis, 30000); + delete envSource.OTEL_EXPORTER_OTLP_LOGS_HEADERS; + delete envSource.OTEL_EXPORTER_OTLP_LOGS_HEADERS; + }); + }); + + describe('getDefaultUrl', () => { + it('should call getDefaultUrl', () => { + const getDefaultUrl = sinon.stub(Config, 'getDefaultUrl'); + const exporter = new OTLPLogExporter(); + exporter.getDefaultUrl({}); + // this callCount is 2, because new OTLPLogExporter also call it + assert.strictEqual(getDefaultUrl.callCount, 2); + }); + }); +}); diff --git a/experimental/packages/exporter-logs-otlp-http/test/browser/index-webpack.ts b/experimental/packages/exporter-logs-otlp-http/test/browser/index-webpack.ts new file mode 100644 index 0000000000..9a4c86560e --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-http/test/browser/index-webpack.ts @@ -0,0 +1,20 @@ +/* + * 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. + */ + +{ + const testsContext = require.context('../browser', true, /test$/); + testsContext.keys().forEach(testsContext); +} diff --git a/experimental/packages/exporter-logs-otlp-http/test/config.test.ts b/experimental/packages/exporter-logs-otlp-http/test/config.test.ts new file mode 100644 index 0000000000..77656de561 --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-http/test/config.test.ts @@ -0,0 +1,136 @@ +/* + * 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 assert from 'assert'; + +import { getDefaultUrl } from '../src/platform/config'; + +describe('getDefaultUrl', () => { + let envSource: Record; + + if (typeof process === 'undefined') { + envSource = globalThis as unknown as Record; + } else { + envSource = process.env as Record; + } + + it('should use config url if config url is defined', () => { + const configUrl = 'http://foo.bar/v1/logs/'; + envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = 'http://foo.bar.logs/'; + envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar/'; + const defaultUrl = getDefaultUrl({ url: configUrl }); + assert.strictEqual(defaultUrl, configUrl); + delete envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT; + delete envSource.OTEL_EXPORTER_OTLP_ENDPOINT; + }); + + it('should use url defined in env that ends with root path and append version and signal path', () => { + envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = ''; + envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar/'; + const defaultUrl = getDefaultUrl({}); + assert.strictEqual( + defaultUrl, + `${envSource.OTEL_EXPORTER_OTLP_ENDPOINT}v1/logs` + ); + delete envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT; + delete envSource.OTEL_EXPORTER_OTLP_ENDPOINT; + }); + + it('should use url defined in env without checking if path is already present', () => { + envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = ''; + envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar/v1/logs'; + const defaultUrl = getDefaultUrl({}); + assert.strictEqual( + defaultUrl, + `${envSource.OTEL_EXPORTER_OTLP_ENDPOINT}/v1/logs` + ); + delete envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT; + delete envSource.OTEL_EXPORTER_OTLP_ENDPOINT; + }); + + it('should use url defined in env and append version and signal', () => { + envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = ''; + envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar'; + const defaultUrl = getDefaultUrl({}); + assert.strictEqual( + defaultUrl, + `${envSource.OTEL_EXPORTER_OTLP_ENDPOINT}/v1/logs` + ); + delete envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT; + delete envSource.OTEL_EXPORTER_OTLP_ENDPOINT; + }); + + it('should override global exporter url with signal url defined in env', () => { + envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = 'http://foo.logs/'; + envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar/'; + const defaultUrl = getDefaultUrl({}); + assert.strictEqual( + defaultUrl, + `${envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT}` + ); + delete envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT; + delete envSource.OTEL_EXPORTER_OTLP_ENDPOINT; + }); + + it('should add root path when signal url defined in env contains no path and no root path', () => { + envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = 'http://foo.logs'; + envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; + + const defaultUrl = getDefaultUrl({}); + assert.strictEqual( + defaultUrl, + `${envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT}/` + ); + delete envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT; + delete envSource.OTEL_EXPORTER_OTLP_ENDPOINT; + }); + + it('should not add root path when signal url defined in env contains root path but no path', () => { + envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = 'http://foo.bar/'; + envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; + const defaultUrl = getDefaultUrl({}); + assert.strictEqual( + defaultUrl, + `${envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT}` + ); + delete envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT; + delete envSource.OTEL_EXPORTER_OTLP_ENDPOINT; + }); + + it('should not add root path when signal url defined in env contains path', () => { + envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = 'http://foo.bar/v1/logs'; + envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; + const defaultUrl = getDefaultUrl({}); + assert.strictEqual( + defaultUrl, + `${envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT}` + ); + delete envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT; + delete envSource.OTEL_EXPORTER_OTLP_ENDPOINT; + }); + + it('should not add root path when signal url defined in env contains path and ends in /', () => { + envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = 'http://foo.bar/v1/logs/'; + envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; + const defaultUrl = getDefaultUrl({}); + assert.strictEqual( + defaultUrl, + `${envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT}` + ); + delete envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT; + delete envSource.OTEL_EXPORTER_OTLP_ENDPOINT; + }); +}); diff --git a/experimental/packages/exporter-logs-otlp-http/test/node/OTLPLogExporter.test.ts b/experimental/packages/exporter-logs-otlp-http/test/node/OTLPLogExporter.test.ts new file mode 100644 index 0000000000..aa0d345348 --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-http/test/node/OTLPLogExporter.test.ts @@ -0,0 +1,64 @@ +/* + * 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 assert from 'assert'; +import * as sinon from 'sinon'; + +import * as Config from '../../src/platform/config'; +import { OTLPLogExporter } from '../../src/platform/node'; + +describe('OTLPLogExporter', () => { + let envSource: Record; + + if (typeof process === 'undefined') { + envSource = globalThis as unknown as Record; + } else { + envSource = process.env as Record; + } + + describe('constructor', () => { + it('should create an instance', () => { + const exporter = new OTLPLogExporter(); + assert.ok(exporter instanceof OTLPLogExporter); + }); + + it('should use headers defined via env', () => { + envSource.OTEL_EXPORTER_OTLP_LOGS_HEADERS = 'foo=bar'; + const exporter = new OTLPLogExporter(); + assert.strictEqual(exporter.headers.foo, 'bar'); + delete envSource.OTEL_EXPORTER_OTLP_LOGS_HEADERS; + }); + + it('should use timeout defined via env', () => { + envSource.OTEL_EXPORTER_OTLP_LOGS_HEADERS = ''; + envSource.OTEL_EXPORTER_OTLP_LOGS_TIMEOUT = 30000; + const exporter = new OTLPLogExporter(); + assert.strictEqual(exporter.timeoutMillis, 30000); + delete envSource.OTEL_EXPORTER_OTLP_LOGS_HEADERS; + delete envSource.OTEL_EXPORTER_OTLP_LOGS_TIMEOUT; + }); + }); + + describe('getDefaultUrl', () => { + it('should call getDefaultUrl', () => { + const getDefaultUrl = sinon.stub(Config, 'getDefaultUrl'); + const exporter = new OTLPLogExporter(); + exporter.getDefaultUrl({}); + // this callCount is 2, because new OTLPLogExporter also call it + assert.strictEqual(getDefaultUrl.callCount, 2); + }); + }); +}); diff --git a/experimental/packages/exporter-logs-otlp-http/tsconfig.esm.json b/experimental/packages/exporter-logs-otlp-http/tsconfig.esm.json new file mode 100644 index 0000000000..554367c373 --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-http/tsconfig.esm.json @@ -0,0 +1,28 @@ +{ + "extends": "../../../tsconfig.base.esm.json", + "compilerOptions": { + "outDir": "build/esm", + "rootDir": "src", + "tsBuildInfoFile": "build/esm/tsconfig.esm.tsbuildinfo" + }, + "include": [ + "src/**/*.ts" + ], + "references": [ + { + "path": "../../../packages/opentelemetry-core" + }, + { + "path": "../api-logs" + }, + { + "path": "../otlp-exporter-base" + }, + { + "path": "../otlp-transformer" + }, + { + "path": "../sdk-logs" + } + ] +} diff --git a/experimental/packages/exporter-logs-otlp-http/tsconfig.esnext.json b/experimental/packages/exporter-logs-otlp-http/tsconfig.esnext.json new file mode 100644 index 0000000000..87a369521c --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-http/tsconfig.esnext.json @@ -0,0 +1,28 @@ +{ + "extends": "../../../tsconfig.base.esnext.json", + "compilerOptions": { + "outDir": "build/esnext", + "rootDir": "src", + "tsBuildInfoFile": "build/esnext/tsconfig.esnext.tsbuildinfo" + }, + "include": [ + "src/**/*.ts" + ], + "references": [ + { + "path": "../../../packages/opentelemetry-core" + }, + { + "path": "../api-logs" + }, + { + "path": "../otlp-exporter-base" + }, + { + "path": "../otlp-transformer" + }, + { + "path": "../sdk-logs" + } + ] +} diff --git a/experimental/packages/exporter-logs-otlp-http/tsconfig.json b/experimental/packages/exporter-logs-otlp-http/tsconfig.json new file mode 100644 index 0000000000..c4eda837a9 --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-http/tsconfig.json @@ -0,0 +1,29 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "esModuleInterop": true, + "outDir": "build", + "rootDir": "." + }, + "include": [ + "src/**/*.ts", + "test/**/*.ts" + ], + "references": [ + { + "path": "../../../packages/opentelemetry-core" + }, + { + "path": "../api-logs" + }, + { + "path": "../otlp-exporter-base" + }, + { + "path": "../otlp-transformer" + }, + { + "path": "../sdk-logs" + } + ] +} diff --git a/experimental/packages/exporter-logs-otlp-proto/.eslintignore b/experimental/packages/exporter-logs-otlp-proto/.eslintignore new file mode 100644 index 0000000000..378eac25d3 --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-proto/.eslintignore @@ -0,0 +1 @@ +build diff --git a/experimental/packages/exporter-logs-otlp-proto/.eslintrc.js b/experimental/packages/exporter-logs-otlp-proto/.eslintrc.js new file mode 100644 index 0000000000..3ed0fbeba3 --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-proto/.eslintrc.js @@ -0,0 +1,8 @@ +module.exports = { + "env": { + "mocha": true, + "commonjs": true, + "node": true, + }, + ...require('../../../eslint.config.js') +} diff --git a/experimental/packages/exporter-logs-otlp-proto/.npmignore b/experimental/packages/exporter-logs-otlp-proto/.npmignore new file mode 100644 index 0000000000..9505ba9450 --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-proto/.npmignore @@ -0,0 +1,4 @@ +/bin +/coverage +/doc +/test diff --git a/experimental/packages/exporter-logs-otlp-proto/LICENSE b/experimental/packages/exporter-logs-otlp-proto/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-proto/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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 + + http://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. diff --git a/experimental/packages/exporter-logs-otlp-proto/README.md b/experimental/packages/exporter-logs-otlp-proto/README.md new file mode 100644 index 0000000000..3019f50d3f --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-proto/README.md @@ -0,0 +1,110 @@ +# An OTLP exporter to send logs using protobuf over HTTP + +[![NPM Published Version][npm-img]][npm-url] +[![Apache License][license-image]][license-image] + +**Note: This is an experimental package under active development. New releases may include breaking changes.** + +This module provides an exporter for OTLP (http/protobuf) logs using protocol version `v0.18`. + +## Installation + +```bash +npm install --save @opentelemetry/exporter-logs-otlp-proto +``` + +## Further Documentation + +To see documentation and sample code for the traces exporter, as well as instructions for using TLS, see the [exporter-trace-otlp-proto package][trace-exporter-url]. +To see documentation and sample code for the metric exporter, see the [exporter-trace-otlp-proto package][metrics-exporter-url]. + +## Example Setup + +```js +const { LoggerProvider, SimpleLogRecordProcessor } = require('@opentelemetry/sdk-logs'); +const { OTLPLogsExporter } = require('@opentelemetry/exporter-logs-otlp-proto'); + +const collectorOptions = { + url: '', // url is optional and can be omitted - default is http://localhost:4318/v1/logs + headers: { + foo: 'bar' + }, //an optional object containing custom headers to be sent with each request will only work with http +}; + +const logProvider = new LoggerProvider({resource: new Resource({'service.name': 'testApp'})}); +const logExporter = new OTLPLogsExporter(collectorOptions); +logProvider.addLogRecordProcessor(new SimpleLogRecordProcessor(exporter)); + +const logger = logProvider.getLogger('test_log_instrumentation'); + +logger.emit({ + //log data to emit +}) +``` + +## Exporter Timeout Configuration + +The OTLPLogsExporter has a timeout configuration option which is the maximum time, in milliseconds, the OTLP exporter will wait for each batch export. The default value is 10000ms. + +To override the default timeout duration, use the following options: + ++ Set with environment variables: + + | Environment variable | Description | +------------------------------|----------------------|-------------| + | OTEL_EXPORTER_OTLP_LOGS_TIMEOUT | The maximum waiting time, in milliseconds, allowed to send each OTLP trace batch. Default is 10000. | + | OTEL_EXPORTER_OTLP_TIMEOUT | The maximum waiting time, in milliseconds, allowed to send each OTLP trace and metric batch. Default is 10000. | + + > `OTEL_EXPORTER_OTLP_LOGS_TIMEOUT` takes precedence and overrides `OTEL_EXPORTER_OTLP_TIMEOUT`. + ++ Provide `timeoutMillis` to OTLPLogsExporter with `collectorOptions`: + + ```js + const collectorOptions = { + timeoutMillis: 15000, + url: '', // url is optional and can be omitted - default is http://localhost:4318/v1/logs + headers: { + foo: 'bar' + }, //an optional object containing custom headers to be sent with each request will only work with http + }; + + const exporter = new OTLPLogsExporter(collectorOptions); + ``` + + > Providing `timeoutMillis` with `collectorOptions` takes precedence and overrides timeout set with environment variables. + +## OTLP Exporter Retry + +OTLP requires that transient errors be handled with a [retry strategy](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#retry). + +This retry policy has the following configuration, which there is currently no way to customize. + ++ `DEFAULT_EXPORT_MAX_ATTEMPTS`: The maximum number of attempts, including the original request. Defaults to 5. ++ `DEFAULT_EXPORT_INITIAL_BACKOFF`: The initial backoff duration. Defaults to 1 second. ++ `DEFAULT_EXPORT_MAX_BACKOFF`: The maximum backoff duration. Defaults to 5 seconds. ++ `DEFAULT_EXPORT_BACKOFF_MULTIPLIER`: The backoff multiplier. Defaults to 1.5. + +This retry policy first checks if the response has a `'Retry-After'` header. If there is a `'Retry-After'` header, the exporter will wait the amount specified in the `'Retry-After'` header before retrying. If there is no `'Retry-After'` header, the exporter will use an exponential backoff with jitter retry strategy. + + > The exporter will retry exporting within the [exporter timeout configuration](#Exporter-Timeout-Configuration) time. + +## Useful links + ++ For more information on OpenTelemetry, visit: ++ For more about OpenTelemetry JavaScript: ++ For help or feedback on this project, join us in [GitHub Discussions][discussions-url] + +## License + +Apache 2.0 - See [LICENSE][license-url] for more information. + +[discussions-url]: https://github.com/open-telemetry/opentelemetry-js/discussions +[license-url]: https://github.com/open-telemetry/opentelemetry-js/blob/main/LICENSE +[license-image]: https://img.shields.io/badge/license-Apache_2.0-green.svg?style=flat +[npm-url]: https://www.npmjs.com/package/@opentelemetry/exporter-logs-otlp-proto +[npm-img]: https://badge.fury.io/js/%40opentelemetry%2Fexporter-logs-otlp-proto.svg +[opentelemetry-collector-url]: https://github.com/open-telemetry/opentelemetry-collector +[semconv-resource-service-name]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/semantic_conventions/README.md#service +[logs-exporter-url]: https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-exporter-logs-otlp-proto +[trace-exporter-url]: https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/exporter-trace-otlp-proto +[metrics-exporter-url]: https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-exporter-metrics-otlp-proto diff --git a/experimental/packages/exporter-logs-otlp-proto/karma.conf.js b/experimental/packages/exporter-logs-otlp-proto/karma.conf.js new file mode 100644 index 0000000000..4c60b54edb --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-proto/karma.conf.js @@ -0,0 +1,26 @@ +/*! + * 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 + * + * http://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. + */ + +const karmaWebpackConfig = require('../../../karma.webpack'); +const karmaBaseConfig = require('../../../karma.base'); + +module.exports = (config) => { + config.set(Object.assign({}, karmaBaseConfig, { + webpack: karmaWebpackConfig, + files: ['test/browser/index-webpack.ts'], + preprocessors: { 'test/browser/index-webpack.ts': ['webpack'] } + })) +}; diff --git a/experimental/packages/exporter-logs-otlp-proto/package.json b/experimental/packages/exporter-logs-otlp-proto/package.json new file mode 100644 index 0000000000..016ec13486 --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-proto/package.json @@ -0,0 +1,95 @@ +{ + "name": "@opentelemetry/exporter-logs-otlp-proto", + "version": "0.39.1", + "description": "An OTLP exporter to send logs using protobuf over HTTP", + "main": "build/src/index.js", + "module": "build/esm/index.js", + "esnext": "build/esnext/index.js", + "types": "build/src/index.d.ts", + "repository": "open-telemetry/opentelemetry-js", + "browser": { + "./src/platform/index.ts": "./src/platform/browser/index.ts", + "./build/esm/platform/index.js": "./build/esm/platform/browser/index.js", + "./build/esnext/platform/index.js": "./build/esnext/platform/browser/index.js", + "./build/src/platform/index.js": "./build/src/platform/browser/index.js" + }, + "scripts": { + "prepublishOnly": "npm run compile", + "compile": "tsc --build tsconfig.json tsconfig.esm.json tsconfig.esnext.json", + "clean": "tsc --build --clean tsconfig.json tsconfig.esm.json tsconfig.esnext.json", + "lint": "eslint . --ext .ts", + "lint:fix": "eslint . --ext .ts --fix", + "tdd": "npm run test -- --watch-extensions ts --watch", + "test": "nyc ts-mocha -p tsconfig.json 'test/**/*.test.ts' --exclude 'test/browser/**/*.ts'", + "test:browser": "nyc karma start --single-run", + "version": "node ../../../scripts/version-update.js", + "watch": "tsc --build --watch tsconfig.json tsconfig.esm.json tsconfig.esnext.json", + "precompile": "lerna run version --scope $(npm pkg get name) --include-dependencies", + "prewatch": "npm run precompile", + "peer-api-check": "node ../../../scripts/peer-api-check.js", + "codecov": "nyc report --reporter=json && codecov -f coverage/*.json -p ../../../" + }, + "keywords": [ + "opentelemetry", + "nodejs", + "protobuf", + "tracing", + "profiling", + "metrics", + "stats", + "logs" + ], + "author": "OpenTelemetry Authors", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + }, + "files": [ + "build/esm/**/*.js", + "build/esm/**/*.js.map", + "build/esm/**/*.d.ts", + "build/esnext/**/*.js", + "build/esnext/**/*.js.map", + "build/esnext/**/*.d.ts", + "build/src/**/*.js", + "build/src/**/*.js.map", + "build/src/**/*.d.ts", + "build/protos/**/*.proto", + "doc", + "LICENSE", + "README.md" + ], + "publishConfig": { + "access": "public" + }, + "devDependencies": { + "@babel/core": "7.16.0", + "@opentelemetry/api": "1.4.1", + "@types/mocha": "10.0.0", + "@types/node": "18.6.5", + "@types/sinon": "10.0.13", + "codecov": "3.8.3", + "cpx": "1.5.0", + "mocha": "10.0.0", + "nyc": "15.1.0", + "sinon": "15.0.0", + "ts-loader": "8.4.0", + "ts-mocha": "10.0.0", + "typescript": "4.4.4" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" + }, + "dependencies": { + "@opentelemetry/core": "1.13.0", + "@opentelemetry/otlp-transformer": "0.39.1", + "@opentelemetry/resources": "1.13.0", + "@opentelemetry/sdk-trace-base": "1.13.0", + "@opentelemetry/api-logs": "^0.39.1", + "@opentelemetry/otlp-exporter-base": "0.39.1", + "@opentelemetry/otlp-proto-exporter-base": "0.39.1", + "@opentelemetry/sdk-logs": "^0.39.1" + }, + "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/exporter-logs-otlp-proto", + "sideEffects": false +} diff --git a/experimental/packages/exporter-logs-otlp-proto/src/index.ts b/experimental/packages/exporter-logs-otlp-proto/src/index.ts new file mode 100644 index 0000000000..9fde4be45b --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-proto/src/index.ts @@ -0,0 +1,16 @@ +/* + * 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 { OTLPLogsExporter } from './platform'; diff --git a/experimental/packages/exporter-logs-otlp-proto/src/platform/browser/OTLPLogsExporter.ts b/experimental/packages/exporter-logs-otlp-proto/src/platform/browser/OTLPLogsExporter.ts new file mode 100644 index 0000000000..185d5094f1 --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-proto/src/platform/browser/OTLPLogsExporter.ts @@ -0,0 +1,76 @@ +/* + * 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 { getEnv, baggageUtils } from '@opentelemetry/core'; +import { + OTLPExporterConfigBase, + appendResourcePathToUrl, + appendRootPathToUrlIfNeeded, +} from '@opentelemetry/otlp-exporter-base'; +import { + OTLPProtoExporterBrowserBase, + ServiceClientType, +} from '@opentelemetry/otlp-proto-exporter-base'; +import { + createExportLogsServiceRequest, + IExportLogsServiceRequest, +} from '@opentelemetry/otlp-transformer'; + +import { ReadableLogRecord, LogRecordExporter } from '@opentelemetry/sdk-logs'; + +const DEFAULT_COLLECTOR_RESOURCE_PATH = 'v1/logs'; +const DEFAULT_COLLECTOR_URL = `http://localhost:4318/${DEFAULT_COLLECTOR_RESOURCE_PATH}`; + +/** + * Collector Trace Exporter for Web + */ +export class OTLPLogsExporter + extends OTLPProtoExporterBrowserBase< + ReadableLogRecord, + IExportLogsServiceRequest + > + implements LogRecordExporter +{ + constructor(config: OTLPExporterConfigBase = {}) { + super(config); + this._headers = Object.assign( + this._headers, + baggageUtils.parseKeyPairsIntoRecord( + getEnv().OTEL_EXPORTER_OTLP_LOGS_HEADERS + ) + ); + } + convert(logs: ReadableLogRecord[]): IExportLogsServiceRequest { + return createExportLogsServiceRequest(logs); + } + + getDefaultUrl(config: OTLPExporterConfigBase): string { + return typeof config.url === 'string' + ? config.url + : getEnv().OTEL_EXPORTER_OTLP_LOGS_ENDPOINT.length > 0 + ? appendRootPathToUrlIfNeeded(getEnv().OTEL_EXPORTER_OTLP_LOGS_ENDPOINT) + : getEnv().OTEL_EXPORTER_OTLP_ENDPOINT.length > 0 + ? appendResourcePathToUrl( + getEnv().OTEL_EXPORTER_OTLP_ENDPOINT, + DEFAULT_COLLECTOR_RESOURCE_PATH + ) + : DEFAULT_COLLECTOR_URL; + } + + getServiceClientType() { + return ServiceClientType.LOGS; + } +} diff --git a/experimental/packages/exporter-logs-otlp-proto/src/platform/browser/index.ts b/experimental/packages/exporter-logs-otlp-proto/src/platform/browser/index.ts new file mode 100644 index 0000000000..e6968b3466 --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-proto/src/platform/browser/index.ts @@ -0,0 +1,16 @@ +/* + * 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 { OTLPLogsExporter } from './OTLPLogsExporter'; diff --git a/experimental/packages/exporter-logs-otlp-proto/src/platform/index.ts b/experimental/packages/exporter-logs-otlp-proto/src/platform/index.ts new file mode 100644 index 0000000000..851ff9a015 --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-proto/src/platform/index.ts @@ -0,0 +1,16 @@ +/* + * 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 { OTLPLogsExporter } from './node'; diff --git a/experimental/packages/exporter-logs-otlp-proto/src/platform/node/OTLPLogsExporter.ts b/experimental/packages/exporter-logs-otlp-proto/src/platform/node/OTLPLogsExporter.ts new file mode 100644 index 0000000000..88d97e21d3 --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-proto/src/platform/node/OTLPLogsExporter.ts @@ -0,0 +1,76 @@ +/* + * 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 { getEnv, baggageUtils } from '@opentelemetry/core'; +import { + OTLPExporterConfigBase, + appendResourcePathToUrl, + appendRootPathToUrlIfNeeded, +} from '@opentelemetry/otlp-exporter-base'; +import { + OTLPProtoExporterNodeBase, + ServiceClientType, +} from '@opentelemetry/otlp-proto-exporter-base'; +import { + createExportLogsServiceRequest, + IExportLogsServiceRequest, +} from '@opentelemetry/otlp-transformer'; + +import { ReadableLogRecord, LogRecordExporter } from '@opentelemetry/sdk-logs'; + +const DEFAULT_COLLECTOR_RESOURCE_PATH = 'v1/logs'; +const DEFAULT_COLLECTOR_URL = `http://localhost:4318/${DEFAULT_COLLECTOR_RESOURCE_PATH}`; + +/** + * Collector Trace Exporter for Node + */ +export class OTLPLogsExporter + extends OTLPProtoExporterNodeBase< + ReadableLogRecord, + IExportLogsServiceRequest + > + implements LogRecordExporter +{ + constructor(config: OTLPExporterConfigBase = {}) { + super(config); + this.headers = Object.assign( + this.headers, + baggageUtils.parseKeyPairsIntoRecord( + getEnv().OTEL_EXPORTER_OTLP_LOGS_HEADERS + ) + ); + } + convert(logs: ReadableLogRecord[]): IExportLogsServiceRequest { + return createExportLogsServiceRequest(logs); + } + + getDefaultUrl(config: OTLPExporterConfigBase): string { + return typeof config.url === 'string' + ? config.url + : getEnv().OTEL_EXPORTER_OTLP_LOGS_ENDPOINT.length > 0 + ? appendRootPathToUrlIfNeeded(getEnv().OTEL_EXPORTER_OTLP_LOGS_ENDPOINT) + : getEnv().OTEL_EXPORTER_OTLP_ENDPOINT.length > 0 + ? appendResourcePathToUrl( + getEnv().OTEL_EXPORTER_OTLP_ENDPOINT, + DEFAULT_COLLECTOR_RESOURCE_PATH + ) + : DEFAULT_COLLECTOR_URL; + } + + getServiceClientType() { + return ServiceClientType.LOGS; + } +} diff --git a/experimental/packages/exporter-logs-otlp-proto/src/platform/node/index.ts b/experimental/packages/exporter-logs-otlp-proto/src/platform/node/index.ts new file mode 100644 index 0000000000..4797b30651 --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-proto/src/platform/node/index.ts @@ -0,0 +1,17 @@ +/* + * 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 { OTLPLogsExporter } from './OTLPLogsExporter'; diff --git a/experimental/packages/exporter-logs-otlp-proto/test/browser/OTLPLogsExporter.test.ts b/experimental/packages/exporter-logs-otlp-proto/test/browser/OTLPLogsExporter.test.ts new file mode 100644 index 0000000000..382a132c3e --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-proto/test/browser/OTLPLogsExporter.test.ts @@ -0,0 +1,50 @@ +/* + * 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 assert from 'assert'; +import * as sinon from 'sinon'; +import { OTLPLogsExporter } from '../../src/platform/browser/index'; + +describe('OTLPLogsExporter - web', () => { + let collectorLogsExporter: OTLPLogsExporter; + describe('constructor', () => { + let onInitSpy: any; + beforeEach(() => { + onInitSpy = sinon.stub(OTLPLogsExporter.prototype, 'onInit'); + const collectorExporterConfig = { + hostname: 'foo', + url: 'http://foo.bar.com', + }; + collectorLogsExporter = new OTLPLogsExporter(collectorExporterConfig); + }); + afterEach(() => { + sinon.restore(); + }); + it('should create an instance', () => { + assert.ok(typeof collectorLogsExporter !== 'undefined'); + }); + it('should call onInit', () => { + assert.strictEqual(onInitSpy.callCount, 1); + }); + it('should set hostname', () => { + assert.strictEqual(collectorLogsExporter.hostname, 'foo'); + }); + + it('should set url', () => { + assert.strictEqual(collectorLogsExporter.url, 'http://foo.bar.com'); + }); + }); +}); diff --git a/experimental/packages/exporter-logs-otlp-proto/test/browser/index-webpack.ts b/experimental/packages/exporter-logs-otlp-proto/test/browser/index-webpack.ts new file mode 100644 index 0000000000..ae7d4b5a9d --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-proto/test/browser/index-webpack.ts @@ -0,0 +1,20 @@ +/* + * 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. + */ +const testsContext = require.context('../browser', true, /test$/); +testsContext.keys().forEach(testsContext); + +const srcContext = require.context('.', true, /src$/); +srcContext.keys().forEach(srcContext); diff --git a/experimental/packages/exporter-logs-otlp-proto/test/logHelper.ts b/experimental/packages/exporter-logs-otlp-proto/test/logHelper.ts new file mode 100644 index 0000000000..7b8c8e8b9a --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-proto/test/logHelper.ts @@ -0,0 +1,186 @@ +/* + * 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 { HrTime, TraceFlags } from '@opentelemetry/api'; +import { SeverityNumber } from '@opentelemetry/api-logs'; +import { Resource } from '@opentelemetry/resources'; +import * as assert from 'assert'; +import { VERSION } from '@opentelemetry/core'; +import { + IAnyValue, + IExportLogsServiceRequest, + IKeyValue, + ILogRecord, + IResource, +} from '@opentelemetry/otlp-transformer'; +import { ReadableLogRecord } from '@opentelemetry/sdk-logs'; +import { Stream } from 'stream'; + +export const mockedReadableLogRecord: ReadableLogRecord = { + resource: Resource.default().merge( + new Resource({ + 'resource-attribute': 'some resource-attr value', + }) + ), + instrumentationScope: { + name: 'scope_name_1', + version: '0.1.0', + schemaUrl: 'http://url.to.schema', + }, + hrTime: [1680253513, 123241635] as HrTime, + hrTimeObserved: [1680253513, 123241635] as HrTime, + attributes: { + 'some-attribute': 'some attribute value', + }, + severityNumber: SeverityNumber.ERROR, + severityText: 'error', + body: 'some_log_body', + spanContext: { + traceFlags: TraceFlags.SAMPLED, + traceId: '1f1008dc8e270e85c40a0d7c3939b278', + spanId: '5e107261f64fa53e', + }, +}; +export function ensureExportedAttributesAreCorrect(attributes: IKeyValue[]) { + assert.deepStrictEqual( + attributes, + [ + { + key: 'some-attribute', + value: { + stringValue: 'some attribute value', + }, + }, + ], + 'exported attributes are incorrect' + ); +} + +export function ensureExportedBodyIsCorrect(body?: IAnyValue) { + assert.deepStrictEqual( + body, + { stringValue: 'some_log_body' }, + 'exported attributes are incorrect' + ); +} + +export function ensureExportedLogRecordIsCorrect(logRecord: ILogRecord) { + ensureExportedBodyIsCorrect(logRecord.body); + ensureExportedAttributesAreCorrect(logRecord.attributes); + assert.strictEqual( + logRecord.timeUnixNano, + '1680253513123241728', + 'timeUnixNano is wrong' + ); + assert.strictEqual( + logRecord.observedTimeUnixNano, + '1680253513123241728', + 'observedTimeUnixNano is wrong' + ); + assert.strictEqual( + logRecord.severityNumber, + 'SEVERITY_NUMBER_ERROR', + 'severityNumber is wrong' + ); + assert.strictEqual(logRecord.severityText, 'error', 'severityText is wrong'); + assert.strictEqual( + logRecord.droppedAttributesCount, + 0, + 'droppedAttributesCount is wrong' + ); + assert.strictEqual(logRecord.flags, TraceFlags.SAMPLED, 'flags is wrong'); +} + +export function ensureResourceIsCorrect(resource: IResource) { + assert.deepStrictEqual(resource, { + attributes: [ + { + key: 'service.name', + value: { + stringValue: `unknown_service:${process.argv0}`, + value: 'stringValue', + }, + }, + { + key: 'telemetry.sdk.language', + value: { + stringValue: 'nodejs', + value: 'stringValue', + }, + }, + { + key: 'telemetry.sdk.name', + value: { + stringValue: 'opentelemetry', + value: 'stringValue', + }, + }, + { + key: 'telemetry.sdk.version', + value: { + stringValue: VERSION, + value: 'stringValue', + }, + }, + { + key: 'resource-attribute', + value: { + stringValue: 'some resource-attr value', + value: 'stringValue', + }, + }, + ], + droppedAttributesCount: 0, + }); +} + +export function ensureExportLogsServiceRequestIsSet( + json: IExportLogsServiceRequest +) { + const resourceLogs = json.resourceLogs; + assert.strictEqual(resourceLogs?.length, 1, 'resourceLogs is missing'); + + const resource = resourceLogs?.[0].resource; + assert.ok(resource, 'resource is missing'); + + const scopeLogs = resourceLogs?.[0].scopeLogs; + assert.strictEqual(scopeLogs?.length, 1, 'scopeLogs is missing'); + + const scope = scopeLogs?.[0].scope; + assert.ok(scope, 'scope is missing'); + + const logRecords = scopeLogs?.[0].logRecords; + assert.strictEqual(logRecords?.length, 1, 'logs are missing'); +} + +export class MockedResponse extends Stream { + constructor(private _code: number, private _msg?: string) { + super(); + } + + send(data: string) { + this.emit('data', data); + this.emit('end'); + } + + get statusCode() { + return this._code; + } + + get statusMessage() { + return this._msg; + } +} diff --git a/experimental/packages/exporter-logs-otlp-proto/test/node/OTLPLogsExporter.test.ts b/experimental/packages/exporter-logs-otlp-proto/test/node/OTLPLogsExporter.test.ts new file mode 100644 index 0000000000..994a2f0ea4 --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-proto/test/node/OTLPLogsExporter.test.ts @@ -0,0 +1,416 @@ +/* + * 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 { diag } from '@opentelemetry/api'; +import { ExportResultCode } from '@opentelemetry/core'; +import * as assert from 'assert'; +import * as http from 'http'; +import * as sinon from 'sinon'; +import { Stream, PassThrough } from 'stream'; +import * as zlib from 'zlib'; +import { OTLPLogsExporter } from '../../src'; +import { + ensureExportLogsServiceRequestIsSet, + ensureExportedLogRecordIsCorrect, + mockedReadableLogRecord, + MockedResponse, +} from '../logHelper'; +import { + CompressionAlgorithm, + OTLPExporterNodeConfigBase, + OTLPExporterError, +} from '@opentelemetry/otlp-exporter-base'; +import { + getExportRequestProto, + ServiceClientType, +} from '@opentelemetry/otlp-proto-exporter-base'; +import { IExportLogsServiceRequest } from '@opentelemetry/otlp-transformer'; +import { ReadableLogRecord } from '@opentelemetry/sdk-logs'; + +let fakeRequest: PassThrough; + +describe('OTLPLogsExporter - node with proto over http', () => { + let collectorExporter: OTLPLogsExporter; + let collectorExporterConfig: OTLPExporterNodeConfigBase; + let logs: ReadableLogRecord[]; + + afterEach(() => { + fakeRequest = new Stream.PassThrough(); + sinon.restore(); + }); + + describe('when configuring via environment', () => { + const envSource = process.env; + it('should use url defined in env that ends with root path and append version and signal path', () => { + envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar/'; + const collectorExporter = new OTLPLogsExporter(); + assert.strictEqual( + collectorExporter.url, + `${envSource.OTEL_EXPORTER_OTLP_ENDPOINT}v1/logs` + ); + envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; + }); + it('should use url defined in env without checking if path is already present', () => { + envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar/v1/logs'; + const collectorExporter = new OTLPLogsExporter(); + assert.strictEqual( + collectorExporter.url, + `${envSource.OTEL_EXPORTER_OTLP_ENDPOINT}/v1/logs` + ); + envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; + }); + it('should use url defined in env and append version and signal', () => { + envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar'; + const collectorExporter = new OTLPLogsExporter(); + assert.strictEqual( + collectorExporter.url, + `${envSource.OTEL_EXPORTER_OTLP_ENDPOINT}/v1/logs` + ); + 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 OTLPLogsExporter(); + assert.strictEqual( + collectorExporter.url, + envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT + ); + envSource.OTEL_EXPORTER_OTLP_ENDPOINT = ''; + envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = ''; + }); + it('should add root path when signal url defined in env contains no path and no root path', () => { + envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = 'http://foo.bar'; + const collectorExporter = new OTLPLogsExporter(); + assert.strictEqual( + collectorExporter.url, + `${envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT}/` + ); + envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = ''; + }); + it('should not add root path when signal url defined in env contains root path but no path', () => { + envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = 'http://foo.bar/'; + const collectorExporter = new OTLPLogsExporter(); + assert.strictEqual( + collectorExporter.url, + `${envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT}` + ); + envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = ''; + }); + it('should not add root path when signal url defined in env contains path', () => { + envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = 'http://foo.bar/v1/logs'; + const collectorExporter = new OTLPLogsExporter(); + assert.strictEqual( + collectorExporter.url, + `${envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT}` + ); + envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = ''; + }); + it('should not add root path when signal url defined in env contains path and ends in /', () => { + envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = 'http://foo.bar/v1/logs/'; + const collectorExporter = new OTLPLogsExporter(); + assert.strictEqual( + collectorExporter.url, + `${envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT}` + ); + envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = ''; + }); + it('should use headers defined via env', () => { + envSource.OTEL_EXPORTER_OTLP_LOGS_HEADERS = 'foo=bar'; + const collectorExporter = new OTLPLogsExporter(); + assert.strictEqual(collectorExporter.headers.foo, 'bar'); + envSource.OTEL_EXPORTER_OTLP_HEADERS = ''; + }); + it('should override global headers config with signal headers defined via env', () => { + envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar,bar=foo'; + envSource.OTEL_EXPORTER_OTLP_LOGS_HEADERS = 'foo=boo'; + const collectorExporter = new OTLPLogsExporter(); + assert.strictEqual(collectorExporter.headers.foo, 'boo'); + assert.strictEqual(collectorExporter.headers.bar, 'foo'); + envSource.OTEL_EXPORTER_OTLP_LOGS_HEADERS = ''; + envSource.OTEL_EXPORTER_OTLP_HEADERS = ''; + }); + }); + + describe('export', () => { + beforeEach(() => { + collectorExporterConfig = { + headers: { + foo: 'bar', + }, + hostname: 'foo', + url: 'http://foo.bar.com', + keepAlive: true, + httpAgentOptions: { keepAliveMsecs: 2000 }, + }; + collectorExporter = new OTLPLogsExporter(collectorExporterConfig); + logs = []; + logs.push(Object.assign({}, mockedReadableLogRecord)); + }); + afterEach(() => { + sinon.restore(); + }); + + it('should open the connection', done => { + collectorExporter.export(logs, () => {}); + + sinon.stub(http, 'request').callsFake((options: any, cb: any) => { + assert.strictEqual(options.hostname, 'foo.bar.com'); + assert.strictEqual(options.method, 'POST'); + assert.strictEqual(options.path, '/'); + + const mockRes = new MockedResponse(200); + cb(mockRes); + mockRes.send('success'); + done(); + return fakeRequest as any; + }); + }); + + it('should set custom headers', done => { + collectorExporter.export(logs, () => {}); + + sinon.stub(http, 'request').callsFake((options: any, cb: any) => { + assert.strictEqual(options.headers['foo'], 'bar'); + + const mockRes = new MockedResponse(200); + cb(mockRes); + mockRes.send('success'); + done(); + return fakeRequest as any; + }); + }); + + it('should have keep alive and keepAliveMsecs option set', done => { + collectorExporter.export(logs, () => {}); + + sinon.stub(http, 'request').callsFake((options: any, cb: any) => { + assert.strictEqual(options.agent.keepAlive, true); + assert.strictEqual(options.agent.options.keepAliveMsecs, 2000); + + const mockRes = new MockedResponse(200); + cb(mockRes); + mockRes.send('success'); + done(); + return fakeRequest as any; + }); + }); + + it('should successfully send the logs', done => { + const fakeRequest = new Stream.PassThrough(); + sinon.stub(http, 'request').returns(fakeRequest as any); + + let buff = Buffer.from(''); + fakeRequest.on('end', () => { + const ExportLogsServiceRequestProto = getExportRequestProto( + ServiceClientType.LOGS + ); + const data = ExportLogsServiceRequestProto.decode(buff); + const json = data?.toJSON() as IExportLogsServiceRequest; + const log1 = json.resourceLogs?.[0].scopeLogs?.[0].logRecords?.[0]; + assert.ok(typeof log1 !== 'undefined', "log doesn't exist"); + ensureExportedLogRecordIsCorrect(log1); + + ensureExportLogsServiceRequestIsSet(json); + + done(); + }); + + fakeRequest.on('data', chunk => { + buff = Buffer.concat([buff, chunk]); + }); + + const clock = sinon.useFakeTimers(); + collectorExporter.export(logs, () => {}); + clock.tick(200); + clock.restore(); + }); + + it('should log the successful message', done => { + // Need to stub/spy on the underlying logger as the "diag" instance is global + const spyLoggerError = sinon.stub(diag, 'error'); + + collectorExporter.export(logs, result => { + assert.strictEqual(result.code, ExportResultCode.SUCCESS); + assert.strictEqual(spyLoggerError.args.length, 0); + done(); + }); + + sinon.stub(http, 'request').callsFake((options: any, cb: any) => { + const mockRes = new MockedResponse(200); + cb(mockRes); + mockRes.send('success'); + return fakeRequest as any; + }); + }); + + it('should log the error message', done => { + collectorExporter.export(logs, result => { + assert.strictEqual(result.code, ExportResultCode.FAILED); + // @ts-expect-error verify error code + assert.strictEqual(result.error.code, 400); + done(); + }); + + sinon.stub(http, 'request').callsFake((options: any, cb: any) => { + const mockResError = new MockedResponse(400); + cb(mockResError); + mockResError.send('failed'); + + return fakeRequest as any; + }); + }); + }); + describe('export - with compression', () => { + beforeEach(() => { + collectorExporterConfig = { + headers: { + foo: 'bar', + }, + hostname: 'foo', + url: 'http://foo.bar.com', + keepAlive: true, + compression: CompressionAlgorithm.GZIP, + httpAgentOptions: { keepAliveMsecs: 2000 }, + }; + collectorExporter = new OTLPLogsExporter(collectorExporterConfig); + logs = []; + logs.push(Object.assign({}, mockedReadableLogRecord)); + }); + afterEach(() => { + sinon.restore(); + }); + + it('should successfully send the logs', done => { + const fakeRequest = new Stream.PassThrough(); + sinon.stub(http, 'request').returns(fakeRequest as any); + const spySetHeader = sinon.spy(); + (fakeRequest as any).setHeader = spySetHeader; + + let buff = Buffer.from(''); + fakeRequest.on('end', () => { + const unzippedBuff = zlib.gunzipSync(buff); + const ExportLogsServiceRequestProto = getExportRequestProto( + ServiceClientType.LOGS + ); + const data = ExportLogsServiceRequestProto.decode(unzippedBuff); + const json = data?.toJSON() as IExportLogsServiceRequest; + const log1 = json.resourceLogs?.[0].scopeLogs?.[0].logRecords?.[0]; + assert.ok(typeof log1 !== 'undefined', "log doesn't exist"); + ensureExportedLogRecordIsCorrect(log1); + + ensureExportLogsServiceRequestIsSet(json); + assert.ok(spySetHeader.calledWith('Content-Encoding', 'gzip')); + + done(); + }); + + fakeRequest.on('data', chunk => { + buff = Buffer.concat([buff, chunk]); + }); + + const clock = sinon.useFakeTimers(); + collectorExporter.export(logs, () => {}); + clock.tick(200); + clock.restore(); + }); + }); +}); + +describe('export - real http request destroyed before response received', () => { + let collectorExporter: OTLPLogsExporter; + let collectorExporterConfig: OTLPExporterNodeConfigBase; + let logs: ReadableLogRecord[]; + const server = http.createServer((_, res) => { + setTimeout(() => { + res.statusCode = 200; + res.end(); + }, 200); + }); + before(done => { + server.listen(8082, done); + }); + after(done => { + server.close(done); + }); + it('should log the timeout request error message when timeout is 1', done => { + collectorExporterConfig = { + url: 'http://localhost:8082', + timeoutMillis: 1, + }; + collectorExporter = new OTLPLogsExporter(collectorExporterConfig); + logs = []; + logs.push(Object.assign({}, mockedReadableLogRecord)); + + collectorExporter.export(logs, result => { + assert.strictEqual(result.code, ExportResultCode.FAILED); + const error = result.error as OTLPExporterError; + assert.ok(error !== undefined); + assert.strictEqual(error.message, 'Request Timeout'); + done(); + }); + }); + it('should log the timeout request error message when timeout is 100', done => { + collectorExporterConfig = { + url: 'http://localhost:8082', + timeoutMillis: 100, + }; + collectorExporter = new OTLPLogsExporter(collectorExporterConfig); + logs = []; + logs.push(Object.assign({}, mockedReadableLogRecord)); + + collectorExporter.export(logs, result => { + assert.strictEqual(result.code, ExportResultCode.FAILED); + const error = result.error as OTLPExporterError; + assert.ok(error !== undefined); + assert.strictEqual(error.message, 'Request Timeout'); + done(); + }); + }); +}); + +describe('export - real http request destroyed after response received', () => { + let collectorExporter: OTLPLogsExporter; + let collectorExporterConfig: OTLPExporterNodeConfigBase; + let logs: ReadableLogRecord[]; + + const server = http.createServer((_, res) => { + res.write('writing something'); + }); + before(done => { + server.listen(8082, done); + }); + after(done => { + server.close(done); + }); + it('should log the timeout request error message', done => { + collectorExporterConfig = { + url: 'http://localhost:8082', + timeoutMillis: 300, + }; + collectorExporter = new OTLPLogsExporter(collectorExporterConfig); + logs = []; + logs.push(Object.assign({}, mockedReadableLogRecord)); + + collectorExporter.export(logs, result => { + assert.strictEqual(result.code, ExportResultCode.FAILED); + const error = result.error as OTLPExporterError; + assert.ok(error !== undefined); + assert.strictEqual(error.message, 'Request Timeout'); + done(); + }); + }); +}); diff --git a/experimental/packages/exporter-logs-otlp-proto/tsconfig.esm.json b/experimental/packages/exporter-logs-otlp-proto/tsconfig.esm.json new file mode 100644 index 0000000000..5eba4694be --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-proto/tsconfig.esm.json @@ -0,0 +1,40 @@ +{ + "extends": "../../../tsconfig.base.esm.json", + "compilerOptions": { + "outDir": "build/esm", + "rootDir": "src", + "tsBuildInfoFile": "build/esm/tsconfig.esm.tsbuildinfo" + }, + "include": [ + "src/**/*.ts" + ], + "references": [ + { + "path": "../../../api" + }, + { + "path": "../../../packages/opentelemetry-core" + }, + { + "path": "../../../packages/opentelemetry-resources" + }, + { + "path": "../../../packages/opentelemetry-sdk-trace-base" + }, + { + "path": "../api-logs" + }, + { + "path": "../otlp-exporter-base" + }, + { + "path": "../otlp-proto-exporter-base" + }, + { + "path": "../otlp-transformer" + }, + { + "path": "../sdk-logs" + } + ] +} diff --git a/experimental/packages/exporter-logs-otlp-proto/tsconfig.esnext.json b/experimental/packages/exporter-logs-otlp-proto/tsconfig.esnext.json new file mode 100644 index 0000000000..b37520a70f --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-proto/tsconfig.esnext.json @@ -0,0 +1,40 @@ +{ + "extends": "../../../tsconfig.base.esnext.json", + "compilerOptions": { + "outDir": "build/esnext", + "rootDir": "src", + "tsBuildInfoFile": "build/esnext/tsconfig.esnext.tsbuildinfo" + }, + "include": [ + "src/**/*.ts" + ], + "references": [ + { + "path": "../../../api" + }, + { + "path": "../../../packages/opentelemetry-core" + }, + { + "path": "../../../packages/opentelemetry-resources" + }, + { + "path": "../../../packages/opentelemetry-sdk-trace-base" + }, + { + "path": "../api-logs" + }, + { + "path": "../otlp-exporter-base" + }, + { + "path": "../otlp-proto-exporter-base" + }, + { + "path": "../otlp-transformer" + }, + { + "path": "../sdk-logs" + } + ] +} diff --git a/experimental/packages/exporter-logs-otlp-proto/tsconfig.json b/experimental/packages/exporter-logs-otlp-proto/tsconfig.json new file mode 100644 index 0000000000..2349f8706c --- /dev/null +++ b/experimental/packages/exporter-logs-otlp-proto/tsconfig.json @@ -0,0 +1,40 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "build", + "rootDir": "." + }, + "include": [ + "src/**/*.ts", + "test/**/*.ts" + ], + "references": [ + { + "path": "../../../api" + }, + { + "path": "../../../packages/opentelemetry-core" + }, + { + "path": "../../../packages/opentelemetry-resources" + }, + { + "path": "../../../packages/opentelemetry-sdk-trace-base" + }, + { + "path": "../api-logs" + }, + { + "path": "../otlp-exporter-base" + }, + { + "path": "../otlp-proto-exporter-base" + }, + { + "path": "../otlp-transformer" + }, + { + "path": "../sdk-logs" + } + ] +} diff --git a/experimental/packages/exporter-trace-otlp-grpc/package.json b/experimental/packages/exporter-trace-otlp-grpc/package.json index 62d71a5124..506bf706e8 100644 --- a/experimental/packages/exporter-trace-otlp-grpc/package.json +++ b/experimental/packages/exporter-trace-otlp-grpc/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/exporter-trace-otlp-grpc", - "version": "0.38.0", + "version": "0.39.1", "description": "OpenTelemetry Collector Exporter allows user to send collected traces to the OpenTelemetry Collector", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -50,7 +50,7 @@ "@babel/core": "7.16.0", "@grpc/proto-loader": "^0.7.3", "@opentelemetry/api": "1.4.1", - "@opentelemetry/otlp-exporter-base": "0.38.0", + "@opentelemetry/otlp-exporter-base": "0.39.1", "@types/mocha": "10.0.0", "@types/node": "18.6.5", "@types/sinon": "10.0.13", @@ -68,11 +68,11 @@ }, "dependencies": { "@grpc/grpc-js": "^1.7.1", - "@opentelemetry/core": "1.12.0", - "@opentelemetry/otlp-grpc-exporter-base": "0.38.0", - "@opentelemetry/otlp-transformer": "0.38.0", - "@opentelemetry/resources": "1.12.0", - "@opentelemetry/sdk-trace-base": "1.12.0" + "@opentelemetry/core": "1.13.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.39.1", + "@opentelemetry/otlp-transformer": "0.39.1", + "@opentelemetry/resources": "1.13.0", + "@opentelemetry/sdk-trace-base": "1.13.0" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/exporter-trace-otlp-grpc", "sideEffects": false diff --git a/experimental/packages/exporter-trace-otlp-grpc/src/OTLPTraceExporter.ts b/experimental/packages/exporter-trace-otlp-grpc/src/OTLPTraceExporter.ts index 2cc5abcb2e..c99826a176 100644 --- a/experimental/packages/exporter-trace-otlp-grpc/src/OTLPTraceExporter.ts +++ b/experimental/packages/exporter-trace-otlp-grpc/src/OTLPTraceExporter.ts @@ -28,6 +28,11 @@ import { createExportTraceServiceRequest, IExportTraceServiceRequest, } from '@opentelemetry/otlp-transformer'; +import { VERSION } from './version'; + +const USER_AGENT = { + 'User-Agent': `OTel-OTLP-Exporter-JavaScript/${VERSION}`, +}; /** * OTLP Trace Exporter for Node @@ -38,9 +43,12 @@ export class OTLPTraceExporter { constructor(config: OTLPGRPCExporterConfigNode = {}) { super(config); - const headers = baggageUtils.parseKeyPairsIntoRecord( - getEnv().OTEL_EXPORTER_OTLP_TRACES_HEADERS - ); + const headers = { + ...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); 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 29c080b966..17e5511010 100644 --- a/experimental/packages/exporter-trace-otlp-grpc/test/OTLPTraceExporter.test.ts +++ b/experimental/packages/exporter-trace-otlp-grpc/test/OTLPTraceExporter.test.ts @@ -27,6 +27,7 @@ import * as grpc from '@grpc/grpc-js'; import * as path from 'path'; import * as sinon from 'sinon'; import { OTLPTraceExporter } from '../src'; +import { VERSION } from '../src/version'; import { ensureExportedSpanIsCorrect, @@ -336,6 +337,12 @@ describe('when configuring via environment', () => { assert.deepStrictEqual(collectorExporter.metadata?.get('foo'), ['bar']); envSource.OTEL_EXPORTER_OTLP_HEADERS = ''; }); + it('should include user agent in header', () => { + const collectorExporter = new OTLPTraceExporter(); + assert.deepStrictEqual(collectorExporter.metadata?.get('User-Agent'), [ + `OTel-OTLP-Exporter-JavaScript/${VERSION}`, + ]); + }); it('should override global headers config with signal headers defined via env', () => { const metadata = new grpc.Metadata(); metadata.set('foo', 'bar'); diff --git a/experimental/packages/exporter-trace-otlp-http/package.json b/experimental/packages/exporter-trace-otlp-http/package.json index 72cb324a81..2910830372 100644 --- a/experimental/packages/exporter-trace-otlp-http/package.json +++ b/experimental/packages/exporter-trace-otlp-http/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/exporter-trace-otlp-http", - "version": "0.38.0", + "version": "0.39.1", "description": "OpenTelemetry Collector Trace Exporter allows user to send collected traces to the OpenTelemetry Collector", "main": "build/src/index.js", "module": "build/esm/index.js", @@ -94,11 +94,11 @@ "@opentelemetry/api": "^1.0.0" }, "dependencies": { - "@opentelemetry/core": "1.12.0", - "@opentelemetry/otlp-exporter-base": "0.38.0", - "@opentelemetry/otlp-transformer": "0.38.0", - "@opentelemetry/resources": "1.12.0", - "@opentelemetry/sdk-trace-base": "1.12.0" + "@opentelemetry/core": "1.13.0", + "@opentelemetry/otlp-exporter-base": "0.39.1", + "@opentelemetry/otlp-transformer": "0.39.1", + "@opentelemetry/resources": "1.13.0", + "@opentelemetry/sdk-trace-base": "1.13.0" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/exporter-trace-otlp-http", "sideEffects": false 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 8704baf576..f10fbd0ec0 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 @@ -26,9 +26,13 @@ import { createExportTraceServiceRequest, IExportTraceServiceRequest, } from '@opentelemetry/otlp-transformer'; +import { VERSION } from '../../version'; const DEFAULT_COLLECTOR_RESOURCE_PATH = 'v1/traces'; const DEFAULT_COLLECTOR_URL = `http://localhost:4318/${DEFAULT_COLLECTOR_RESOURCE_PATH}`; +const USER_AGENT = { + 'User-Agent': `OTel-OTLP-Exporter-JavaScript/${VERSION}`, +}; /** * Collector Trace Exporter for Node @@ -39,12 +43,13 @@ export class OTLPTraceExporter { constructor(config: OTLPExporterNodeConfigBase = {}) { super(config); - this.headers = Object.assign( - this.headers, - baggageUtils.parseKeyPairsIntoRecord( + this.headers = { + ...this.headers, + ...USER_AGENT, + ...baggageUtils.parseKeyPairsIntoRecord( getEnv().OTEL_EXPORTER_OTLP_TRACES_HEADERS - ) - ); + ), + }; } convert(spans: ReadableSpan[]): IExportTraceServiceRequest { diff --git a/experimental/packages/exporter-trace-otlp-http/test/node/CollectorTraceExporter.test.ts b/experimental/packages/exporter-trace-otlp-http/test/node/CollectorTraceExporter.test.ts index 426aed4431..9238260d30 100644 --- a/experimental/packages/exporter-trace-otlp-http/test/node/CollectorTraceExporter.test.ts +++ b/experimental/packages/exporter-trace-otlp-http/test/node/CollectorTraceExporter.test.ts @@ -36,6 +36,7 @@ import { import { nextTick } from 'process'; import { MockedResponse } from './nodeHelpers'; import { IExportTraceServiceRequest } from '@opentelemetry/otlp-transformer'; +import { VERSION } from '../../src/version'; let fakeRequest: PassThrough; @@ -160,6 +161,13 @@ describe('OTLPTraceExporter - node with json over http', () => { assert.strictEqual(collectorExporter.headers.foo, 'bar'); envSource.OTEL_EXPORTER_OTLP_HEADERS = ''; }); + it('should include user agent in header', () => { + const collectorExporter = new OTLPTraceExporter(); + assert.strictEqual( + collectorExporter.headers['User-Agent'], + `OTel-OTLP-Exporter-JavaScript/${VERSION}` + ); + }); it('should override global headers config with signal headers defined via env', () => { envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar,bar=foo'; envSource.OTEL_EXPORTER_OTLP_TRACES_HEADERS = 'foo=boo'; diff --git a/experimental/packages/exporter-trace-otlp-proto/package.json b/experimental/packages/exporter-trace-otlp-proto/package.json index 10fb854c80..bfe610b4d2 100644 --- a/experimental/packages/exporter-trace-otlp-proto/package.json +++ b/experimental/packages/exporter-trace-otlp-proto/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/exporter-trace-otlp-proto", - "version": "0.38.0", + "version": "0.39.1", "description": "OpenTelemetry Collector Exporter allows user to send collected traces to the OpenTelemetry Collector using protobuf over HTTP", "main": "build/src/index.js", "module": "build/esm/index.js", @@ -80,12 +80,12 @@ "@opentelemetry/api": "^1.0.0" }, "dependencies": { - "@opentelemetry/core": "1.12.0", - "@opentelemetry/otlp-exporter-base": "0.38.0", - "@opentelemetry/otlp-proto-exporter-base": "0.38.0", - "@opentelemetry/otlp-transformer": "0.38.0", - "@opentelemetry/resources": "1.12.0", - "@opentelemetry/sdk-trace-base": "1.12.0" + "@opentelemetry/core": "1.13.0", + "@opentelemetry/otlp-exporter-base": "0.39.1", + "@opentelemetry/otlp-proto-exporter-base": "0.39.1", + "@opentelemetry/otlp-transformer": "0.39.1", + "@opentelemetry/resources": "1.13.0", + "@opentelemetry/sdk-trace-base": "1.13.0" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/exporter-trace-otlp-proto", "sideEffects": false 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 0634013311..210a16145a 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 @@ -29,9 +29,13 @@ import { createExportTraceServiceRequest, IExportTraceServiceRequest, } from '@opentelemetry/otlp-transformer'; +import { VERSION } from '../../version'; const DEFAULT_COLLECTOR_RESOURCE_PATH = 'v1/traces'; const DEFAULT_COLLECTOR_URL = `http://localhost:4318/${DEFAULT_COLLECTOR_RESOURCE_PATH}`; +const USER_AGENT = { + 'User-Agent': `OTel-OTLP-Exporter-JavaScript/${VERSION}`, +}; /** * Collector Trace Exporter for Node with protobuf @@ -42,12 +46,13 @@ export class OTLPTraceExporter { constructor(config: OTLPExporterNodeConfigBase = {}) { super(config); - this.headers = Object.assign( - this.headers, - baggageUtils.parseKeyPairsIntoRecord( + this.headers = { + ...this.headers, + ...USER_AGENT, + ...baggageUtils.parseKeyPairsIntoRecord( getEnv().OTEL_EXPORTER_OTLP_TRACES_HEADERS - ) - ); + ), + }; } convert(spans: ReadableSpan[]): IExportTraceServiceRequest { diff --git a/experimental/packages/exporter-trace-otlp-proto/test/node/OTLPTraceExporter.test.ts b/experimental/packages/exporter-trace-otlp-proto/test/node/OTLPTraceExporter.test.ts index 64f0e40ea0..c0a604ce90 100644 --- a/experimental/packages/exporter-trace-otlp-proto/test/node/OTLPTraceExporter.test.ts +++ b/experimental/packages/exporter-trace-otlp-proto/test/node/OTLPTraceExporter.test.ts @@ -39,6 +39,7 @@ import { ServiceClientType, } from '@opentelemetry/otlp-proto-exporter-base'; import { IExportTraceServiceRequest } from '@opentelemetry/otlp-transformer'; +import { VERSION } from '../../src/version'; let fakeRequest: PassThrough; @@ -52,6 +53,16 @@ describe('OTLPTraceExporter - node with proto over http', () => { sinon.restore(); }); + describe('default behavior for headers', () => { + const collectorExporter = new OTLPTraceExporter(); + it('should include user agent in header', () => { + assert.strictEqual( + collectorExporter.headers['User-Agent'], + `OTel-OTLP-Exporter-JavaScript/${VERSION}` + ); + }); + }); + describe('when configuring via environment', () => { const envSource = process.env; it('should use url defined in env that ends with root path and append version and signal path', () => { diff --git a/experimental/packages/opentelemetry-browser-detector/package.json b/experimental/packages/opentelemetry-browser-detector/package.json index c691be743b..fa3e4746a0 100644 --- a/experimental/packages/opentelemetry-browser-detector/package.json +++ b/experimental/packages/opentelemetry-browser-detector/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/opentelemetry-browser-detector", - "version": "0.38.0", + "version": "0.39.1", "description": "OpenTelemetry Resource Detector for Browser", "main": "build/src/index.js", "module": "build/esm/index.js", @@ -70,8 +70,8 @@ "@opentelemetry/api": "^1.0.0" }, "dependencies": { - "@opentelemetry/resources": "1.12.0", - "@opentelemetry/semantic-conventions": "1.12.0" + "@opentelemetry/resources": "1.13.0", + "@opentelemetry/semantic-conventions": "1.13.0" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/browser-detector" } diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/package.json b/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/package.json index 02e86fcb46..85b92e6fcb 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/package.json +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/exporter-metrics-otlp-grpc", - "version": "0.38.0", + "version": "0.39.1", "description": "OpenTelemetry Collector Metrics Exporter allows user to send collected metrics to the OpenTelemetry Collector", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -67,12 +67,12 @@ }, "dependencies": { "@grpc/grpc-js": "^1.7.1", - "@opentelemetry/core": "1.12.0", - "@opentelemetry/exporter-metrics-otlp-http": "0.38.0", - "@opentelemetry/otlp-grpc-exporter-base": "0.38.0", - "@opentelemetry/otlp-transformer": "0.38.0", - "@opentelemetry/resources": "1.12.0", - "@opentelemetry/sdk-metrics": "1.12.0" + "@opentelemetry/core": "1.13.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.39.1", + "@opentelemetry/otlp-grpc-exporter-base": "0.39.1", + "@opentelemetry/otlp-transformer": "0.39.1", + "@opentelemetry/resources": "1.13.0", + "@opentelemetry/sdk-metrics": "1.13.0" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc", "sideEffects": false 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 20f917e543..0ada75d437 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/src/OTLPMetricExporter.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/src/OTLPMetricExporter.ts @@ -32,6 +32,11 @@ import { createExportMetricsServiceRequest, IExportMetricsServiceRequest, } from '@opentelemetry/otlp-transformer'; +import { VERSION } from './version'; + +const USER_AGENT = { + 'User-Agent': `OTel-OTLP-Exporter-JavaScript/${VERSION}`, +}; class OTLPMetricExporterProxy extends OTLPGRPCExporterNodeBase< ResourceMetrics, @@ -39,9 +44,13 @@ class OTLPMetricExporterProxy extends OTLPGRPCExporterNodeBase< > { constructor(config?: OTLPGRPCExporterConfigNode & OTLPMetricExporterOptions) { super(config); - const headers = baggageUtils.parseKeyPairsIntoRecord( - getEnv().OTEL_EXPORTER_OTLP_METRICS_HEADERS - ); + const headers = { + ...USER_AGENT, + ...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); 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 4b8d326545..9e4e27ecb0 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 @@ -43,6 +43,7 @@ import { IExportMetricsServiceRequest, IResourceMetrics, } from '@opentelemetry/otlp-transformer'; +import { VERSION } from '../src/version'; const metricsServiceProtoPath = 'opentelemetry/proto/collector/metrics/v1/metrics_service.proto'; @@ -314,6 +315,13 @@ describe('when configuring via environment', () => { ); 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}`] + ); + }); it('should override global headers config with signal headers defined via env', () => { const metadata = new grpc.Metadata(); metadata.set('foo', 'bar'); diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/package.json b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/package.json index 8230989634..7ddbd94874 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/package.json +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/exporter-metrics-otlp-http", - "version": "0.38.0", + "version": "0.39.1", "description": "OpenTelemetry Collector Metrics Exporter allows user to send collected metrics to the OpenTelemetry Collector", "main": "build/src/index.js", "module": "build/esm/index.js", @@ -93,11 +93,11 @@ "@opentelemetry/api": "^1.3.0" }, "dependencies": { - "@opentelemetry/core": "1.12.0", - "@opentelemetry/otlp-exporter-base": "0.38.0", - "@opentelemetry/otlp-transformer": "0.38.0", - "@opentelemetry/resources": "1.12.0", - "@opentelemetry/sdk-metrics": "1.12.0" + "@opentelemetry/core": "1.13.0", + "@opentelemetry/otlp-exporter-base": "0.39.1", + "@opentelemetry/otlp-transformer": "0.39.1", + "@opentelemetry/resources": "1.13.0", + "@opentelemetry/sdk-metrics": "1.13.0" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-exporter-metrics-otlp-http", "sideEffects": false 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 980931a6cb..f83e414e70 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 @@ -28,9 +28,13 @@ import { createExportMetricsServiceRequest, IExportMetricsServiceRequest, } from '@opentelemetry/otlp-transformer'; +import { VERSION } from '../../version'; const DEFAULT_COLLECTOR_RESOURCE_PATH = 'v1/metrics'; const DEFAULT_COLLECTOR_URL = `http://localhost:4318/${DEFAULT_COLLECTOR_RESOURCE_PATH}`; +const USER_AGENT = { + 'User-Agent': `OTel-OTLP-Exporter-JavaScript/${VERSION}`, +}; class OTLPExporterNodeProxy extends OTLPExporterNodeBase< ResourceMetrics, @@ -38,12 +42,13 @@ class OTLPExporterNodeProxy extends OTLPExporterNodeBase< > { constructor(config?: OTLPExporterNodeConfigBase & OTLPMetricExporterOptions) { super(config); - this.headers = Object.assign( - this.headers, - baggageUtils.parseKeyPairsIntoRecord( + this.headers = { + ...this.headers, + ...USER_AGENT, + ...baggageUtils.parseKeyPairsIntoRecord( getEnv().OTEL_EXPORTER_OTLP_METRICS_HEADERS - ) - ); + ), + }; } convert(metrics: ResourceMetrics[]): IExportMetricsServiceRequest { diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/node/CollectorMetricExporter.test.ts b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/node/CollectorMetricExporter.test.ts index d23ca628f7..042e5ebb4b 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/node/CollectorMetricExporter.test.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-http/test/node/CollectorMetricExporter.test.ts @@ -50,6 +50,7 @@ import { OTLPExporterNodeConfigBase, } from '@opentelemetry/otlp-exporter-base'; import { IExportMetricsServiceRequest } from '@opentelemetry/otlp-transformer'; +import { VERSION } from '../../src/version'; let fakeRequest: PassThrough; @@ -188,6 +189,13 @@ describe('OTLPMetricExporter - node with json over http', () => { assert.strictEqual(collectorExporter._otlpExporter.headers.foo, 'bar'); envSource.OTEL_EXPORTER_OTLP_HEADERS = ''; }); + it('should include user agent in header', () => { + const collectorExporter = new OTLPMetricExporter(); + assert.strictEqual( + collectorExporter._otlpExporter.headers['User-Agent'], + `OTel-OTLP-Exporter-JavaScript/${VERSION}` + ); + }); it('should override global headers config with signal headers defined via env', () => { envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar,bar=foo'; envSource.OTEL_EXPORTER_OTLP_METRICS_HEADERS = 'foo=boo'; diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/package.json b/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/package.json index ceb2880326..d384051e56 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/package.json +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/exporter-metrics-otlp-proto", - "version": "0.38.0", + "version": "0.39.1", "description": "OpenTelemetry Collector Metrics Exporter allows user to send collected metrics to the OpenTelemetry Collector using protobuf over HTTP", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -65,13 +65,13 @@ "@opentelemetry/api": "^1.3.0" }, "dependencies": { - "@opentelemetry/core": "1.12.0", - "@opentelemetry/exporter-metrics-otlp-http": "0.38.0", - "@opentelemetry/otlp-exporter-base": "0.38.0", - "@opentelemetry/otlp-proto-exporter-base": "0.38.0", - "@opentelemetry/otlp-transformer": "0.38.0", - "@opentelemetry/resources": "1.12.0", - "@opentelemetry/sdk-metrics": "1.12.0" + "@opentelemetry/core": "1.13.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.39.1", + "@opentelemetry/otlp-exporter-base": "0.39.1", + "@opentelemetry/otlp-proto-exporter-base": "0.39.1", + "@opentelemetry/otlp-transformer": "0.39.1", + "@opentelemetry/resources": "1.13.0", + "@opentelemetry/sdk-metrics": "1.13.0" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-exporter-metrics-otlp-proto", "sideEffects": false 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 c29ae0085c..8d1fb114b3 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/src/OTLPMetricExporter.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/src/OTLPMetricExporter.ts @@ -31,9 +31,13 @@ import { createExportMetricsServiceRequest, IExportMetricsServiceRequest, } from '@opentelemetry/otlp-transformer'; +import { VERSION } from './version'; const DEFAULT_COLLECTOR_RESOURCE_PATH = 'v1/metrics'; const DEFAULT_COLLECTOR_URL = `http://localhost:4318/${DEFAULT_COLLECTOR_RESOURCE_PATH}`; +const USER_AGENT = { + 'User-Agent': `OTel-OTLP-Exporter-JavaScript/${VERSION}`, +}; class OTLPMetricExporterNodeProxy extends OTLPProtoExporterNodeBase< ResourceMetrics, @@ -41,12 +45,13 @@ class OTLPMetricExporterNodeProxy extends OTLPProtoExporterNodeBase< > { constructor(config?: OTLPExporterNodeConfigBase & OTLPMetricExporterOptions) { super(config); - this.headers = Object.assign( - this.headers, - baggageUtils.parseKeyPairsIntoRecord( + this.headers = { + ...this.headers, + ...USER_AGENT, + ...baggageUtils.parseKeyPairsIntoRecord( getEnv().OTEL_EXPORTER_OTLP_METRICS_HEADERS - ) - ); + ), + }; } convert(metrics: ResourceMetrics[]): IExportMetricsServiceRequest { diff --git a/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/test/OTLPMetricExporter.test.ts b/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/test/OTLPMetricExporter.test.ts index 05ca183dcc..d3270b6a9f 100644 --- a/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/test/OTLPMetricExporter.test.ts +++ b/experimental/packages/opentelemetry-exporter-metrics-otlp-proto/test/OTLPMetricExporter.test.ts @@ -46,6 +46,7 @@ import { OTLPMetricExporterOptions } from '@opentelemetry/exporter-metrics-otlp- import { Stream, PassThrough } from 'stream'; import { OTLPExporterNodeConfigBase } from '@opentelemetry/otlp-exporter-base'; import { IExportMetricsServiceRequest } from '@opentelemetry/otlp-transformer'; +import { VERSION } from '../src/version'; let fakeRequest: PassThrough; @@ -60,6 +61,16 @@ describe('OTLPMetricExporter - node with proto over http', () => { sinon.restore(); }); + describe('default behavior for headers', () => { + const collectorExporter = new OTLPMetricExporter(); + it('should include user agent in header', () => { + assert.strictEqual( + collectorExporter._otlpExporter.headers['User-Agent'], + `OTel-OTLP-Exporter-JavaScript/${VERSION}` + ); + }); + }); + describe('when configuring via environment', () => { const envSource = process.env; it('should use url defined in env that ends with root path and append version and signal path', () => { diff --git a/experimental/packages/opentelemetry-exporter-prometheus/package.json b/experimental/packages/opentelemetry-exporter-prometheus/package.json index be79a2b2e5..144c4e62d8 100644 --- a/experimental/packages/opentelemetry-exporter-prometheus/package.json +++ b/experimental/packages/opentelemetry-exporter-prometheus/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/exporter-prometheus", - "version": "0.38.0", + "version": "0.39.1", "description": "OpenTelemetry Exporter Prometheus provides a metrics endpoint for Prometheus", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -44,7 +44,7 @@ }, "devDependencies": { "@opentelemetry/api": "1.4.1", - "@opentelemetry/semantic-conventions": "1.12.0", + "@opentelemetry/semantic-conventions": "1.13.0", "@types/mocha": "10.0.0", "@types/node": "18.6.5", "@types/sinon": "10.0.13", @@ -59,9 +59,9 @@ "@opentelemetry/api": "^1.3.0" }, "dependencies": { - "@opentelemetry/core": "1.12.0", - "@opentelemetry/resources": "1.12.0", - "@opentelemetry/sdk-metrics": "1.12.0" + "@opentelemetry/core": "1.13.0", + "@opentelemetry/resources": "1.13.0", + "@opentelemetry/sdk-metrics": "1.13.0" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-exporter-prometheus", "sideEffects": false diff --git a/experimental/packages/opentelemetry-instrumentation-fetch/package.json b/experimental/packages/opentelemetry-instrumentation-fetch/package.json index e192e4b602..29479c7c8f 100644 --- a/experimental/packages/opentelemetry-instrumentation-fetch/package.json +++ b/experimental/packages/opentelemetry-instrumentation-fetch/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/instrumentation-fetch", - "version": "0.38.0", + "version": "0.39.1", "description": "OpenTelemetry fetch automatic instrumentation package.", "main": "build/src/index.js", "module": "build/esm/index.js", @@ -56,9 +56,9 @@ "devDependencies": { "@babel/core": "7.16.0", "@opentelemetry/api": "1.4.1", - "@opentelemetry/context-zone": "1.12.0", - "@opentelemetry/propagator-b3": "1.12.0", - "@opentelemetry/sdk-trace-base": "1.12.0", + "@opentelemetry/context-zone": "1.13.0", + "@opentelemetry/propagator-b3": "1.13.0", + "@opentelemetry/sdk-trace-base": "1.13.0", "@types/mocha": "10.0.0", "@types/node": "18.6.5", "@types/sinon": "10.0.13", @@ -86,10 +86,10 @@ "@opentelemetry/api": "^1.0.0" }, "dependencies": { - "@opentelemetry/core": "1.12.0", - "@opentelemetry/instrumentation": "0.38.0", - "@opentelemetry/sdk-trace-web": "1.12.0", - "@opentelemetry/semantic-conventions": "1.12.0" + "@opentelemetry/core": "1.13.0", + "@opentelemetry/instrumentation": "0.39.1", + "@opentelemetry/sdk-trace-web": "1.13.0", + "@opentelemetry/semantic-conventions": "1.13.0" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-instrumentation-fetch", "sideEffects": false diff --git a/experimental/packages/opentelemetry-instrumentation-fetch/test/fetch.test.ts b/experimental/packages/opentelemetry-instrumentation-fetch/test/fetch.test.ts index 40f3381dbb..318e0f98c3 100644 --- a/experimental/packages/opentelemetry-instrumentation-fetch/test/fetch.test.ts +++ b/experimental/packages/opentelemetry-instrumentation-fetch/test/fetch.test.ts @@ -49,6 +49,10 @@ class DummySpanExporter implements tracing.SpanExporter { shutdown() { return Promise.resolve(); } + + forceFlush(): Promise { + return Promise.resolve(); + } } const getData = (url: string, method?: string) => { diff --git a/experimental/packages/opentelemetry-instrumentation-grpc/package.json b/experimental/packages/opentelemetry-instrumentation-grpc/package.json index 5166c08fae..1e63a6dd06 100644 --- a/experimental/packages/opentelemetry-instrumentation-grpc/package.json +++ b/experimental/packages/opentelemetry-instrumentation-grpc/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/instrumentation-grpc", - "version": "0.38.0", + "version": "0.39.1", "description": "OpenTelemetry grpc automatic instrumentation package.", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -18,7 +18,8 @@ "watch": "tsc --build --watch", "precompile": "lerna run version --scope $(npm pkg get name) --include-dependencies", "prewatch": "node ../../../scripts/version-update.js", - "peer-api-check": "node ../../../scripts/peer-api-check.js" + "peer-api-check": "node ../../../scripts/peer-api-check.js", + "postinstall": "node -e \"console.log(\\\"\\x1b[95m%s\\x1b[0m\\\", \\\"@opentelemetry/instrumentation-grpc - warning: The package 'grpc' (https://www.npmjs.com/package/grpc) is deprecated. It will no longer be instrumented in the next release of '@opentelemetry/instrumentation-grpc'. Please migrate to '@grpc/grpc-js' (https://www.npmjs.com/package/@grpc/grpc-js) to continue receiving telemetry.\\\");\"" }, "keywords": [ "opentelemetry", @@ -48,10 +49,10 @@ "@grpc/grpc-js": "^1.7.1", "@grpc/proto-loader": "^0.7.3", "@opentelemetry/api": "1.4.1", - "@opentelemetry/context-async-hooks": "1.12.0", - "@opentelemetry/core": "1.12.0", - "@opentelemetry/sdk-trace-base": "1.12.0", - "@opentelemetry/sdk-trace-node": "1.12.0", + "@opentelemetry/context-async-hooks": "1.13.0", + "@opentelemetry/core": "1.13.0", + "@opentelemetry/sdk-trace-base": "1.13.0", + "@opentelemetry/sdk-trace-node": "1.13.0", "@types/mocha": "10.0.0", "@types/node": "18.6.5", "@types/semver": "7.3.9", @@ -70,8 +71,8 @@ "@opentelemetry/api": "^1.3.0" }, "dependencies": { - "@opentelemetry/instrumentation": "0.38.0", - "@opentelemetry/semantic-conventions": "1.12.0" + "@opentelemetry/instrumentation": "0.39.1", + "@opentelemetry/semantic-conventions": "1.13.0" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-instrumentation-grpc", "sideEffects": false diff --git a/experimental/packages/opentelemetry-instrumentation-http/package.json b/experimental/packages/opentelemetry-instrumentation-http/package.json index a2e7604109..b46eef6a18 100644 --- a/experimental/packages/opentelemetry-instrumentation-http/package.json +++ b/experimental/packages/opentelemetry-instrumentation-http/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/instrumentation-http", - "version": "0.38.0", + "version": "0.39.1", "description": "OpenTelemetry http/https automatic instrumentation package.", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -46,17 +46,17 @@ }, "devDependencies": { "@opentelemetry/api": "1.4.1", - "@opentelemetry/context-async-hooks": "1.12.0", - "@opentelemetry/sdk-metrics": "1.12.0", - "@opentelemetry/sdk-trace-base": "1.12.0", - "@opentelemetry/sdk-trace-node": "1.12.0", + "@opentelemetry/context-async-hooks": "1.13.0", + "@opentelemetry/sdk-metrics": "1.13.0", + "@opentelemetry/sdk-trace-base": "1.13.0", + "@opentelemetry/sdk-trace-node": "1.13.0", "@types/mocha": "10.0.0", "@types/node": "18.6.5", "@types/request-promise-native": "1.0.18", "@types/semver": "7.3.9", "@types/sinon": "10.0.13", "@types/superagent": "4.1.13", - "axios": "0.24.0", + "axios": "1.4.0", "codecov": "3.8.3", "mocha": "10.0.0", "nock": "13.0.11", @@ -72,9 +72,9 @@ "@opentelemetry/api": "^1.3.0" }, "dependencies": { - "@opentelemetry/core": "1.12.0", - "@opentelemetry/instrumentation": "0.38.0", - "@opentelemetry/semantic-conventions": "1.12.0", + "@opentelemetry/core": "1.13.0", + "@opentelemetry/instrumentation": "0.39.1", + "@opentelemetry/semantic-conventions": "1.13.0", "semver": "^7.3.5" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-instrumentation-http", diff --git a/experimental/packages/opentelemetry-instrumentation-http/src/http.ts b/experimental/packages/opentelemetry-instrumentation-http/src/http.ts index 0b5c166dd5..15ca92a45e 100644 --- a/experimental/packages/opentelemetry-instrumentation-http/src/http.ts +++ b/experimental/packages/opentelemetry-instrumentation-http/src/http.ts @@ -66,7 +66,6 @@ import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; export class HttpInstrumentation extends InstrumentationBase { /** keep track on spans not ended */ private readonly _spanNotEnded: WeakSet = new WeakSet(); - private readonly _version = process.versions.node; private _headerCapture; private _httpServerDurationHistogram!: Histogram; private _httpClientDurationHistogram!: Histogram; @@ -112,11 +111,12 @@ export class HttpInstrumentation extends InstrumentationBase { } private _getHttpInstrumentation() { + const version = process.versions.node; return new InstrumentationNodeModuleDefinition( 'http', ['*'], moduleExports => { - this._diag.debug(`Applying patch for http@${this._version}`); + this._diag.debug(`Applying patch for http@${version}`); if (isWrapped(moduleExports.request)) { this._unwrap(moduleExports, 'request'); } @@ -145,7 +145,7 @@ export class HttpInstrumentation extends InstrumentationBase { }, moduleExports => { if (moduleExports === undefined) return; - this._diag.debug(`Removing patch for http@${this._version}`); + this._diag.debug(`Removing patch for http@${version}`); this._unwrap(moduleExports, 'request'); this._unwrap(moduleExports, 'get'); @@ -155,11 +155,12 @@ export class HttpInstrumentation extends InstrumentationBase { } private _getHttpsInstrumentation() { + const version = process.versions.node; return new InstrumentationNodeModuleDefinition( 'https', ['*'], moduleExports => { - this._diag.debug(`Applying patch for https@${this._version}`); + this._diag.debug(`Applying patch for https@${version}`); if (isWrapped(moduleExports.request)) { this._unwrap(moduleExports, 'request'); } @@ -188,7 +189,7 @@ export class HttpInstrumentation extends InstrumentationBase { }, moduleExports => { if (moduleExports === undefined) return; - this._diag.debug(`Removing patch for https@${this._version}`); + this._diag.debug(`Removing patch for https@${version}`); this._unwrap(moduleExports, 'request'); this._unwrap(moduleExports, 'get'); diff --git a/experimental/packages/opentelemetry-instrumentation-http/test/functionals/http-package.test.ts b/experimental/packages/opentelemetry-instrumentation-http/test/functionals/http-package.test.ts index 39b288ccdb..c07039f290 100644 --- a/experimental/packages/opentelemetry-instrumentation-http/test/functionals/http-package.test.ts +++ b/experimental/packages/opentelemetry-instrumentation-http/test/functionals/http-package.test.ts @@ -104,7 +104,7 @@ describe('Packages', () => { const result = await httpPackage.get(urlparsed.href!); if (!resHeaders) { const res = result as AxiosResponse; - resHeaders = res.headers; + resHeaders = res.headers as any; } const spans = memoryExporter.getFinishedSpans(); const span = spans[0]; diff --git a/experimental/packages/opentelemetry-instrumentation-http/test/functionals/https-package.test.ts b/experimental/packages/opentelemetry-instrumentation-http/test/functionals/https-package.test.ts index dada803e45..a7c5972a6a 100644 --- a/experimental/packages/opentelemetry-instrumentation-http/test/functionals/https-package.test.ts +++ b/experimental/packages/opentelemetry-instrumentation-http/test/functionals/https-package.test.ts @@ -110,7 +110,7 @@ describe('Packages', () => { const result = await httpPackage.get(urlparsed.href!); if (!resHeaders) { const res = result as AxiosResponse; - resHeaders = res.headers; + resHeaders = res.headers as any; } const spans = memoryExporter.getFinishedSpans(); const span = spans[0]; diff --git a/experimental/packages/opentelemetry-instrumentation-xml-http-request/package.json b/experimental/packages/opentelemetry-instrumentation-xml-http-request/package.json index 67607dacfa..4bf5707caf 100644 --- a/experimental/packages/opentelemetry-instrumentation-xml-http-request/package.json +++ b/experimental/packages/opentelemetry-instrumentation-xml-http-request/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/instrumentation-xml-http-request", - "version": "0.38.0", + "version": "0.39.1", "description": "OpenTelemetry XMLHttpRequest automatic instrumentation package.", "main": "build/src/index.js", "module": "build/esm/index.js", @@ -56,9 +56,9 @@ "devDependencies": { "@babel/core": "7.16.0", "@opentelemetry/api": "1.4.1", - "@opentelemetry/context-zone": "1.12.0", - "@opentelemetry/propagator-b3": "1.12.0", - "@opentelemetry/sdk-trace-base": "1.12.0", + "@opentelemetry/context-zone": "1.13.0", + "@opentelemetry/propagator-b3": "1.13.0", + "@opentelemetry/sdk-trace-base": "1.13.0", "@types/mocha": "10.0.0", "@types/node": "18.6.5", "@types/sinon": "10.0.13", @@ -86,10 +86,10 @@ "@opentelemetry/api": "^1.0.0" }, "dependencies": { - "@opentelemetry/core": "1.12.0", - "@opentelemetry/instrumentation": "0.38.0", - "@opentelemetry/sdk-trace-web": "1.12.0", - "@opentelemetry/semantic-conventions": "1.12.0" + "@opentelemetry/core": "1.13.0", + "@opentelemetry/instrumentation": "0.39.1", + "@opentelemetry/sdk-trace-web": "1.13.0", + "@opentelemetry/semantic-conventions": "1.13.0" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-instrumentation-xml-http-request", "sideEffects": false diff --git a/experimental/packages/opentelemetry-instrumentation-xml-http-request/test/xhr.test.ts b/experimental/packages/opentelemetry-instrumentation-xml-http-request/test/xhr.test.ts index 6176dd005c..b304bb9c03 100644 --- a/experimental/packages/opentelemetry-instrumentation-xml-http-request/test/xhr.test.ts +++ b/experimental/packages/opentelemetry-instrumentation-xml-http-request/test/xhr.test.ts @@ -46,6 +46,10 @@ class DummySpanExporter implements tracing.SpanExporter { shutdown() { return Promise.resolve(); } + + forceFlush(): Promise { + return Promise.resolve(); + } } const XHR_TIMEOUT = 2000; diff --git a/experimental/packages/opentelemetry-instrumentation/README.md b/experimental/packages/opentelemetry-instrumentation/README.md index 682fc5741a..6c27e3d36d 100644 --- a/experimental/packages/opentelemetry-instrumentation/README.md +++ b/experimental/packages/opentelemetry-instrumentation/README.md @@ -219,12 +219,18 @@ If nothing is specified the global registered provider is used. Usually this is 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 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) + +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. + +**Note**: ESM Instrumentation is not yet supported for Node 20. + ## Limitations -Instrumentations for external modules (e.g. express, mongodb,...) hooks the `require` call. Therefore following conditions need to be met that this mechanism can work: +Instrumentations for external modules (e.g. express, mongodb,...) hooks the `require` call or `import` statement. Therefore following conditions need to be met that this mechanism can work: -* `require` is used. ECMA script modules (using `import`) is not supported as of now -* Instrumentations are registered **before** the module to instrument is `require`ed +* Instrumentations are registered **before** the module to instrument is `require`ed (CJS only) * modules are not included in a bundle. Tools like `esbuild`, `webpack`, ... usually have some mechanism to exclude specific modules from bundling ## License diff --git a/experimental/packages/opentelemetry-instrumentation/hook.mjs b/experimental/packages/opentelemetry-instrumentation/hook.mjs new file mode 100644 index 0000000000..7111b37699 --- /dev/null +++ b/experimental/packages/opentelemetry-instrumentation/hook.mjs @@ -0,0 +1,28 @@ +/*! + * Copyright 2021 Datadog, Inc. + * 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 + * + * http://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. + */ + +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2.0 License. +// +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. + +import { + load, + resolve, + getFormat, + getSource, +} from 'import-in-the-middle/hook.mjs'; +export { load, resolve, getFormat, getSource }; diff --git a/experimental/packages/opentelemetry-instrumentation/package.json b/experimental/packages/opentelemetry-instrumentation/package.json index 266198f5c5..4164952821 100644 --- a/experimental/packages/opentelemetry-instrumentation/package.json +++ b/experimental/packages/opentelemetry-instrumentation/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/instrumentation", - "version": "0.38.0", + "version": "0.39.1", "description": "Base class for node which OpenTelemetry instrumentation modules extend", "author": "OpenTelemetry Authors", "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-instrumentation", @@ -32,6 +32,7 @@ "build/src/**/*.js", "build/src/**/*.js.map", "build/src/**/*.d.ts", + "hook.mjs", "doc", "LICENSE", "README.md" @@ -47,7 +48,9 @@ "tdd": "npm run tdd:node", "tdd:node": "npm run test -- --watch-extensions ts --watch", "tdd:browser": "karma start", - "test": "nyc ts-mocha -p tsconfig.json 'test/**/*.test.ts' --exclude 'test/browser/**/*.ts'", + "test:cjs": "nyc ts-mocha -p tsconfig.json 'test/**/*.test.ts' --exclude 'test/browser/**/*.ts'", + "test:esm": "nyc node --experimental-loader=./hook.mjs ../../../node_modules/mocha/bin/mocha 'test/node/*.test.mjs' test/node/*.test.mjs", + "test": "npm run test:cjs && npm run test:esm", "test:browser": "nyc karma start --single-run", "version": "node ../../../scripts/version-update.js", "watch": "tsc --build --watch tsconfig.json tsconfig.esm.json tsconfig.esnext.json", @@ -68,6 +71,8 @@ "url": "https://github.com/open-telemetry/opentelemetry-js/issues" }, "dependencies": { + "@types/shimmer": "^1.0.2", + "import-in-the-middle": "1.3.5", "require-in-the-middle": "^7.1.0", "semver": "^7.3.2", "shimmer": "^1.2.1" @@ -78,11 +83,10 @@ "devDependencies": { "@babel/core": "7.16.0", "@opentelemetry/api": "1.4.1", - "@opentelemetry/sdk-metrics": "1.12.0", + "@opentelemetry/sdk-metrics": "1.13.0", "@types/mocha": "10.0.0", "@types/node": "18.6.5", "@types/semver": "7.3.9", - "@types/shimmer": "1.0.2", "@types/sinon": "10.0.13", "@types/webpack-env": "1.16.3", "babel-loader": "8.2.3", diff --git a/experimental/packages/opentelemetry-instrumentation/src/platform/node/instrumentation.ts b/experimental/packages/opentelemetry-instrumentation/src/platform/node/instrumentation.ts index 5790c9564e..03d8f6ba37 100644 --- a/experimental/packages/opentelemetry-instrumentation/src/platform/node/instrumentation.ts +++ b/experimental/packages/opentelemetry-instrumentation/src/platform/node/instrumentation.ts @@ -16,12 +16,16 @@ import * as types from '../../types'; import * as path from 'path'; +import { types as utilTypes } from 'util'; import { satisfies } from 'semver'; +import { wrap, unwrap, massWrap, massUnwrap } from 'shimmer'; import { InstrumentationAbstract } from '../../instrumentation'; import { RequireInTheMiddleSingleton, Hooked, } from './RequireInTheMiddleSingleton'; +import type { HookFn } from 'import-in-the-middle'; +import * as ImportInTheMiddle from 'import-in-the-middle'; import { InstrumentationModuleDefinition } from './types'; import { diag } from '@opentelemetry/api'; import type { OnRequireFn } from 'require-in-the-middle'; @@ -68,6 +72,75 @@ export abstract class InstrumentationBase } } + protected override _wrap: typeof wrap = (moduleExports, name, wrapper) => { + if (!utilTypes.isProxy(moduleExports)) { + return wrap(moduleExports, name, wrapper); + } else { + const wrapped = wrap(Object.assign({}, moduleExports), name, wrapper); + + return Object.defineProperty(moduleExports, name, { + value: wrapped, + }); + } + }; + + protected override _unwrap: typeof unwrap = (moduleExports, name) => { + if (!utilTypes.isProxy(moduleExports)) { + return unwrap(moduleExports, name); + } else { + return Object.defineProperty(moduleExports, name, { + value: moduleExports[name], + }); + } + }; + + protected override _massWrap: typeof massWrap = ( + moduleExportsArray, + names, + wrapper + ) => { + if (!moduleExportsArray) { + diag.error('must provide one or more modules to patch'); + return; + } else if (!Array.isArray(moduleExportsArray)) { + moduleExportsArray = [moduleExportsArray]; + } + + if (!(names && Array.isArray(names))) { + diag.error('must provide one or more functions to wrap on modules'); + return; + } + + moduleExportsArray.forEach(moduleExports => { + names.forEach(name => { + this._wrap(moduleExports, name, wrapper); + }); + }); + }; + + protected override _massUnwrap: typeof massUnwrap = ( + moduleExportsArray, + names + ) => { + if (!moduleExportsArray) { + diag.error('must provide one or more modules to patch'); + return; + } else if (!Array.isArray(moduleExportsArray)) { + moduleExportsArray = [moduleExportsArray]; + } + + if (!(names && Array.isArray(names))) { + diag.error('must provide one or more functions to wrap on modules'); + return; + } + + moduleExportsArray.forEach(moduleExports => { + names.forEach(name => { + this._unwrap(moduleExports, name); + }); + }); + }; + private _warnOnPreloadedModules(): void { this._modules.forEach((module: InstrumentationModuleDefinition) => { const { name } = module; @@ -101,7 +174,7 @@ export abstract class InstrumentationBase module: InstrumentationModuleDefinition, exports: T, name: string, - baseDir?: string + baseDir?: string | void ): T { if (!baseDir) { if (typeof module.patch === 'function') { @@ -168,6 +241,14 @@ export abstract class InstrumentationBase this._warnOnPreloadedModules(); for (const module of this._modules) { + const hookFn: HookFn = (exports, name, baseDir) => { + return this._onRequire( + module as unknown as InstrumentationModuleDefinition, + exports, + name, + baseDir + ); + }; const onRequire: OnRequireFn = (exports, name, baseDir) => { return this._onRequire( module as unknown as InstrumentationModuleDefinition, @@ -185,6 +266,13 @@ export abstract class InstrumentationBase : this._requireInTheMiddleSingleton.register(module.name, onRequire); this._hooks.push(hook); + const esmHook = + new (ImportInTheMiddle as unknown as typeof ImportInTheMiddle.default)( + [module.name], + { internals: false }, + hookFn + ); + this._hooks.push(esmHook); } } diff --git a/experimental/packages/opentelemetry-instrumentation/test/node/EsmInstrumentation.test.mjs b/experimental/packages/opentelemetry-instrumentation/test/node/EsmInstrumentation.test.mjs new file mode 100644 index 0000000000..f09097cd79 --- /dev/null +++ b/experimental/packages/opentelemetry-instrumentation/test/node/EsmInstrumentation.test.mjs @@ -0,0 +1,138 @@ +/* + * 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 assert from 'assert'; +import { + InstrumentationBase, + InstrumentationNodeModuleDefinition, +} from '../../build/src/index.js'; +import * as exported from 'test-esm-module'; + +class TestInstrumentationWrapFn extends InstrumentationBase { + constructor(config) { + super('test-esm-instrumentation', '0.0.1', config); + } + init() { + console.log('test-esm-instrumentation initialized!'); + return new InstrumentationNodeModuleDefinition( + 'test-esm-module', + ['*'], + moduleExports => { + this._wrap(moduleExports, 'testFunction', () => { + return () => 'patched'; + }); + return moduleExports; + }, + moduleExports => { + this._unwrap(moduleExports, 'testFunction'); + return moduleExports; + } + ); + } +} + +class TestInstrumentationMasswrapFn extends InstrumentationBase { + constructor(config) { + super('test-esm-instrumentation', '0.0.1', config); + } + init() { + console.log('test-esm-instrumentation initialized!'); + return new InstrumentationNodeModuleDefinition( + 'test-esm-module', + ['*'], + moduleExports => { + this._massWrap( + [moduleExports], + ['testFunction', 'secondTestFunction'], + () => { + return () => 'patched'; + } + ); + return moduleExports; + }, + moduleExports => { + this._massUnwrap( + [moduleExports], + ['testFunction', 'secondTestFunction'] + ); + return moduleExports; + } + ); + } +} + +class TestInstrumentationSimple extends InstrumentationBase { + constructor(config) { + super('test-esm-instrumentation', '0.0.1', config); + } + init() { + console.log('test-esm-instrumentation initialized!'); + return new InstrumentationNodeModuleDefinition( + 'test-esm-module', + ['*'], + moduleExports => { + moduleExports.testConstant = 43; + return moduleExports; + } + ); + } +} +describe('when loading esm module', () => { + const instrumentationWrap = new TestInstrumentationWrapFn({ + enabled: false, + }); + + it('should patch module file directly', async () => { + const instrumentation = new TestInstrumentationSimple({ + enabled: false, + }); + instrumentation.enable(); + assert.deepEqual(exported.testConstant, 43); + }); + + it('should patch a module with the wrap function', async () => { + instrumentationWrap.enable(); + assert.deepEqual(exported.testFunction(), 'patched'); + }); + + it('should unwrap a patched function', async () => { + instrumentationWrap.enable(); + // disable to trigger unwrap + instrumentationWrap.disable(); + assert.deepEqual(exported.testFunction(), 'original'); + }); + + it('should wrap multiple functions with masswrap', () => { + const instrumentation = new TestInstrumentationMasswrapFn({ + enabled: false, + }); + + instrumentation.enable(); + assert.deepEqual(exported.testFunction(), 'patched'); + assert.deepEqual(exported.secondTestFunction(), 'patched'); + }); + + it('should unwrap multiple functions with massunwrap', () => { + const instrumentation = new TestInstrumentationMasswrapFn({ + enabled: false, + }); + + instrumentation.enable(); + instrumentation.disable(); + assert.deepEqual(exported.testFunction(), 'original'); + assert.deepEqual(exported.secondTestFunction(), 'original'); + }); +}); diff --git a/experimental/packages/opentelemetry-instrumentation/test/node/node_modules/.gitkeep b/experimental/packages/opentelemetry-instrumentation/test/node/node_modules/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/experimental/packages/opentelemetry-instrumentation/test/node/node_modules/test-esm-module/package.json b/experimental/packages/opentelemetry-instrumentation/test/node/node_modules/test-esm-module/package.json new file mode 100644 index 0000000000..3caeae666d --- /dev/null +++ b/experimental/packages/opentelemetry-instrumentation/test/node/node_modules/test-esm-module/package.json @@ -0,0 +1,6 @@ +{ + "name": "test-esm-module", + "version": "0.1.0", + "main": "./src/index.js", + "type": "module" +} diff --git a/experimental/packages/opentelemetry-instrumentation/test/node/node_modules/test-esm-module/src/index.js b/experimental/packages/opentelemetry-instrumentation/test/node/node_modules/test-esm-module/src/index.js new file mode 100644 index 0000000000..2fb6c6ed25 --- /dev/null +++ b/experimental/packages/opentelemetry-instrumentation/test/node/node_modules/test-esm-module/src/index.js @@ -0,0 +1,9 @@ +export const testFunction = () => { + return 'original'; +}; + +export const secondTestFunction = () => { + return 'original'; +}; + +export const testConstant = 42; diff --git a/experimental/packages/opentelemetry-sdk-node/package.json b/experimental/packages/opentelemetry-sdk-node/package.json index fd4f4cc255..b6dd740713 100644 --- a/experimental/packages/opentelemetry-sdk-node/package.json +++ b/experimental/packages/opentelemetry-sdk-node/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/sdk-node", - "version": "0.38.0", + "version": "0.39.1", "description": "OpenTelemetry SDK for Node.js", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -44,25 +44,25 @@ "access": "public" }, "dependencies": { - "@opentelemetry/core": "1.12.0", - "@opentelemetry/exporter-jaeger": "1.12.0", - "@opentelemetry/exporter-trace-otlp-grpc": "0.38.0", - "@opentelemetry/exporter-trace-otlp-http": "0.38.0", - "@opentelemetry/exporter-trace-otlp-proto": "0.38.0", - "@opentelemetry/exporter-zipkin": "1.12.0", - "@opentelemetry/instrumentation": "0.38.0", - "@opentelemetry/resources": "1.12.0", - "@opentelemetry/sdk-metrics": "1.12.0", - "@opentelemetry/sdk-trace-base": "1.12.0", - "@opentelemetry/sdk-trace-node": "1.12.0", - "@opentelemetry/semantic-conventions": "1.12.0" + "@opentelemetry/core": "1.13.0", + "@opentelemetry/exporter-jaeger": "1.13.0", + "@opentelemetry/exporter-trace-otlp-grpc": "0.39.1", + "@opentelemetry/exporter-trace-otlp-http": "0.39.1", + "@opentelemetry/exporter-trace-otlp-proto": "0.39.1", + "@opentelemetry/exporter-zipkin": "1.13.0", + "@opentelemetry/instrumentation": "0.39.1", + "@opentelemetry/resources": "1.13.0", + "@opentelemetry/sdk-metrics": "1.13.0", + "@opentelemetry/sdk-trace-base": "1.13.0", + "@opentelemetry/sdk-trace-node": "1.13.0", + "@opentelemetry/semantic-conventions": "1.13.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.5.0" }, "devDependencies": { "@opentelemetry/api": "1.4.1", - "@opentelemetry/context-async-hooks": "1.12.0", + "@opentelemetry/context-async-hooks": "1.13.0", "@types/mocha": "10.0.0", "@types/node": "18.6.5", "@types/semver": "7.3.9", diff --git a/experimental/packages/opentelemetry-sdk-node/src/types.ts b/experimental/packages/opentelemetry-sdk-node/src/types.ts index 82074719df..03efc31142 100644 --- a/experimental/packages/opentelemetry-sdk-node/src/types.ts +++ b/experimental/packages/opentelemetry-sdk-node/src/types.ts @@ -17,7 +17,7 @@ import type { ContextManager } from '@opentelemetry/api'; import { TextMapPropagator } from '@opentelemetry/api'; import { InstrumentationOption } from '@opentelemetry/instrumentation'; -import { Detector, DetectorSync, Resource } from '@opentelemetry/resources'; +import { Detector, DetectorSync, IResource } from '@opentelemetry/resources'; import { MetricReader, View } from '@opentelemetry/sdk-metrics'; import { Sampler, @@ -34,7 +34,7 @@ export interface NodeSDKConfiguration { metricReader: MetricReader; views: View[]; instrumentations: InstrumentationOption[]; - resource: Resource; + resource: IResource; resourceDetectors: Array; sampler: Sampler; serviceName?: string; diff --git a/experimental/packages/otlp-exporter-base/package.json b/experimental/packages/otlp-exporter-base/package.json index 4a75e6ad89..2d06e7b181 100644 --- a/experimental/packages/otlp-exporter-base/package.json +++ b/experimental/packages/otlp-exporter-base/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/otlp-exporter-base", - "version": "0.38.0", + "version": "0.39.1", "description": "OpenTelemetry OTLP Exporter base (for internal use only)", "main": "build/src/index.js", "module": "build/esm/index.js", @@ -61,7 +61,7 @@ "access": "public" }, "dependencies": { - "@opentelemetry/core": "1.12.0" + "@opentelemetry/core": "1.13.0" }, "devDependencies": { "@opentelemetry/api": "1.4.1", diff --git a/experimental/packages/otlp-exporter-base/src/OTLPExporterBase.ts b/experimental/packages/otlp-exporter-base/src/OTLPExporterBase.ts index c78adb7e27..c960305783 100644 --- a/experimental/packages/otlp-exporter-base/src/OTLPExporterBase.ts +++ b/experimental/packages/otlp-exporter-base/src/OTLPExporterBase.ts @@ -117,15 +117,22 @@ export abstract class OTLPExporterBase< return this._shutdownOnce.call(); } + /** + * Exports any pending spans in the exporter + */ + forceFlush(): Promise { + return Promise.all(this._sendingPromises).then(() => { + /** ignore resolved values */ + }); + } + /** * Called by _shutdownOnce with BindOnceFuture */ private _shutdown(): Promise { diag.debug('shutdown started'); this.onShutdown(); - return Promise.all(this._sendingPromises).then(() => { - /** ignore resolved values */ - }); + return this.forceFlush(); } abstract onShutdown(): void; diff --git a/experimental/packages/otlp-exporter-base/test/node/util.test.ts b/experimental/packages/otlp-exporter-base/test/node/util.test.ts index 86c8df40ab..b279e57b9a 100644 --- a/experimental/packages/otlp-exporter-base/test/node/util.test.ts +++ b/experimental/packages/otlp-exporter-base/test/node/util.test.ts @@ -60,6 +60,13 @@ class Exporter extends OTLPExporterNodeBase { } } +describe('force flush', () => { + it('forceFlush should flush spans and return', async () => { + const exporter = new Exporter({}); + await exporter.forceFlush(); + }); +}); + describe('configureExporterTimeout', () => { const envSource = process.env; it('should use timeoutMillis parameter as export timeout value', () => { diff --git a/experimental/packages/otlp-grpc-exporter-base/package.json b/experimental/packages/otlp-grpc-exporter-base/package.json index 132c96b4c5..97f8940ef6 100644 --- a/experimental/packages/otlp-grpc-exporter-base/package.json +++ b/experimental/packages/otlp-grpc-exporter-base/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/otlp-grpc-exporter-base", - "version": "0.38.0", + "version": "0.39.1", "description": "OpenTelemetry OTLP-gRPC Exporter base (for internal use only)", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -50,9 +50,9 @@ "devDependencies": { "@babel/core": "7.16.0", "@opentelemetry/api": "1.4.1", - "@opentelemetry/otlp-transformer": "0.38.0", - "@opentelemetry/resources": "1.12.0", - "@opentelemetry/sdk-trace-base": "1.12.0", + "@opentelemetry/otlp-transformer": "0.39.1", + "@opentelemetry/resources": "1.13.0", + "@opentelemetry/sdk-trace-base": "1.13.0", "@types/mocha": "10.0.0", "@types/node": "18.6.5", "@types/sinon": "10.0.13", @@ -60,19 +60,19 @@ "cpx": "1.5.0", "mocha": "10.0.0", "nyc": "15.1.0", + "protobufjs-cli": "1.0.2", "sinon": "15.0.0", "ts-loader": "8.4.0", "ts-mocha": "10.0.0", - "typescript": "4.4.4", - "protobufjs-cli": "1.0.2" + "typescript": "4.4.4" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" }, "dependencies": { "@grpc/grpc-js": "^1.7.1", - "@opentelemetry/core": "1.12.0", - "@opentelemetry/otlp-exporter-base": "0.38.0", + "@opentelemetry/core": "1.13.0", + "@opentelemetry/otlp-exporter-base": "0.39.1", "protobufjs": "^7.2.2" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/otlp-grpc-exporter-base", diff --git a/experimental/packages/otlp-proto-exporter-base/package.json b/experimental/packages/otlp-proto-exporter-base/package.json index 56fc97450f..29018d61b4 100644 --- a/experimental/packages/otlp-proto-exporter-base/package.json +++ b/experimental/packages/otlp-proto-exporter-base/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/otlp-proto-exporter-base", - "version": "0.38.0", + "version": "0.39.1", "description": "OpenTelemetry OTLP-HTTP-protobuf Exporter base (for internal use only)", "main": "build/src/index.js", "module": "build/esm/index.js", @@ -77,8 +77,8 @@ "@opentelemetry/api": "^1.0.0" }, "dependencies": { - "@opentelemetry/core": "1.12.0", - "@opentelemetry/otlp-exporter-base": "0.38.0", + "@opentelemetry/core": "1.13.0", + "@opentelemetry/otlp-exporter-base": "0.39.1", "protobufjs": "^7.1.2" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/otlp-proto-exporter-base", diff --git a/experimental/packages/otlp-proto-exporter-base/src/platform/browser/OTLPProtoExporterBrowserBase.ts b/experimental/packages/otlp-proto-exporter-base/src/platform/browser/OTLPProtoExporterBrowserBase.ts index 4e9f95d5c0..34c80d6e82 100644 --- a/experimental/packages/otlp-proto-exporter-base/src/platform/browser/OTLPProtoExporterBrowserBase.ts +++ b/experimental/packages/otlp-proto-exporter-base/src/platform/browser/OTLPProtoExporterBrowserBase.ts @@ -22,13 +22,8 @@ import { OTLPExporterConfigBase, sendWithXhr, } from '@opentelemetry/otlp-exporter-base'; -import * as root from '../../generated/root'; -interface ExportRequestType unknown }> { - create(properties?: T): R; - encode(message: T, writer?: protobuf.Writer): protobuf.Writer; - decode(reader: protobuf.Reader | Uint8Array, length?: number): R; -} +import { getExportRequestProto } from '../util'; /** * Collector Exporter abstract base class @@ -41,18 +36,6 @@ export abstract class OTLPProtoExporterBrowserBase< super(config); } - private _getExportRequestProto( - clientType: ServiceClientType - ): ExportRequestType { - if (clientType === ServiceClientType.SPANS) { - // eslint-disable-next-line - return root.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest as unknown as ExportRequestType; - } else { - // eslint-disable-next-line - return root.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest as unknown as ExportRequestType; - } - } - override send( objects: ExportItem[], onSuccess: () => void, @@ -64,7 +47,7 @@ export abstract class OTLPProtoExporterBrowserBase< } const serviceRequest = this.convert(objects); - const exportRequestType = this._getExportRequestProto( + const exportRequestType = getExportRequestProto( this.getServiceClientType() ); const message = exportRequestType.create(serviceRequest); diff --git a/experimental/packages/otlp-proto-exporter-base/src/platform/index.ts b/experimental/packages/otlp-proto-exporter-base/src/platform/index.ts index 22efeb0309..fc344756ea 100644 --- a/experimental/packages/otlp-proto-exporter-base/src/platform/index.ts +++ b/experimental/packages/otlp-proto-exporter-base/src/platform/index.ts @@ -14,11 +14,7 @@ * limitations under the License. */ -export { - OTLPProtoExporterNodeBase, - ExportRequestType, - getExportRequestProto, - send, -} from './node'; +export { OTLPProtoExporterNodeBase, send } from './node'; export { OTLPProtoExporterBrowserBase } from './browser'; export { ServiceClientType } from './types'; +export { ExportRequestType, getExportRequestProto } from './util'; diff --git a/experimental/packages/otlp-proto-exporter-base/src/platform/node/index.ts b/experimental/packages/otlp-proto-exporter-base/src/platform/node/index.ts index c9c84bdac3..08016fec93 100644 --- a/experimental/packages/otlp-proto-exporter-base/src/platform/node/index.ts +++ b/experimental/packages/otlp-proto-exporter-base/src/platform/node/index.ts @@ -15,4 +15,4 @@ */ export { OTLPProtoExporterNodeBase } from './OTLPProtoExporterNodeBase'; -export { ExportRequestType, getExportRequestProto, send } from './util'; +export { send } from './util'; diff --git a/experimental/packages/otlp-proto-exporter-base/src/platform/node/util.ts b/experimental/packages/otlp-proto-exporter-base/src/platform/node/util.ts index 5cdbfa396d..36fe866d67 100644 --- a/experimental/packages/otlp-proto-exporter-base/src/platform/node/util.ts +++ b/experimental/packages/otlp-proto-exporter-base/src/platform/node/util.ts @@ -14,33 +14,14 @@ * limitations under the License. */ -import { ServiceClientType } from '../types'; import { OTLPProtoExporterNodeBase } from './OTLPProtoExporterNodeBase'; import { CompressionAlgorithm, OTLPExporterError, sendWithHttp, } from '@opentelemetry/otlp-exporter-base'; -import type * as protobuf from 'protobufjs'; -import * as root from '../../generated/root'; -export interface ExportRequestType unknown }> { - create(properties?: T): R; - encode(message: T, writer?: protobuf.Writer): protobuf.Writer; - decode(reader: protobuf.Reader | Uint8Array, length?: number): R; -} - -export function getExportRequestProto( - clientType: ServiceClientType -): ExportRequestType { - if (clientType === ServiceClientType.SPANS) { - return root.opentelemetry.proto.collector.trace.v1 - .ExportTraceServiceRequest as unknown as ExportRequestType; - } else { - return root.opentelemetry.proto.collector.metrics.v1 - .ExportMetricsServiceRequest as unknown as ExportRequestType; - } -} +import { getExportRequestProto } from '../util'; export function send( collector: OTLPProtoExporterNodeBase, diff --git a/experimental/packages/otlp-proto-exporter-base/src/platform/types.ts b/experimental/packages/otlp-proto-exporter-base/src/platform/types.ts index 389cfb1f52..b15ad36abc 100644 --- a/experimental/packages/otlp-proto-exporter-base/src/platform/types.ts +++ b/experimental/packages/otlp-proto-exporter-base/src/platform/types.ts @@ -17,4 +17,5 @@ export enum ServiceClientType { SPANS, METRICS, + LOGS, } diff --git a/experimental/packages/otlp-proto-exporter-base/src/platform/util.ts b/experimental/packages/otlp-proto-exporter-base/src/platform/util.ts new file mode 100644 index 0000000000..c6e1272816 --- /dev/null +++ b/experimental/packages/otlp-proto-exporter-base/src/platform/util.ts @@ -0,0 +1,39 @@ +/* + * 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 { ServiceClientType } from './types'; + +export interface ExportRequestType unknown }> { + create(properties?: T): R; + encode(message: T, writer?: protobuf.Writer): protobuf.Writer; + decode(reader: protobuf.Reader | Uint8Array, length?: number): R; +} + +export function getExportRequestProto( + clientType: ServiceClientType +): ExportRequestType { + if (clientType === ServiceClientType.SPANS) { + return root.opentelemetry.proto.collector.trace.v1 + .ExportTraceServiceRequest as unknown as ExportRequestType; + } else if (clientType === ServiceClientType.LOGS) { + return root.opentelemetry.proto.collector.logs.v1 + .ExportLogsServiceRequest as unknown as ExportRequestType; + } else { + return root.opentelemetry.proto.collector.metrics.v1 + .ExportMetricsServiceRequest as unknown as ExportRequestType; + } +} diff --git a/experimental/packages/otlp-transformer/package.json b/experimental/packages/otlp-transformer/package.json index be608f5ed5..cc1e43642e 100644 --- a/experimental/packages/otlp-transformer/package.json +++ b/experimental/packages/otlp-transformer/package.json @@ -4,7 +4,7 @@ "publishConfig": { "access": "public" }, - "version": "0.38.0", + "version": "0.39.1", "description": "Transform OpenTelemetry SDK data into OTLP", "module": "build/esm/index.js", "esnext": "build/esnext/index.js", @@ -52,12 +52,12 @@ "README.md" ], "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.5.0", - "@opentelemetry/api-logs": "0.38.0" + "@opentelemetry/api": ">=1.3.0 <1.5.0" }, "devDependencies": { "@opentelemetry/api": "1.4.1", - "@opentelemetry/api-logs": "0.38.0", + "@opentelemetry/api-logs": "0.39.1", + "@opentelemetry/sdk-logs": "0.39.1", "@types/mocha": "10.0.0", "@types/webpack-env": "1.16.3", "codecov": "3.8.3", @@ -76,12 +76,13 @@ "webpack": "4.46.0" }, "dependencies": { - "@opentelemetry/core": "1.12.0", - "@opentelemetry/resources": "1.12.0", - "@opentelemetry/sdk-logs": "0.38.0", - "@opentelemetry/sdk-metrics": "1.12.0", - "@opentelemetry/sdk-trace-base": "1.12.0" + "@opentelemetry/api-logs": "0.39.1", + "@opentelemetry/core": "1.13.0", + "@opentelemetry/resources": "1.13.0", + "@opentelemetry/sdk-logs": "0.39.1", + "@opentelemetry/sdk-metrics": "1.13.0", + "@opentelemetry/sdk-trace-base": "1.13.0" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/otlp-transformer", "sideEffects": false -} \ No newline at end of file +} diff --git a/experimental/packages/otlp-transformer/src/common/internal.ts b/experimental/packages/otlp-transformer/src/common/internal.ts index 5612b87ce9..0fe649f525 100644 --- a/experimental/packages/otlp-transformer/src/common/internal.ts +++ b/experimental/packages/otlp-transformer/src/common/internal.ts @@ -13,8 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import type { Attributes } from '@opentelemetry/api'; import type { IAnyValue, IKeyValue } from './types'; +import { Attributes } from '@opentelemetry/api'; export function toAttributes(attributes: Attributes): IKeyValue[] { return Object.keys(attributes).map(key => toKeyValue(key, attributes[key])); diff --git a/experimental/packages/otlp-transformer/src/logs/index.ts b/experimental/packages/otlp-transformer/src/logs/index.ts index 4fd114fa69..c499476498 100644 --- a/experimental/packages/otlp-transformer/src/logs/index.ts +++ b/experimental/packages/otlp-transformer/src/logs/index.ts @@ -22,9 +22,11 @@ import { IResourceLogs, } from './types'; import { IResource } from '@opentelemetry/resources'; -import { toAnyValue, toAttributes } from '../common/internal'; +import { toAnyValue, toAttributes, toKeyValue } from '../common/internal'; import { hexToBase64, hrTimeToNanoseconds } from '@opentelemetry/core'; import { SeverityNumber } from '@opentelemetry/api-logs'; +import { IKeyValue } from '../common/types'; +import { LogAttributes } from '@opentelemetry/api-logs'; export function createExportLogsServiceRequest( logRecords: ReadableLogRecord[], @@ -93,11 +95,11 @@ function logRecordsToResourceLogs( function toLogRecord(log: ReadableLogRecord, useHex?: boolean): ILogRecord { return { timeUnixNano: hrTimeToNanoseconds(log.hrTime), - observedTimeUnixNano: hrTimeToNanoseconds(log.hrTime), + observedTimeUnixNano: hrTimeToNanoseconds(log.hrTimeObserved), severityNumber: toSeverityNumber(log.severityNumber), severityText: log.severityText, body: toAnyValue(log.body), - attributes: toAttributes(log.attributes), + attributes: toLogAttributes(log.attributes), droppedAttributesCount: 0, flags: log.spanContext?.traceFlags, traceId: useHex @@ -119,3 +121,7 @@ function optionalHexToBase64(str: string | undefined): string | undefined { if (str === undefined) return undefined; return hexToBase64(str); } + +export function toLogAttributes(attributes: LogAttributes): IKeyValue[] { + return Object.keys(attributes).map(key => toKeyValue(key, attributes[key])); +} diff --git a/experimental/packages/otlp-transformer/test/logs.test.ts b/experimental/packages/otlp-transformer/test/logs.test.ts index c184134ce4..ef0a4cf9d7 100644 --- a/experimental/packages/otlp-transformer/test/logs.test.ts +++ b/experimental/packages/otlp-transformer/test/logs.test.ts @@ -52,7 +52,7 @@ function createExpectedLogJson(useHex: boolean): IExportLogsServiceRequest { // eslint-disable-next-line @typescript-eslint/no-loss-of-precision timeUnixNano: 1680253513123241635, // eslint-disable-next-line @typescript-eslint/no-loss-of-precision - observedTimeUnixNano: 1680253513123241635, + observedTimeUnixNano: 1683526948965142784, severityNumber: ESeverityNumber.SEVERITY_NUMBER_ERROR, severityText: 'error', body: { stringValue: 'some_log_body' }, @@ -105,6 +105,7 @@ describe('Logs', () => { }; const log_fragment_1 = { hrTime: [1680253513, 123241635] as HrTime, + hrTimeObserved: [1683526948, 965142784] as HrTime, attributes: { 'some-attribute': 'some attribute value', }, @@ -119,6 +120,7 @@ describe('Logs', () => { }; const log_fragment_2 = { hrTime: [1680253797, 687038506] as HrTime, + hrTimeObserved: [1680253797, 687038506] as HrTime, attributes: { 'another-attribute': 'another attribute value', }, diff --git a/experimental/packages/sdk-logs/package.json b/experimental/packages/sdk-logs/package.json index f9e5fb1962..f4222cb545 100644 --- a/experimental/packages/sdk-logs/package.json +++ b/experimental/packages/sdk-logs/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/sdk-logs", - "version": "0.38.0", + "version": "0.39.1", "publishConfig": { "access": "public" }, @@ -69,14 +69,14 @@ "sideEffects": false, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.5.0", - "@opentelemetry/api-logs": ">=0.38.0" + "@opentelemetry/api-logs": ">=0.39.1" }, "devDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.5.0", + "@opentelemetry/api-logs": "0.39.1", "@types/mocha": "10.0.0", "@types/node": "18.6.5", "@types/sinon": "10.0.13", - "@opentelemetry/api": ">=1.4.0 <1.5.0", - "@opentelemetry/api-logs": ">=0.38.0", "codecov": "3.8.3", "karma": "6.3.16", "karma-chrome-launcher": "3.1.0", @@ -91,7 +91,7 @@ "typescript": "4.4.4" }, "dependencies": { - "@opentelemetry/core": "^1.11.0", - "@opentelemetry/resources": "^1.11.0" + "@opentelemetry/core": "1.13.0", + "@opentelemetry/resources": "1.13.0" } } diff --git a/experimental/packages/sdk-logs/src/LogRecord.ts b/experimental/packages/sdk-logs/src/LogRecord.ts index a4601af6fc..d184004e6b 100644 --- a/experimental/packages/sdk-logs/src/LogRecord.ts +++ b/experimental/packages/sdk-logs/src/LogRecord.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Attributes, AttributeValue, diag } from '@opentelemetry/api'; +import { AttributeValue, diag } from '@opentelemetry/api'; import type * as logsAPI from '@opentelemetry/api-logs'; import * as api from '@opentelemetry/api'; import { @@ -27,13 +27,15 @@ import type { IResource } from '@opentelemetry/resources'; import type { ReadableLogRecord } from './export/ReadableLogRecord'; import type { LogRecordLimits } from './types'; import { Logger } from './Logger'; +import { LogAttributes } from '@opentelemetry/api-logs'; export class LogRecord implements ReadableLogRecord { readonly hrTime: api.HrTime; + readonly hrTimeObserved: api.HrTime; readonly spanContext?: api.SpanContext; readonly resource: IResource; readonly instrumentationScope: InstrumentationScope; - readonly attributes: Attributes = {}; + readonly attributes: logsAPI.LogAttributes = {}; private _severityText?: string; private _severityNumber?: logsAPI.SeverityNumber; private _body?: string; @@ -73,7 +75,8 @@ export class LogRecord implements ReadableLogRecord { constructor(logger: Logger, logRecord: logsAPI.LogRecord) { const { - timestamp = Date.now(), + timestamp, + observedTimestamp, severityNumber, severityText, body, @@ -81,7 +84,10 @@ export class LogRecord implements ReadableLogRecord { context, } = logRecord; - this.hrTime = timeInputToHrTime(timestamp); + const now = Date.now(); + this.hrTime = timeInputToHrTime(timestamp ?? now); + this.hrTimeObserved = timeInputToHrTime(observedTimestamp ?? now); + if (context) { const spanContext = api.trace.getSpanContext(context); if (spanContext && api.isSpanContextValid(spanContext)) { @@ -97,13 +103,20 @@ export class LogRecord implements ReadableLogRecord { this.setAttributes(attributes); } - public setAttribute(key: string, value?: AttributeValue) { + public setAttribute(key: string, value?: LogAttributes | AttributeValue) { if (this._isLogRecordReadonly()) { return this; } if (value === null) { return this; } + if ( + typeof value === 'object' && + !Array.isArray(value) && + Object.keys(value).length > 0 + ) { + this.attributes[key] = value; + } if (key.length === 0) { api.diag.warn(`Invalid attribute key: ${key}`); return this; @@ -123,7 +136,7 @@ export class LogRecord implements ReadableLogRecord { return this; } - public setAttributes(attributes: Attributes) { + public setAttributes(attributes: LogAttributes) { for (const [k, v] of Object.entries(attributes)) { this.setAttribute(k, v); } diff --git a/experimental/packages/sdk-logs/src/Logger.ts b/experimental/packages/sdk-logs/src/Logger.ts index 0e6cfbe2d9..5ea4e8f28f 100644 --- a/experimental/packages/sdk-logs/src/Logger.ts +++ b/experimental/packages/sdk-logs/src/Logger.ts @@ -39,9 +39,7 @@ export class Logger implements logsAPI.Logger { } public emit(logRecord: logsAPI.LogRecord): void { - const currentContext = this._loggerConfig.includeTraceContext - ? context.active() - : undefined; + const currentContext = logRecord.context || context.active(); /** * If a Logger was obtained with include_trace_context=true, * the LogRecords it emits MUST automatically include the Trace Context from the active Context, diff --git a/experimental/packages/sdk-logs/src/LoggerProvider.ts b/experimental/packages/sdk-logs/src/LoggerProvider.ts index a51438408a..81515dab86 100644 --- a/experimental/packages/sdk-logs/src/LoggerProvider.ts +++ b/experimental/packages/sdk-logs/src/LoggerProvider.ts @@ -84,7 +84,6 @@ export class LoggerProvider implements logsAPI.LoggerProvider { { name: loggerName, version, schemaUrl: options?.schemaUrl }, { logRecordLimits: this._config.logRecordLimits, - includeTraceContext: options?.includeTraceContext, }, this ) diff --git a/experimental/packages/sdk-logs/src/export/ReadableLogRecord.ts b/experimental/packages/sdk-logs/src/export/ReadableLogRecord.ts index e02d53dae9..a7eff21e2d 100644 --- a/experimental/packages/sdk-logs/src/export/ReadableLogRecord.ts +++ b/experimental/packages/sdk-logs/src/export/ReadableLogRecord.ts @@ -15,17 +15,18 @@ */ import type { IResource } from '@opentelemetry/resources'; -import type { Attributes, HrTime, SpanContext } from '@opentelemetry/api'; +import type { HrTime, SpanContext } from '@opentelemetry/api'; import type { InstrumentationScope } from '@opentelemetry/core'; -import type { SeverityNumber } from '@opentelemetry/api-logs'; +import type { LogAttributes, SeverityNumber } from '@opentelemetry/api-logs'; export interface ReadableLogRecord { readonly hrTime: HrTime; + readonly hrTimeObserved: HrTime; readonly spanContext?: SpanContext; readonly severityText?: string; readonly severityNumber?: SeverityNumber; readonly body?: string; readonly resource: IResource; readonly instrumentationScope: InstrumentationScope; - readonly attributes: Attributes; + readonly attributes: LogAttributes; } diff --git a/experimental/packages/sdk-logs/src/index.ts b/experimental/packages/sdk-logs/src/index.ts index 60124da1f0..e718ae069e 100644 --- a/experimental/packages/sdk-logs/src/index.ts +++ b/experimental/packages/sdk-logs/src/index.ts @@ -16,6 +16,7 @@ export { LoggerConfig, + LoggerProviderConfig, LogRecordLimits, BufferConfig, BatchLogRecordProcessorBrowserConfig, diff --git a/experimental/packages/sdk-logs/src/types.ts b/experimental/packages/sdk-logs/src/types.ts index 5bdad2a1e0..026843dc15 100644 --- a/experimental/packages/sdk-logs/src/types.ts +++ b/experimental/packages/sdk-logs/src/types.ts @@ -33,9 +33,6 @@ export interface LoggerProviderConfig { export interface LoggerConfig { /** Log Record Limits*/ logRecordLimits?: LogRecordLimits; - - /** include Trace Context */ - includeTraceContext?: boolean; } export interface LogRecordLimits { diff --git a/experimental/packages/sdk-logs/test/common/Logger.test.ts b/experimental/packages/sdk-logs/test/common/Logger.test.ts index 5df756c901..a5f690a4b7 100644 --- a/experimental/packages/sdk-logs/test/common/Logger.test.ts +++ b/experimental/packages/sdk-logs/test/common/Logger.test.ts @@ -18,8 +18,8 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; import { LogRecord, Logger, LoggerConfig, LoggerProvider } from '../../src'; -import { loadDefaultConfig } from '../../src/config'; -import { context } from '@opentelemetry/api'; +import { ROOT_CONTEXT, TraceFlags, context, trace } from '@opentelemetry/api'; +import { LogRecord as ApiLogRecord } from '@opentelemetry/api-logs'; const setup = (loggerConfig: LoggerConfig = {}) => { const logger = new Logger( @@ -40,14 +40,6 @@ describe('Logger', () => { const { logger } = setup(); assert.ok(logger instanceof Logger); }); - - it('should a default value with config.includeTraceContext', () => { - const { logger } = setup(); - assert.ok( - logger['_loggerConfig'].includeTraceContext === - loadDefaultConfig().includeTraceContext - ); - }); }); describe('emit', () => { @@ -69,8 +61,8 @@ describe('Logger', () => { assert.ok(makeOnlySpy.called); }); - it('should emit with current Context when includeTraceContext is true', () => { - const { logger } = setup({ includeTraceContext: true }); + it('should emit with current Context', () => { + const { logger } = setup({}); const callSpy = sinon.spy(logger.getActiveLogRecordProcessor(), 'onEmit'); logger.emit({ body: 'test log body', @@ -78,13 +70,21 @@ describe('Logger', () => { assert.ok(callSpy.calledWith(sinon.match.any, context.active())); }); - it('should emit with empty Context when includeTraceContext is false', () => { - const { logger } = setup({ includeTraceContext: false }); + it('should emit with Context specified in LogRecord', () => { + const { logger } = setup({}); + const spanContext = { + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + spanId: '6e0c63257de34c92', + traceFlags: TraceFlags.SAMPLED, + }; + const activeContext = trace.setSpanContext(ROOT_CONTEXT, spanContext); + const logRecordData: ApiLogRecord = { + context: activeContext, + }; + const callSpy = sinon.spy(logger.getActiveLogRecordProcessor(), 'onEmit'); - logger.emit({ - body: 'test log body', - }); - assert.ok(callSpy.calledWith(sinon.match.any, undefined)); + logger.emit(logRecordData); + assert.ok(callSpy.calledWith(sinon.match.any, activeContext)); }); }); }); diff --git a/experimental/packages/sdk-logs/test/common/utils.ts b/experimental/packages/sdk-logs/test/common/utils.ts index fd801fc30d..80dc84e0c7 100644 --- a/experimental/packages/sdk-logs/test/common/utils.ts +++ b/experimental/packages/sdk-logs/test/common/utils.ts @@ -21,11 +21,12 @@ export const validAttributes = { 'array': ['str1', 'str2'], 'array': [1, 2], 'array': [true, false], + object: { bar: 'foo' }, }; export const invalidAttributes = { - // invalid attribute type object - object: { foo: 'bar' }, + // invalid attribute empty object + object: {}, // invalid attribute inhomogeneous array 'non-homogeneous-array': [0, ''], // This empty length attribute should not be set diff --git a/experimental/packages/shim-opencensus/README.md b/experimental/packages/shim-opencensus/README.md index 63bea4df42..e614fccf38 100644 --- a/experimental/packages/shim-opencensus/README.md +++ b/experimental/packages/shim-opencensus/README.md @@ -3,10 +3,6 @@ [![NPM Published Version][npm-img]][npm-url] [![Apache License][license-image]][license-image] -> **Note** -> This package is in active development and has not yet been released. You cannot install this -> package from NPM yet. - OpenCensus shim allows existing OpenCensus instrumentation to report to OpenTelemetry. This allows you to incrementally migrate your existing OpenCensus instrumentation to OpenTelemetry. More details are available in the [OpenCensus Compatibility Specification](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/compatibility/opencensus.md). diff --git a/experimental/packages/shim-opencensus/package.json b/experimental/packages/shim-opencensus/package.json index c6bcfc0e85..202f8e2d67 100644 --- a/experimental/packages/shim-opencensus/package.json +++ b/experimental/packages/shim-opencensus/package.json @@ -1,8 +1,7 @@ { "name": "@opentelemetry/shim-opencensus", - "version": "0.38.0", + "version": "0.39.1", "description": "OpenCensus to OpenTelemetry shim", - "private": true, "main": "build/src/index.js", "types": "build/src/index.d.ts", "repository": "open-telemetry/opentelemetry-js", @@ -45,8 +44,8 @@ "access": "public" }, "devDependencies": { - "@opentelemetry/core": "1.12.0", - "@opentelemetry/context-async-hooks": "1.12.0", + "@opentelemetry/context-async-hooks": "1.13.0", + "@opentelemetry/sdk-trace-base": "1.13.0", "@opencensus/core": "0.1.0", "@opentelemetry/api": "1.4.1", "@types/mocha": "10.0.0", @@ -64,10 +63,9 @@ "@opentelemetry/api": "^1.0.0" }, "dependencies": { - "@opentelemetry/core": "^1.0.0", - "@opentelemetry/context-async-hooks": "^1.0.0", - "semver": "^7.3.5", - "require-in-the-middle": "^6.0.0" + "@opentelemetry/core": "1.13.0", + "require-in-the-middle": "^7.0.0", + "semver": "^7.3.5" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/shim-opencensus", "sideEffects": false diff --git a/experimental/packages/shim-opencensus/src/ShimSpan.ts b/experimental/packages/shim-opencensus/src/ShimSpan.ts new file mode 100644 index 0000000000..498c73e5e7 --- /dev/null +++ b/experimental/packages/shim-opencensus/src/ShimSpan.ts @@ -0,0 +1,188 @@ +/* + * 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 oc from '@opencensus/core'; +import { ShimTracer } from './ShimTracer'; +import { AttributeValue, Span, SpanStatusCode, diag } from '@opentelemetry/api'; +import { mapMessageEvent, reverseMapSpanContext } from './transform'; + +// Copied from +// https://github.com/census-instrumentation/opencensus-node/blob/v0.1.0/packages/opencensus-core/src/trace/model/span.ts#L61 +export const DEFAULT_SPAN_NAME = 'span'; + +const STATUS_OK = { + code: oc.CanonicalCode.OK, +}; + +interface Options { + shimTracer: ShimTracer; + otelSpan: Span; + isRootSpan?: boolean | undefined; + kind?: oc.SpanKind | undefined; + parentSpanId?: string | undefined; +} + +export class ShimSpan implements oc.Span { + get id(): string { + return this.otelSpan.spanContext().spanId; + } + + get tracer(): oc.TracerBase { + return this._shimTracer; + } + + logger: oc.Logger = diag; + + /** These are not readable in OTel so we return empty values */ + attributes: oc.Attributes = {}; + annotations: oc.Annotation[] = []; + messageEvents: oc.MessageEvent[] = []; + spans: oc.Span[] = []; + links: oc.Link[] = []; + name = ''; + status: oc.Status = STATUS_OK; + activeTraceParams: oc.TraceParams = {}; + droppedAttributesCount = 0; + droppedLinksCount = 0; + droppedAnnotationsCount = 0; + droppedMessageEventsCount = 0; + started = true; + ended = false; + numberOfChildren = 0; + duration = 0; + + /** Actual private attributes */ + private _shimTracer: ShimTracer; + readonly otelSpan: Span; + private _isRootSpan: boolean; + + readonly kind: oc.SpanKind; + readonly parentSpanId: string; + + get remoteParent(): boolean { + return this.otelSpan.spanContext().isRemote ?? false; + } + + /** Constructs a new SpanBaseModel instance. */ + constructor({ + shimTracer, + otelSpan, + isRootSpan = false, + kind = oc.SpanKind.UNSPECIFIED, + parentSpanId = '', + }: Options) { + this._shimTracer = shimTracer; + this.otelSpan = otelSpan; + this._isRootSpan = isRootSpan; + this.kind = kind; + this.parentSpanId = parentSpanId; + } + + /** Returns whether a span is root or not. */ + isRootSpan(): boolean { + return this._isRootSpan; + } + + get traceId(): string { + return this.otelSpan.spanContext().traceId; + } + + /** Gets the trace state */ + get traceState(): oc.TraceState | undefined { + return this.otelSpan.spanContext().traceState?.serialize(); + } + + /** No-op implementation of this method. */ + get startTime(): Date { + return new Date(); + } + + /** No-op implementation of this method. */ + get endTime(): Date { + return new Date(); + } + + /** No-op implementation of this method. */ + allDescendants(): oc.Span[] { + return []; + } + + /** Gives the TraceContext of the span. */ + get spanContext(): oc.SpanContext { + return reverseMapSpanContext(this.otelSpan.spanContext()); + } + + addAttribute(key: string, value: string | number | boolean | object) { + this.otelSpan.setAttribute(key, value as AttributeValue); + } + + addAnnotation( + description: string, + attributes?: oc.Attributes, + timestamp?: number + ) { + this.otelSpan.addEvent(description, attributes, timestamp); + } + + /** No-op implementation of this method. */ + addLink() { + diag.info( + 'Call to OpenCensus Span.addLink() is being ignored. OTel does not support ' + + 'adding links after span creation' + ); + } + + /** No-op implementation of this method. */ + addMessageEvent( + type: oc.MessageEventType, + id: number, + timestamp?: number, + uncompressedSize?: number, + compressedSize?: number + ) { + this.otelSpan.addEvent( + ...mapMessageEvent(type, id, timestamp, uncompressedSize, compressedSize) + ); + } + + /** No-op implementation of this method. */ + setStatus(code: oc.CanonicalCode, message?: string) { + this.otelSpan.setStatus({ + code: + code === oc.CanonicalCode.OK ? SpanStatusCode.OK : SpanStatusCode.ERROR, + message, + }); + } + + /** No-op implementation of this method. */ + start() {} + + end(): void { + this.otelSpan.end(); + } + + /** No-op implementation of this method. */ + truncate() {} + + /** Starts a new Span instance as a child of this instance */ + startChildSpan(options?: oc.SpanOptions): oc.Span { + return this._shimTracer.startChildSpan({ + name: DEFAULT_SPAN_NAME, + childOf: this, + ...options, + }); + } +} diff --git a/experimental/packages/shim-opencensus/src/ShimTracer.ts b/experimental/packages/shim-opencensus/src/ShimTracer.ts new file mode 100644 index 0000000000..f5e8164e3c --- /dev/null +++ b/experimental/packages/shim-opencensus/src/ShimTracer.ts @@ -0,0 +1,162 @@ +/* + * 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 oc from '@opencensus/core'; + +import { + Context, + context, + createContextKey, + diag, + INVALID_SPAN_CONTEXT, + trace, + Tracer, +} from '@opentelemetry/api'; +import { DEFAULT_SPAN_NAME, ShimSpan } from './ShimSpan'; +import { mapSpanContext, mapSpanKind } from './transform'; +import { shimPropagation } from './propagation'; + +// eslint-disable-next-line @typescript-eslint/no-non-null-assertion +const INVALID_SPAN = trace.getSpan( + trace.setSpanContext(context.active(), INVALID_SPAN_CONTEXT) +)!; +const ROOTSPAN_KEY = createContextKey('rootspan_for_oc_shim'); + +function setRootSpan(ctx: Context, span: ShimSpan): Context { + return ctx.setValue(ROOTSPAN_KEY, span); +} + +export function getRootSpan(ctx: Context): ShimSpan | null { + return ctx.getValue(ROOTSPAN_KEY) as ShimSpan | null; +} + +export class ShimTracer implements oc.Tracer { + logger: oc.Logger = diag; + active: boolean = false; + + /** Noop implementations */ + sampler: oc.Sampler = new oc.AlwaysSampler(); + activeTraceParams: oc.TraceParams = {}; + eventListeners: oc.SpanEventListener[] = []; + // Uses the global OpenTelemetry propagator by default + propagation: oc.Propagation = shimPropagation; + + constructor(private otelTracer: Tracer) {} + + start({ propagation }: oc.TracerConfig): this { + this.active = true; + // Pass a propagation here if you want the shim to use an OpenCensus propagation instance + // instead of the OpenTelemetry global propagator. + if (propagation) { + this.propagation = propagation; + } + return this; + } + + /** Noop implementations */ + stop(): this { + this.active = false; + return this; + } + registerSpanEventListener(): void {} + unregisterSpanEventListener(): void {} + clearCurrentTrace(): void {} + onStartSpan(): void {} + onEndSpan(): void {} + setCurrentRootSpan() { + // This can't be correctly overriden since OTel context does not provide a way to set + // context without a callback. Leave noop for now. + } + + /** Gets the current root span. */ + get currentRootSpan(): oc.Span { + return ( + getRootSpan(context.active()) ?? + new ShimSpan({ + shimTracer: this, + otelSpan: INVALID_SPAN, + }) + ); + } + + /** + * Starts a root span. + * @param options A TraceOptions object to start a root span. + * @param fn A callback function to run after starting a root span. + */ + startRootSpan( + { name, kind, spanContext }: oc.TraceOptions, + fn: (root: oc.Span) => T + ): T { + const parentCtx = + spanContext === undefined + ? context.active() + : trace.setSpanContext(context.active(), mapSpanContext(spanContext)); + + const otelSpan = this.otelTracer.startSpan( + name, + { kind: mapSpanKind(kind) }, + parentCtx + ); + const shimSpan = new ShimSpan({ + shimTracer: this, + otelSpan, + isRootSpan: true, + kind, + parentSpanId: trace.getSpanContext(parentCtx)?.spanId, + }); + + let ctx = trace.setSpan(parentCtx, otelSpan); + ctx = setRootSpan(ctx, shimSpan); + return context.with(ctx, () => fn(shimSpan)); + } + + startChildSpan(options?: oc.SpanOptions): oc.Span { + const { kind, name = DEFAULT_SPAN_NAME, childOf } = options ?? {}; + const rootSpan = getRootSpan(context.active()); + + let ctx = context.active(); + if (childOf) { + ctx = trace.setSpanContext(ctx, mapSpanContext(childOf.spanContext)); + } else if (rootSpan) { + ctx = trace.setSpan(ctx, rootSpan.otelSpan); + } + + const otelSpan = this.otelTracer.startSpan( + name, + { + kind: mapSpanKind(kind), + }, + ctx + ); + return new ShimSpan({ + shimTracer: this, + otelSpan, + isRootSpan: false, + kind, + parentSpanId: trace.getSpanContext(ctx)?.spanId, + }); + } + + wrap(fn: oc.Func): oc.Func { + return context.bind(context.active(), fn); + } + + wrapEmitter(emitter: NodeJS.EventEmitter): void { + // Not sure if this requires returning the modified emitter + context.bind(context.active(), emitter); + } +} diff --git a/experimental/packages/shim-opencensus/src/index.ts b/experimental/packages/shim-opencensus/src/index.ts index bcd7cadc04..ccfe42e2f8 100644 --- a/experimental/packages/shim-opencensus/src/index.ts +++ b/experimental/packages/shim-opencensus/src/index.ts @@ -13,3 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +export { ShimTracer } from './ShimTracer'; diff --git a/experimental/packages/shim-opencensus/src/register.ts b/experimental/packages/shim-opencensus/src/register.ts new file mode 100644 index 0000000000..c07f54c2b2 --- /dev/null +++ b/experimental/packages/shim-opencensus/src/register.ts @@ -0,0 +1,19 @@ +/* + * 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 { installShim } from './shim'; + +installShim(); diff --git a/experimental/packages/shim-opencensus/src/shim.ts b/experimental/packages/shim-opencensus/src/shim.ts new file mode 100644 index 0000000000..0426561c76 --- /dev/null +++ b/experimental/packages/shim-opencensus/src/shim.ts @@ -0,0 +1,62 @@ +/* + * 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 { diag, trace, Tracer } from '@opentelemetry/api'; +import { Hook } from 'require-in-the-middle'; +import * as oc from '@opencensus/core'; + +import { ShimTracer } from './ShimTracer'; +import { VERSION } from './version'; + +type CoreTracerConstructor = new ( + ...args: ConstructorParameters +) => oc.Tracer; + +let hook: Hook | null = null; + +interface OpenCensusShimConfig { + /** + * An optional OpenTelemetry tracer to send OpenCensus spans to. If not provided, one will be + * created for you. + */ + tracer?: Tracer | undefined; +} + +/** + * Patches OpenCensus to redirect all instrumentation to OpenTelemetry. Uses + * require-in-the-middle to override the implementation of OpenCensus's CoreTracer. + * + * Use {@link uninstallShim} to undo the effects of this function. + * + * @param config + */ +export function installShim({ + tracer = trace.getTracer('@opentelemetry/shim-opencensus', VERSION), +}: OpenCensusShimConfig = {}): void { + diag.info('Installing OpenCensus shim require-in-the-middle hook'); + + hook = new Hook(['@opencensus/core'], exports => { + const CoreTracer: CoreTracerConstructor = ShimTracer.bind(null, tracer); + return { + ...exports, + CoreTracer, + }; + }); +} + +export function uninstallShim(): void { + hook?.unhook(); +} diff --git a/experimental/packages/shim-opencensus/test/ShimSpan.test.ts b/experimental/packages/shim-opencensus/test/ShimSpan.test.ts new file mode 100644 index 0000000000..f8883cf10c --- /dev/null +++ b/experimental/packages/shim-opencensus/test/ShimSpan.test.ts @@ -0,0 +1,128 @@ +/* + * 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 { ReadableSpan } from '@opentelemetry/sdk-trace-base'; +import * as oc from '@opencensus/core'; +import * as assert from 'assert'; +import { withTestTracer, setupNodeContextManager } from './util'; +import { ShimSpan } from '../src/ShimSpan'; + +async function withTestSpan( + func: (span: ShimSpan) => void | Promise +): Promise { + return await withTestTracer(tracer => + tracer.startRootSpan({ name: 'test' }, span => func(span as ShimSpan)) + ); +} + +describe('ShimSpan', () => { + setupNodeContextManager(before, after); + + describe('id', () => { + it('should return the span id of the underlying otel span', async () => { + await withTestSpan(span => { + assert.strictEqual(span.id, span.otelSpan.spanContext().spanId); + }); + }); + }); + + describe('traceId', () => { + it('should return the trace id of the underlying otel span', async () => { + await withTestSpan(span => { + assert.strictEqual(span.traceId, span.otelSpan.spanContext().traceId); + }); + }); + }); + + describe('addAttribute', () => { + it('should add attributes', async () => { + const [span] = await withTestSpan(span => { + span.addAttribute('foo', 'bar'); + span.end(); + }); + + assert.deepStrictEqual(span.attributes, { foo: 'bar' }); + }); + }); + + describe('addAnnotation', () => { + it('should add an event', async () => { + const [span] = await withTestSpan(span => { + span.addAnnotation('the annotation', { foo: 'bar' }); + span.end(); + }); + + assert.strictEqual(span.events.length, 1); + const [{ time, ...event }] = span.events; + assert.deepStrictEqual(event, { + attributes: { + foo: 'bar', + }, + droppedAttributesCount: 0, + name: 'the annotation', + }); + }); + }); + + describe('addMessageEvent', () => { + it('should add an event', async () => { + const [span] = await withTestSpan(span => { + span.addMessageEvent(oc.MessageEventType.SENT, 98, undefined, 12, 15); + span.end(); + }); + + assert.strictEqual(span.events.length, 1); + const [{ time, ...event }] = span.events; + assert.deepStrictEqual(event, { + attributes: { + 'message.event.size.compressed': 15, + 'message.event.size.uncompressed': 12, + 'message.event.type': 'SENT', + }, + droppedAttributesCount: 0, + name: '98', + }); + }); + }); + + describe('startChildSpan', () => { + it('should start a child of the current span without options', async () => { + const [childSpan, parentSpan] = await withTestSpan(span => { + span.startChildSpan().end(); + span.end(); + }); + + assert.strictEqual(childSpan.name, 'span'); + assert.deepStrictEqual( + childSpan.parentSpanId, + parentSpan.spanContext().spanId + ); + }); + + it('should start a child of the current span with options', async () => { + const [childSpan, parentSpan] = await withTestSpan(span => { + span.startChildSpan({ name: 'child' }).end(); + span.end(); + }); + + assert.strictEqual(childSpan.name, 'child'); + assert.deepStrictEqual( + childSpan.parentSpanId, + parentSpan.spanContext().spanId + ); + }); + }); +}); diff --git a/experimental/packages/shim-opencensus/test/ShimTracer.test.ts b/experimental/packages/shim-opencensus/test/ShimTracer.test.ts index 33c99a6d8a..a65a2c67be 100644 --- a/experimental/packages/shim-opencensus/test/ShimTracer.test.ts +++ b/experimental/packages/shim-opencensus/test/ShimTracer.test.ts @@ -14,10 +14,171 @@ * limitations under the License. */ +import { Tracer as Tracer } from '@opentelemetry/sdk-trace-base'; import * as assert from 'assert'; +import * as sinon from 'sinon'; +import * as oc from '@opencensus/core'; +import { ShimTracer, getRootSpan } from '../src/ShimTracer'; +import { + INVALID_SPANID, + INVALID_TRACEID, + SpanKind, + context, + createContextKey, +} from '@opentelemetry/api'; +import { withTestTracer, setupNodeContextManager } from './util'; describe('ShimTracer', () => { - it('asserts true', () => { - assert.ok(true); + setupNodeContextManager(before, after); + + it('should initially be inactive', () => { + const shimTracer = new ShimTracer(sinon.createStubInstance(Tracer)); + assert(!shimTracer.active); + }); + describe('start', () => { + it('should set the tracer as active', () => { + const shimTracer = new ShimTracer(sinon.createStubInstance(Tracer)); + shimTracer.start({}); + assert(shimTracer.active); + }); + }); + describe('stop', () => { + it('should set the tracer as inactive', () => { + const shimTracer = new ShimTracer(sinon.createStubInstance(Tracer)); + shimTracer.start({}); + assert(shimTracer.active); + }); + }); + + describe('startRootSpan', () => { + it('should create an OTel span with name', async () => { + const otelSpans = await withTestTracer(shimTracer => { + shimTracer.startRootSpan({ name: 'test' }, span => span.end()); + }); + assert.strictEqual(otelSpans[0].name, 'test'); + }); + + it('should create an OTel span with kind', async () => { + const otelSpans = await withTestTracer(shimTracer => { + shimTracer.startRootSpan( + { name: 'test', kind: oc.SpanKind.CLIENT }, + span => span.end() + ); + }); + assert.strictEqual(otelSpans[0].kind, SpanKind.CLIENT); + }); + + it('should create an OTel span with parent from provided spanContext', async () => { + const otelSpans = await withTestTracer(shimTracer => { + shimTracer.startRootSpan( + { + name: 'test', + spanContext: { + traceId: '9e7ecdc193765065fee1efe757fdd874', + spanId: '4bf6239d37d8b0f0', + }, + }, + span => span.end() + ); + }); + assert.strictEqual( + otelSpans[0].spanContext().traceId, + '9e7ecdc193765065fee1efe757fdd874' + ); + assert.strictEqual(otelSpans[0].parentSpanId, '4bf6239d37d8b0f0'); + }); + + it('should set the span as root span in context', async () => { + await withTestTracer(shimTracer => { + shimTracer.startRootSpan({ name: 'test' }, span => { + assert.strictEqual(getRootSpan(context.active()), span); + }); + }); + }); + }); + + describe('currentRootSpan', () => { + it('should return an span with invalid span context if there is no root', async () => { + await withTestTracer(shimTracer => { + assert.strictEqual(shimTracer.currentRootSpan.traceId, INVALID_TRACEID); + assert.strictEqual(shimTracer.currentRootSpan.id, INVALID_SPANID); + }); + }); + + it('should return the current root span', async () => { + await withTestTracer(shimTracer => { + shimTracer.startRootSpan({ name: 'test' }, span => { + assert.strictEqual(shimTracer.currentRootSpan, span); + }); + }); + }); + }); + + describe('startChildSpan', () => { + it('should create an OTel span with a default name', async () => { + const otelSpans = await withTestTracer(shimTracer => { + shimTracer.startChildSpan().end(); + }); + assert.strictEqual(otelSpans[0].name, 'span'); + }); + + it('should create an OTel span with name', async () => { + const otelSpans = await withTestTracer(shimTracer => { + shimTracer.startChildSpan({ name: 'test' }).end(); + }); + assert.strictEqual(otelSpans[0].name, 'test'); + }); + + it('should create an OTel span with kind', async () => { + const otelSpans = await withTestTracer(shimTracer => { + shimTracer + .startChildSpan({ name: 'test', kind: oc.SpanKind.CLIENT }) + .end(); + }); + assert.strictEqual(otelSpans[0].kind, SpanKind.CLIENT); + }); + + it('should create an OTel span with parent from childOf', async () => { + const [childSpan, parentSpan] = await withTestTracer(shimTracer => { + const parent = shimTracer.startChildSpan({ + name: 'parent', + }); + shimTracer.startChildSpan({ name: 'child', childOf: parent }).end(); + parent.end(); + }); + assert.strictEqual( + childSpan.parentSpanId, + parentSpan.spanContext().spanId + ); + }); + + it('should create an OTel span with parent from root in context', async () => { + const [childSpan, rootSpan] = await withTestTracer(shimTracer => { + shimTracer.startRootSpan( + { + name: 'parent', + }, + root => { + shimTracer.startChildSpan({ name: 'child', childOf: root }).end(); + root.end(); + } + ); + }); + assert.strictEqual(childSpan.parentSpanId, rootSpan.spanContext().spanId); + }); + }); + + describe('wrap', () => { + it('should bind the provided function to active context at time of wrapping', () => { + const shimTracer = new ShimTracer(sinon.createStubInstance(Tracer)); + const key = createContextKey('key'); + const fnToWrap = () => + assert.strictEqual(context.active().getValue(key), 'value'); + + const fn = context.with(context.active().setValue(key, 'value'), () => + shimTracer.wrap(fnToWrap) + ); + fn(); + }); }); }); diff --git a/experimental/packages/shim-opencensus/test/otel-sandwich.test.ts b/experimental/packages/shim-opencensus/test/otel-sandwich.test.ts new file mode 100644 index 0000000000..a1034bf081 --- /dev/null +++ b/experimental/packages/shim-opencensus/test/otel-sandwich.test.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 assert from 'assert'; +import { setupNodeContextManager, withTestTracer } from './util'; + +/** + * Tests the "OpenTelemetry sandwich" problem described in the spec at + * https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/compatibility/opencensus.md#trace-bridge + */ +describe('OpenTelemetry sandwich', () => { + setupNodeContextManager(before, after); + + it('should maintain parent-child relationship for OTel -> OC', async () => { + const spans = await withTestTracer((shimTracer, otelTracer) => { + otelTracer.startActiveSpan('parent-otel', parentSpan => { + shimTracer.startChildSpan({ name: 'child-oc' }).end(); + parentSpan.end(); + }); + }); + + assert.strictEqual(spans.length, 2); + const [childSpan, parentSpan] = spans; + assert.strictEqual( + childSpan.spanContext().traceId, + parentSpan.spanContext().traceId + ); + assert.strictEqual(childSpan.parentSpanId, parentSpan.spanContext().spanId); + }); + + it('should maintain parent-child relationship for OC -> OTel', async () => { + const spans = await withTestTracer((shimTracer, otelTracer) => { + shimTracer.startRootSpan({ name: 'parent-oc' }, parentSpan => { + otelTracer.startSpan('child-otel').end(); + parentSpan.end(); + }); + }); + + assert.strictEqual(spans.length, 2); + const [childSpan, parentSpan] = spans; + assert.strictEqual( + childSpan.spanContext().traceId, + parentSpan.spanContext().traceId + ); + assert.strictEqual(childSpan.parentSpanId, parentSpan.spanContext().spanId); + }); + + it('should maintain structure for OTel -> OC -> OTel', async () => { + const spans = await withTestTracer((shimTracer, otelTracer) => { + otelTracer.startActiveSpan('parent-otel', parentSpan => { + shimTracer.startRootSpan({ name: 'middle-oc' }, middleSpan => { + otelTracer.startSpan('child-otel').end(); + middleSpan.end(); + }); + parentSpan.end(); + }); + }); + + assert.strictEqual(spans.length, 3); + const [childSpan, middleSpan, parentSpan] = spans; + assert.strictEqual( + childSpan.spanContext().traceId, + parentSpan.spanContext().traceId + ); + assert.strictEqual( + middleSpan.spanContext().traceId, + parentSpan.spanContext().traceId + ); + assert.strictEqual( + middleSpan.parentSpanId, + parentSpan.spanContext().spanId + ); + assert.strictEqual(childSpan.parentSpanId, middleSpan.spanContext().spanId); + }); +}); diff --git a/experimental/packages/shim-opencensus/test/shim.test.ts b/experimental/packages/shim-opencensus/test/shim.test.ts new file mode 100644 index 0000000000..525a8026a7 --- /dev/null +++ b/experimental/packages/shim-opencensus/test/shim.test.ts @@ -0,0 +1,72 @@ +/* + * 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 assert from 'assert'; +import { installShim, uninstallShim } from '../src/shim'; +import { ShimTracer } from '../src'; +import { CoreTracer as OrigCoreTracer } from '@opencensus/core'; +import { withTestTracerProvider } from './util'; +import { trace } from '@opentelemetry/api'; + +describe('shim', () => { + beforeEach(uninstallShim); + afterEach(uninstallShim); + afterEach(() => { + trace.disable(); + }); + + describe('installShim', () => { + it('should patch the @opencensus/core CoreTracer to create instances of the ShimTracer', () => { + installShim(); + const { CoreTracer } = require('@opencensus/core'); + assert.notStrictEqual(CoreTracer, OrigCoreTracer); + assert(new CoreTracer() instanceof ShimTracer); + }); + + it('should use the provided Tracer', async () => { + const spans = await withTestTracerProvider(tracerProvider => { + const tracer = tracerProvider.getTracer('test'); + installShim({ tracer }); + const CoreTracer: typeof OrigCoreTracer = + require('@opencensus/core').CoreTracer; + const coreTracer = new CoreTracer(); + coreTracer.startChildSpan().end(); + }); + assert.strictEqual(spans.length, 1); + }); + + it('should use the global OpenTelemetry TracerProvider if none provided', async () => { + installShim(); + const spans = await withTestTracerProvider(tracerProvider => { + trace.setGlobalTracerProvider(tracerProvider); + const CoreTracer: typeof OrigCoreTracer = + require('@opencensus/core').CoreTracer; + const coreTracer = new CoreTracer(); + coreTracer.startChildSpan().end(); + }); + assert.strictEqual(spans.length, 1); + }); + }); + + describe('uninstallShim', () => { + it('should restore the original CoreTracer', () => { + installShim(); + uninstallShim(); + const { CoreTracer } = require('@opencensus/core'); + assert.strictEqual(CoreTracer, OrigCoreTracer); + }); + }); +}); diff --git a/experimental/packages/shim-opencensus/test/util.ts b/experimental/packages/shim-opencensus/test/util.ts new file mode 100644 index 0000000000..842525ea0c --- /dev/null +++ b/experimental/packages/shim-opencensus/test/util.ts @@ -0,0 +1,71 @@ +/* + * 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 { + AlwaysOnSampler, + BasicTracerProvider, + InMemorySpanExporter, + ReadableSpan, + SimpleSpanProcessor, +} from '@opentelemetry/sdk-trace-base'; +import { ShimTracer } from '../src/ShimTracer'; +import * as semver from 'semver'; +import { + AsyncHooksContextManager, + AsyncLocalStorageContextManager, +} from '@opentelemetry/context-async-hooks'; +import { Tracer, TracerProvider, context } from '@opentelemetry/api'; + +export async function withTestTracer( + func: (shimTracer: ShimTracer, otelTracer: Tracer) => void | Promise +): Promise { + return await withTestTracerProvider(tracerProvider => + func( + new ShimTracer(tracerProvider.getTracer('test-shim')), + tracerProvider.getTracer('test-otel') + ) + ); +} + +export async function withTestTracerProvider( + func: (otelTracerProvider: TracerProvider) => void | Promise +): Promise { + const tracerProvider = new BasicTracerProvider({ + sampler: new AlwaysOnSampler(), + }); + const inMemExporter = new InMemorySpanExporter(); + tracerProvider.addSpanProcessor(new SimpleSpanProcessor(inMemExporter)); + + await func(tracerProvider); + + await tracerProvider.forceFlush(); + const spans = inMemExporter.getFinishedSpans(); + await tracerProvider.shutdown(); + return spans; +} + +export function setupNodeContextManager( + before: Mocha.HookFunction, + after: Mocha.HookFunction +) { + const ContextManager = semver.gte(process.version, '14.8.0') + ? AsyncLocalStorageContextManager + : AsyncHooksContextManager; + const instance = new ContextManager(); + instance.enable(); + before(() => context.setGlobalContextManager(instance)); + after(() => context.disable()); +} diff --git a/experimental/packages/shim-opencensus/tsconfig.json b/experimental/packages/shim-opencensus/tsconfig.json index ddf3cfdeed..91cebb5ad5 100644 --- a/experimental/packages/shim-opencensus/tsconfig.json +++ b/experimental/packages/shim-opencensus/tsconfig.json @@ -17,6 +17,9 @@ }, { "path": "../../../packages/opentelemetry-core" + }, + { + "path": "../../../packages/opentelemetry-sdk-trace-base" } ] } diff --git a/integration-tests/propagation-validation-server/package.json b/integration-tests/propagation-validation-server/package.json index 740a61554c..88846f5d9f 100644 --- a/integration-tests/propagation-validation-server/package.json +++ b/integration-tests/propagation-validation-server/package.json @@ -1,6 +1,6 @@ { "name": "propagation-validation-server", - "version": "1.12.0", + "version": "1.13.0", "description": "server for w3c tests", "main": "validation_server.js", "private": true, @@ -12,10 +12,10 @@ }, "dependencies": { "@opentelemetry/api": "^1.0.0", - "@opentelemetry/context-async-hooks": "1.12.0", - "@opentelemetry/core": "1.12.0", - "@opentelemetry/sdk-trace-base": "1.12.0", - "axios": "0.24.0", + "@opentelemetry/context-async-hooks": "1.13.0", + "@opentelemetry/core": "1.13.0", + "@opentelemetry/sdk-trace-base": "1.13.0", + "axios": "1.4.0", "body-parser": "1.19.0", "express": "4.17.3" }, diff --git a/karma.webpack.js b/karma.webpack.js index 559d129b65..138112547b 100644 --- a/karma.webpack.js +++ b/karma.webpack.js @@ -32,12 +32,12 @@ module.exports = { test: /\.ts$/, use: { loader: 'istanbul-instrumenter-loader', - options: { esModules: true } - } + options: { esModules: true }, + }, }, // This setting configures Node polyfills for the browser that will be // added to the webpack bundle for Karma tests. - { parser: { node: webpackNodePolyfills } } - ] - } + { parser: { node: webpackNodePolyfills } }, + ], + }, }; diff --git a/lerna.json b/lerna.json index 6eba3f4874..14fb76982e 100644 --- a/lerna.json +++ b/lerna.json @@ -12,6 +12,7 @@ "examples/otlp-exporter-node", "examples/opentelemetry-web", "examples/http", - "examples/https" + "examples/https", + "examples/esm-http-ts" ] } diff --git a/package.json b/package.json index d03e6ff438..0d232ef636 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,24 @@ "lint:markdown": "./node_modules/.bin/markdownlint $(git ls-files '*.md') -i ./CHANGELOG.md", "lint:markdown:fix": "./node_modules/.bin/markdownlint $(git ls-files '*.md') -i ./CHANGELOG.md --fix", "reset": "lerna clean -y && rm -rf node_modules && npm i && npm run compile && npm run lint:fix", - "update-ts-configs": "node scripts/update-ts-configs.js" + "update-ts-configs": "node scripts/update-ts-configs.js", + + "comment_prepare_1": "echo scripts in this section automatically prepare releases. Intended for use by maintainers only.", + "comment_prepare_2": "echo experimental preparation scripts only prepare experimental packages", + "prepare_release:experimental:patch": "npm run _check:no_changes && npm run _backup:lerna && npm run _lerna:remove_api && npm run _lerna:remove_stable && npm run _lerna:version_patch && npm run _restore:lerna", + "prepare_release:experimental:minor": "npm run _check:no_changes && npm run _backup:lerna && npm run _lerna:remove_api && npm run _lerna:remove_stable && npm run _lerna:version_minor && npm run _restore:lerna", + "comment_prepare_3": "echo sdk preparation scripts prepare all stable and experimental packages", + "prepare_release:sdk:patch": "npm run _check:no_changes && npm run _backup:lerna && npm run _lerna:remove_api && npm run _lerna:version_patch && npm run _restore:lerna", + "prepare_release:sdk:minor": "npm run _check:no_changes && npm run _backup:lerna && npm run _lerna:remove_api && npm run _lerna:version_minor && npm run _restore:lerna", + + "comment_internal": "echo scripts below this line are for internal use", + "_check:no_changes": "if [ ! -z \"$(git status -uall --porcelain)\" ]; then echo Please ensure all changes are committed; exit 1; fi", + "_backup:lerna": "cp lerna.json lerna.json.backup", + "_restore:lerna": "mv lerna.json.backup lerna.json", + "_lerna:remove_api": "node -e 'var fs=require(\"fs\");var l=require(\"./lerna.json\");l.packages=l.packages.filter(p=>p!==\"api\");fs.writeFileSync(\"lerna.json\",JSON.stringify(l,null,2))'", + "_lerna:remove_stable": "node -e 'var fs=require(\"fs\");var l=require(\"./lerna.json\");l.packages=l.packages.filter(p=>p!==\"packages/*\");fs.writeFileSync(\"lerna.json\",JSON.stringify(l,null,2))'", + "_lerna:version_patch": "npx lerna version patch --exact --no-git-tag-version --no-push --yes", + "_lerna:version_minor": "npx lerna version minor --exact --no-git-tag-version --no-push --yes" }, "repository": "open-telemetry/opentelemetry-js", "keywords": [ @@ -55,7 +72,6 @@ "eslint-plugin-prettier": "4.2.1", "gh-pages": "5.0.0", "lerna": "6.0.3", - "lerna-changelog": "2.2.0", "linkinator": "4.0.3", "markdownlint-cli": "0.32.2", "prettier": "2.8.0", diff --git a/packages/opentelemetry-context-async-hooks/package.json b/packages/opentelemetry-context-async-hooks/package.json index 2e1cf67a18..efda2f556d 100644 --- a/packages/opentelemetry-context-async-hooks/package.json +++ b/packages/opentelemetry-context-async-hooks/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/context-async-hooks", - "version": "1.12.0", + "version": "1.13.0", "description": "OpenTelemetry AsyncHooks-based Context Manager", "main": "build/src/index.js", "types": "build/src/index.d.ts", diff --git a/packages/opentelemetry-context-zone-peer-dep/package.json b/packages/opentelemetry-context-zone-peer-dep/package.json index acee0a3244..dfcf81d491 100644 --- a/packages/opentelemetry-context-zone-peer-dep/package.json +++ b/packages/opentelemetry-context-zone-peer-dep/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/context-zone-peer-dep", - "version": "1.12.0", + "version": "1.13.0", "description": "OpenTelemetry Context Zone with peer dependency for zone.js", "main": "build/src/index.js", "module": "build/esm/index.js", diff --git a/packages/opentelemetry-context-zone/package.json b/packages/opentelemetry-context-zone/package.json index c886fdbf01..57d82fcbbc 100644 --- a/packages/opentelemetry-context-zone/package.json +++ b/packages/opentelemetry-context-zone/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/context-zone", - "version": "1.12.0", + "version": "1.13.0", "description": "OpenTelemetry Context Zone", "main": "build/src/index.js", "module": "build/esm/index.js", @@ -73,7 +73,7 @@ "webpack-merge": "5.8.0" }, "dependencies": { - "@opentelemetry/context-zone-peer-dep": "1.12.0", + "@opentelemetry/context-zone-peer-dep": "1.13.0", "zone.js": "^0.11.0" }, "sideEffects": true, diff --git a/packages/opentelemetry-core/package.json b/packages/opentelemetry-core/package.json index f6de9f673f..5900c8d204 100644 --- a/packages/opentelemetry-core/package.json +++ b/packages/opentelemetry-core/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/core", - "version": "1.12.0", + "version": "1.13.0", "description": "OpenTelemetry Core provides constants and utilities shared by all OpenTelemetry SDK packages.", "main": "build/src/index.js", "module": "build/esm/index.js", @@ -90,7 +90,7 @@ "@opentelemetry/api": ">=1.0.0 <1.5.0" }, "dependencies": { - "@opentelemetry/semantic-conventions": "1.12.0" + "@opentelemetry/semantic-conventions": "1.13.0" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-core", "sideEffects": false diff --git a/packages/opentelemetry-exporter-jaeger/package.json b/packages/opentelemetry-exporter-jaeger/package.json index b91d2f1140..6dfc53b45c 100644 --- a/packages/opentelemetry-exporter-jaeger/package.json +++ b/packages/opentelemetry-exporter-jaeger/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/exporter-jaeger", - "version": "1.12.0", + "version": "1.13.0", "description": "OpenTelemetry Exporter Jaeger allows user to send collected traces to Jaeger", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -45,7 +45,7 @@ }, "devDependencies": { "@opentelemetry/api": "^1.0.0", - "@opentelemetry/resources": "1.12.0", + "@opentelemetry/resources": "1.13.0", "@types/mocha": "10.0.0", "@types/node": "18.6.5", "@types/sinon": "10.0.13", @@ -61,9 +61,9 @@ "@opentelemetry/api": "^1.0.0" }, "dependencies": { - "@opentelemetry/core": "1.12.0", - "@opentelemetry/sdk-trace-base": "1.12.0", - "@opentelemetry/semantic-conventions": "1.12.0", + "@opentelemetry/core": "1.13.0", + "@opentelemetry/sdk-trace-base": "1.13.0", + "@opentelemetry/semantic-conventions": "1.13.0", "jaeger-client": "^3.15.0" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-exporter-jaeger", diff --git a/packages/opentelemetry-exporter-jaeger/src/jaeger.ts b/packages/opentelemetry-exporter-jaeger/src/jaeger.ts index 28d3ccb337..aafc884cd0 100644 --- a/packages/opentelemetry-exporter-jaeger/src/jaeger.ts +++ b/packages/opentelemetry-exporter-jaeger/src/jaeger.ts @@ -97,6 +97,13 @@ export class JaegerExporter implements SpanExporter { return this._shutdownOnce.call(); } + /** + * Exports any pending spans in exporter + */ + forceFlush(): Promise { + return this._flush(); + } + private _shutdown(): Promise { return Promise.race([ new Promise((_resolve, reject) => { diff --git a/packages/opentelemetry-exporter-jaeger/test/jaeger.test.ts b/packages/opentelemetry-exporter-jaeger/test/jaeger.test.ts index bcfdbf051f..d667579637 100644 --- a/packages/opentelemetry-exporter-jaeger/test/jaeger.test.ts +++ b/packages/opentelemetry-exporter-jaeger/test/jaeger.test.ts @@ -178,6 +178,14 @@ describe('JaegerExporter', () => { }); }); + describe('force flush', () => { + let exporter: JaegerExporter; + it('forceFlush should flush spans and return', async () => { + exporter = new JaegerExporter(); + await exporter.forceFlush(); + }); + }); + describe('export', () => { let exporter: JaegerExporter; diff --git a/packages/opentelemetry-exporter-zipkin/package.json b/packages/opentelemetry-exporter-zipkin/package.json index 5368ac24bf..a6c0e26d47 100644 --- a/packages/opentelemetry-exporter-zipkin/package.json +++ b/packages/opentelemetry-exporter-zipkin/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/exporter-zipkin", - "version": "1.12.0", + "version": "1.13.0", "description": "OpenTelemetry Zipkin Exporter allows the user to send collected traces to Zipkin.", "main": "build/src/index.js", "module": "build/esm/index.js", @@ -90,10 +90,10 @@ "@opentelemetry/api": "^1.0.0" }, "dependencies": { - "@opentelemetry/core": "1.12.0", - "@opentelemetry/resources": "1.12.0", - "@opentelemetry/sdk-trace-base": "1.12.0", - "@opentelemetry/semantic-conventions": "1.12.0" + "@opentelemetry/core": "1.13.0", + "@opentelemetry/resources": "1.13.0", + "@opentelemetry/sdk-trace-base": "1.13.0", + "@opentelemetry/semantic-conventions": "1.13.0" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-exporter-zipkin", "sideEffects": false diff --git a/packages/opentelemetry-exporter-zipkin/src/zipkin.ts b/packages/opentelemetry-exporter-zipkin/src/zipkin.ts index 2cb3e44436..ca52806d39 100644 --- a/packages/opentelemetry-exporter-zipkin/src/zipkin.ts +++ b/packages/opentelemetry-exporter-zipkin/src/zipkin.ts @@ -102,6 +102,13 @@ export class ZipkinExporter implements SpanExporter { shutdown(): Promise { diag.debug('Zipkin exporter shutdown'); this._isShutdown = true; + return this.forceFlush(); + } + + /** + * Exports any pending spans in exporter + */ + forceFlush(): Promise { return new Promise((resolve, reject) => { Promise.all(this._sendingPromises).then(() => { resolve(); diff --git a/packages/opentelemetry-exporter-zipkin/test/node/zipkin.test.ts b/packages/opentelemetry-exporter-zipkin/test/node/zipkin.test.ts index eeab75348e..3001662498 100644 --- a/packages/opentelemetry-exporter-zipkin/test/node/zipkin.test.ts +++ b/packages/opentelemetry-exporter-zipkin/test/node/zipkin.test.ts @@ -526,6 +526,13 @@ describe('Zipkin Exporter - node', () => { }); }); + describe('force flush', () => { + it('forceFlush should flush spans and return', async () => { + const exporter = new ZipkinExporter({}); + await exporter.forceFlush(); + }); + }); + describe('when env.OTEL_EXPORTER_ZIPKIN_ENDPOINT is set', () => { before(() => { process.env.OTEL_EXPORTER_ZIPKIN_ENDPOINT = 'http://localhost:9412'; diff --git a/packages/opentelemetry-propagator-b3/package.json b/packages/opentelemetry-propagator-b3/package.json index 4ba7209c96..31fde1399c 100644 --- a/packages/opentelemetry-propagator-b3/package.json +++ b/packages/opentelemetry-propagator-b3/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/propagator-b3", - "version": "1.12.0", + "version": "1.13.0", "description": "OpenTelemetry B3 propagator provides context propagation for systems that are using the B3 header format", "main": "build/src/index.js", "module": "build/esm/index.js", @@ -51,7 +51,7 @@ "access": "public" }, "dependencies": { - "@opentelemetry/core": "1.12.0" + "@opentelemetry/core": "1.13.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.5.0" diff --git a/packages/opentelemetry-propagator-jaeger/package.json b/packages/opentelemetry-propagator-jaeger/package.json index 1ad42bea6d..30aa851011 100644 --- a/packages/opentelemetry-propagator-jaeger/package.json +++ b/packages/opentelemetry-propagator-jaeger/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/propagator-jaeger", - "version": "1.12.0", + "version": "1.13.0", "description": "OpenTelemetry Jaeger propagator provides HTTP header propagation for systems that are using Jaeger HTTP header format.", "main": "build/src/index.js", "module": "build/esm/index.js", @@ -79,7 +79,7 @@ "@opentelemetry/api": ">=1.0.0 <1.5.0" }, "dependencies": { - "@opentelemetry/core": "1.12.0" + "@opentelemetry/core": "1.13.0" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-propagator-jaeger", "sideEffects": false diff --git a/packages/opentelemetry-resources/package.json b/packages/opentelemetry-resources/package.json index a0ecdf80bd..a6bb86ecc5 100644 --- a/packages/opentelemetry-resources/package.json +++ b/packages/opentelemetry-resources/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/resources", - "version": "1.12.0", + "version": "1.13.0", "description": "OpenTelemetry SDK resources", "main": "build/src/index.js", "module": "build/esm/index.js", @@ -89,8 +89,8 @@ "@opentelemetry/api": ">=1.0.0 <1.5.0" }, "dependencies": { - "@opentelemetry/core": "1.12.0", - "@opentelemetry/semantic-conventions": "1.12.0" + "@opentelemetry/core": "1.13.0", + "@opentelemetry/semantic-conventions": "1.13.0" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-resources", "sideEffects": false diff --git a/packages/opentelemetry-sdk-trace-base/package.json b/packages/opentelemetry-sdk-trace-base/package.json index f80647b127..d493500dc1 100644 --- a/packages/opentelemetry-sdk-trace-base/package.json +++ b/packages/opentelemetry-sdk-trace-base/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/sdk-trace-base", - "version": "1.12.0", + "version": "1.13.0", "description": "OpenTelemetry Tracing", "main": "build/src/index.js", "module": "build/esm/index.js", @@ -91,9 +91,9 @@ "@opentelemetry/api": ">=1.0.0 <1.5.0" }, "dependencies": { - "@opentelemetry/core": "1.12.0", - "@opentelemetry/resources": "1.12.0", - "@opentelemetry/semantic-conventions": "1.12.0" + "@opentelemetry/core": "1.13.0", + "@opentelemetry/resources": "1.13.0", + "@opentelemetry/semantic-conventions": "1.13.0" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-sdk-trace-base", "sideEffects": false diff --git a/packages/opentelemetry-sdk-trace-base/src/export/ConsoleSpanExporter.ts b/packages/opentelemetry-sdk-trace-base/src/export/ConsoleSpanExporter.ts index 77b0965598..596b3cefd3 100644 --- a/packages/opentelemetry-sdk-trace-base/src/export/ConsoleSpanExporter.ts +++ b/packages/opentelemetry-sdk-trace-base/src/export/ConsoleSpanExporter.ts @@ -46,6 +46,13 @@ export class ConsoleSpanExporter implements SpanExporter { */ shutdown(): Promise { this._sendSpans([]); + return this.forceFlush(); + } + + /** + * Exports any pending spans in exporter + */ + forceFlush(): Promise { return Promise.resolve(); } diff --git a/packages/opentelemetry-sdk-trace-base/src/export/InMemorySpanExporter.ts b/packages/opentelemetry-sdk-trace-base/src/export/InMemorySpanExporter.ts index c7c17d80c1..4a755ea8cd 100644 --- a/packages/opentelemetry-sdk-trace-base/src/export/InMemorySpanExporter.ts +++ b/packages/opentelemetry-sdk-trace-base/src/export/InMemorySpanExporter.ts @@ -48,6 +48,13 @@ export class InMemorySpanExporter implements SpanExporter { shutdown(): Promise { this._stopped = true; this._finishedSpans = []; + return this.forceFlush(); + } + + /** + * Exports any pending spans in the exporter + */ + forceFlush(): Promise { return Promise.resolve(); } diff --git a/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts b/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts index 8351b4b6ed..463e8376bd 100644 --- a/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts +++ b/packages/opentelemetry-sdk-trace-base/src/export/SimpleSpanProcessor.ts @@ -46,6 +46,9 @@ export class SimpleSpanProcessor implements SpanProcessor { async forceFlush(): Promise { // await unresolved resources before resolving await Promise.all(Array.from(this._unresolvedExports)); + if (this._exporter.forceFlush) { + await this._exporter.forceFlush(); + } } onStart(_span: Span, _parentContext: Context): void {} diff --git a/packages/opentelemetry-sdk-trace-base/src/export/SpanExporter.ts b/packages/opentelemetry-sdk-trace-base/src/export/SpanExporter.ts index b3b89d4aa6..c9ca9c0c89 100644 --- a/packages/opentelemetry-sdk-trace-base/src/export/SpanExporter.ts +++ b/packages/opentelemetry-sdk-trace-base/src/export/SpanExporter.ts @@ -36,4 +36,7 @@ export interface SpanExporter { /** Stops the exporter. */ shutdown(): Promise; + + /** Immediately export all spans */ + forceFlush?(): Promise; } diff --git a/packages/opentelemetry-sdk-trace-base/test/common/export/ConsoleSpanExporter.test.ts b/packages/opentelemetry-sdk-trace-base/test/common/export/ConsoleSpanExporter.test.ts index 72642927a3..d005a2f803 100644 --- a/packages/opentelemetry-sdk-trace-base/test/common/export/ConsoleSpanExporter.test.ts +++ b/packages/opentelemetry-sdk-trace-base/test/common/export/ConsoleSpanExporter.test.ts @@ -99,4 +99,11 @@ describe('ConsoleSpanExporter', () => { }); }); }); + + describe('force flush', () => { + it('forceFlush should flush spans and return', async () => { + consoleExporter = new ConsoleSpanExporter(); + await consoleExporter.forceFlush(); + }); + }); }); 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 df9105d97b..585610514e 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 @@ -86,6 +86,13 @@ describe('InMemorySpanExporter', () => { assert.strictEqual(memoryExporter.getFinishedSpans().length, 0); }); + describe('force flush', () => { + it('forceFlush should flush spans and return', async () => { + memoryExporter = new InMemorySpanExporter(); + await memoryExporter.forceFlush(); + }); + }); + it('should return the success result', () => { const exorter = new InMemorySpanExporter(); exorter.export([], (result: ExportResult) => { diff --git a/packages/opentelemetry-sdk-trace-base/test/common/export/SimpleSpanProcessor.test.ts b/packages/opentelemetry-sdk-trace-base/test/common/export/SimpleSpanProcessor.test.ts index 88490c527b..94e86eeed7 100644 --- a/packages/opentelemetry-sdk-trace-base/test/common/export/SimpleSpanProcessor.test.ts +++ b/packages/opentelemetry-sdk-trace-base/test/common/export/SimpleSpanProcessor.test.ts @@ -152,6 +152,14 @@ describe('SimpleSpanProcessor', () => { }); describe('force flush', () => { + it('should call forceflush on exporter', () => { + const spyflush = sinon.spy(exporter, 'forceFlush'); + const processor = new SimpleSpanProcessor(exporter); + processor.forceFlush().then(() => { + assert.ok(spyflush.calledOnce); + }); + }); + it('should await unresolved resources', async () => { const processor = new SimpleSpanProcessor(exporter); const providerWithAsyncResource = new BasicTracerProvider({ diff --git a/packages/opentelemetry-sdk-trace-node/package.json b/packages/opentelemetry-sdk-trace-node/package.json index 110b0ad4c9..60ef5ccba8 100644 --- a/packages/opentelemetry-sdk-trace-node/package.json +++ b/packages/opentelemetry-sdk-trace-node/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/sdk-trace-node", - "version": "1.12.0", + "version": "1.13.0", "description": "OpenTelemetry Node SDK provides automatic telemetry (tracing, metrics, etc) for Node.js applications", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -46,8 +46,8 @@ }, "devDependencies": { "@opentelemetry/api": ">=1.0.0 <1.5.0", - "@opentelemetry/resources": "1.12.0", - "@opentelemetry/semantic-conventions": "1.12.0", + "@opentelemetry/resources": "1.13.0", + "@opentelemetry/semantic-conventions": "1.13.0", "@types/mocha": "10.0.0", "@types/node": "18.6.5", "@types/semver": "7.3.9", @@ -63,11 +63,11 @@ "@opentelemetry/api": ">=1.0.0 <1.5.0" }, "dependencies": { - "@opentelemetry/context-async-hooks": "1.12.0", - "@opentelemetry/core": "1.12.0", - "@opentelemetry/propagator-b3": "1.12.0", - "@opentelemetry/propagator-jaeger": "1.12.0", - "@opentelemetry/sdk-trace-base": "1.12.0", + "@opentelemetry/context-async-hooks": "1.13.0", + "@opentelemetry/core": "1.13.0", + "@opentelemetry/propagator-b3": "1.13.0", + "@opentelemetry/propagator-jaeger": "1.13.0", + "@opentelemetry/sdk-trace-base": "1.13.0", "semver": "^7.3.5" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-sdk-trace-node", diff --git a/packages/opentelemetry-sdk-trace-web/package.json b/packages/opentelemetry-sdk-trace-web/package.json index b20d6c21c6..21110b939c 100644 --- a/packages/opentelemetry-sdk-trace-web/package.json +++ b/packages/opentelemetry-sdk-trace-web/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/sdk-trace-web", - "version": "1.12.0", + "version": "1.13.0", "description": "OpenTelemetry Web Tracer", "main": "build/src/index.js", "module": "build/esm/index.js", @@ -57,9 +57,9 @@ "devDependencies": { "@babel/core": "7.16.0", "@opentelemetry/api": ">=1.0.0 <1.5.0", - "@opentelemetry/context-zone": "1.12.0", - "@opentelemetry/propagator-b3": "1.12.0", - "@opentelemetry/resources": "1.12.0", + "@opentelemetry/context-zone": "1.13.0", + "@opentelemetry/propagator-b3": "1.13.0", + "@opentelemetry/resources": "1.13.0", "@types/jquery": "3.5.8", "@types/mocha": "10.0.0", "@types/node": "18.6.5", @@ -90,9 +90,9 @@ "@opentelemetry/api": ">=1.0.0 <1.5.0" }, "dependencies": { - "@opentelemetry/core": "1.12.0", - "@opentelemetry/sdk-trace-base": "1.12.0", - "@opentelemetry/semantic-conventions": "1.12.0" + "@opentelemetry/core": "1.13.0", + "@opentelemetry/sdk-trace-base": "1.13.0", + "@opentelemetry/semantic-conventions": "1.13.0" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-sdk-trace-web", "sideEffects": false diff --git a/packages/opentelemetry-semantic-conventions/README.md b/packages/opentelemetry-semantic-conventions/README.md index 9974e9117a..bf2700473b 100644 --- a/packages/opentelemetry-semantic-conventions/README.md +++ b/packages/opentelemetry-semantic-conventions/README.md @@ -38,4 +38,4 @@ Apache 2.0 - See [LICENSE][license-url] for more information. [npm-url]: https://www.npmjs.com/package/@opentelemetry/semantic-conventions [npm-img]: https://badge.fury.io/js/%40opentelemetry%2Fsemantic-conventions.svg -[trace-semantic_conventions]: https://github.com/open-telemetry/opentelemetry-specification/tree/master/specification/trace/semantic_conventions +[trace-semantic_conventions]: https://github.com/open-telemetry/semantic-conventions/tree/main/specification/trace/semantic_conventions diff --git a/packages/opentelemetry-semantic-conventions/package.json b/packages/opentelemetry-semantic-conventions/package.json index 6146ca2016..9f094c97cd 100644 --- a/packages/opentelemetry-semantic-conventions/package.json +++ b/packages/opentelemetry-semantic-conventions/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/semantic-conventions", - "version": "1.12.0", + "version": "1.13.0", "description": "OpenTelemetry semantic conventions", "main": "build/src/index.js", "module": "build/esm/index.js", diff --git a/packages/opentelemetry-shim-opentracing/package.json b/packages/opentelemetry-shim-opentracing/package.json index 68c0846e1f..f009398ef2 100644 --- a/packages/opentelemetry-shim-opentracing/package.json +++ b/packages/opentelemetry-shim-opentracing/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/shim-opentracing", - "version": "1.12.0", + "version": "1.13.0", "description": "OpenTracing to OpenTelemetry shim", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -43,9 +43,9 @@ }, "devDependencies": { "@opentelemetry/api": ">=1.0.0 <1.5.0", - "@opentelemetry/propagator-b3": "1.12.0", - "@opentelemetry/propagator-jaeger": "1.12.0", - "@opentelemetry/sdk-trace-base": "1.12.0", + "@opentelemetry/propagator-b3": "1.13.0", + "@opentelemetry/propagator-jaeger": "1.13.0", + "@opentelemetry/sdk-trace-base": "1.13.0", "@types/mocha": "10.0.0", "@types/node": "18.6.5", "codecov": "3.8.3", @@ -58,8 +58,8 @@ "@opentelemetry/api": ">=1.0.0 <1.5.0" }, "dependencies": { - "@opentelemetry/core": "1.12.0", - "@opentelemetry/semantic-conventions": "1.12.0", + "@opentelemetry/core": "1.13.0", + "@opentelemetry/semantic-conventions": "1.13.0", "opentracing": "^0.14.4" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-shim-opentracing", diff --git a/packages/sdk-metrics/package.json b/packages/sdk-metrics/package.json index e8bcadc32e..db230fd6b5 100644 --- a/packages/sdk-metrics/package.json +++ b/packages/sdk-metrics/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/sdk-metrics", - "version": "1.12.0", + "version": "1.13.0", "description": "OpenTelemetry metrics SDK", "main": "build/src/index.js", "module": "build/esm/index.js", @@ -76,8 +76,8 @@ "@opentelemetry/api": ">=1.3.0 <1.5.0" }, "dependencies": { - "@opentelemetry/core": "1.12.0", - "@opentelemetry/resources": "1.12.0", + "@opentelemetry/core": "1.13.0", + "@opentelemetry/resources": "1.13.0", "lodash.merge": "4.6.2" }, "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/sdk-metrics", diff --git a/packages/template/package.json b/packages/template/package.json index 5385530c31..47b183c625 100644 --- a/packages/template/package.json +++ b/packages/template/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/template", - "version": "1.12.0", + "version": "1.13.0", "private": true, "publishConfig": { "access": "restricted" diff --git a/scripts/peer-api-check.js b/scripts/peer-api-check.js index 27abe9124d..bb5a1bea07 100644 --- a/scripts/peer-api-check.js +++ b/scripts/peer-api-check.js @@ -24,7 +24,7 @@ const appRoot = process.cwd(); const packageJsonUrl = path.resolve(`${appRoot}/package.json`); const pjson = require(packageJsonUrl); -const needCheckPackages = ['@opentelemetry/api', '@opentelemetry/api-logs']; +const needCheckPackages = ['@opentelemetry/api']; function checkPackage(package) { if (pjson.dependencies && pjson.dependencies[package]) diff --git a/scripts/update-ts-configs.js b/scripts/update-ts-configs.js index 283811997b..3c4f43273f 100644 --- a/scripts/update-ts-configs.js +++ b/scripts/update-ts-configs.js @@ -52,6 +52,7 @@ const ignoredLernaProjects = [ 'examples/opentelemetry-web', 'examples/http', 'examples/https', + 'examples/esm-http-ts', ]; let dryRun = false; diff --git a/selenium-tests/package.json b/selenium-tests/package.json index 37aa32a7e7..1bef831b3e 100644 --- a/selenium-tests/package.json +++ b/selenium-tests/package.json @@ -1,6 +1,6 @@ { "name": "@opentelemetry/selenium-tests", - "version": "1.12.0", + "version": "1.13.0", "private": true, "description": "OpenTelemetry Selenium Tests", "main": "index.js", @@ -40,7 +40,7 @@ "babel-loader": "8.2.3", "babel-polyfill": "6.26.0", "browserstack-local": "1.4.8", - "chromedriver": "112.0.0", + "chromedriver": "113.0.0", "dotenv": "16.0.0", "fast-safe-stringify": "2.1.1", "geckodriver": "3.0.1", @@ -56,16 +56,16 @@ "@opentelemetry/api": "^1.0.0" }, "dependencies": { - "@opentelemetry/context-zone-peer-dep": "1.12.0", - "@opentelemetry/core": "1.12.0", - "@opentelemetry/exporter-trace-otlp-http": "0.38.0", - "@opentelemetry/exporter-zipkin": "1.12.0", - "@opentelemetry/instrumentation": "0.38.0", - "@opentelemetry/instrumentation-fetch": "0.38.0", - "@opentelemetry/instrumentation-xml-http-request": "0.38.0", - "@opentelemetry/sdk-metrics": "1.12.0", - "@opentelemetry/sdk-trace-base": "1.12.0", - "@opentelemetry/sdk-trace-web": "1.12.0", + "@opentelemetry/context-zone-peer-dep": "1.13.0", + "@opentelemetry/core": "1.13.0", + "@opentelemetry/exporter-trace-otlp-http": "0.39.1", + "@opentelemetry/exporter-zipkin": "1.13.0", + "@opentelemetry/instrumentation": "0.39.1", + "@opentelemetry/instrumentation-fetch": "0.39.1", + "@opentelemetry/instrumentation-xml-http-request": "0.39.1", + "@opentelemetry/sdk-metrics": "1.13.0", + "@opentelemetry/sdk-trace-base": "1.13.0", + "@opentelemetry/sdk-trace-web": "1.13.0", "zone.js": "0.11.4" } } diff --git a/tsconfig.esm.json b/tsconfig.esm.json index eccabd638e..2fec210b9a 100644 --- a/tsconfig.esm.json +++ b/tsconfig.esm.json @@ -11,6 +11,12 @@ { "path": "experimental/packages/api-logs/tsconfig.esm.json" }, + { + "path": "experimental/packages/exporter-logs-otlp-http/tsconfig.esm.json" + }, + { + "path": "experimental/packages/exporter-logs-otlp-proto/tsconfig.esm.json" + }, { "path": "experimental/packages/exporter-trace-otlp-http/tsconfig.esm.json" }, diff --git a/tsconfig.esnext.json b/tsconfig.esnext.json index 4b0d0df9fa..c5970c9c5c 100644 --- a/tsconfig.esnext.json +++ b/tsconfig.esnext.json @@ -11,6 +11,12 @@ { "path": "experimental/packages/api-logs/tsconfig.esnext.json" }, + { + "path": "experimental/packages/exporter-logs-otlp-http/tsconfig.esnext.json" + }, + { + "path": "experimental/packages/exporter-logs-otlp-proto/tsconfig.esnext.json" + }, { "path": "experimental/packages/exporter-trace-otlp-http/tsconfig.esnext.json" }, diff --git a/tsconfig.json b/tsconfig.json index 4448fed797..acf7329087 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,6 +8,8 @@ "experimental/packages/api-events", "experimental/packages/api-logs", "experimental/packages/exporter-logs-otlp-grpc", + "experimental/packages/exporter-logs-otlp-http", + "experimental/packages/exporter-logs-otlp-proto", "experimental/packages/exporter-trace-otlp-grpc", "experimental/packages/exporter-trace-otlp-http", "experimental/packages/exporter-trace-otlp-proto", @@ -27,6 +29,7 @@ "experimental/packages/otlp-proto-exporter-base", "experimental/packages/otlp-transformer", "experimental/packages/sdk-logs", + "experimental/packages/shim-opencensus", "packages/opentelemetry-context-async-hooks", "packages/opentelemetry-context-zone", "packages/opentelemetry-context-zone-peer-dep", @@ -66,6 +69,12 @@ { "path": "experimental/packages/exporter-logs-otlp-grpc" }, + { + "path": "experimental/packages/exporter-logs-otlp-http" + }, + { + "path": "experimental/packages/exporter-logs-otlp-proto" + }, { "path": "experimental/packages/exporter-trace-otlp-grpc" },