From b1acbe3b51e438e3e88dd621892886336bb4e8b7 Mon Sep 17 00:00:00 2001 From: David Luna Date: Tue, 31 Mar 2026 11:50:31 +0200 Subject: [PATCH 01/20] feat: add otel/browser package --- package-lock.json | 544 +++++++++++++++++++++++++++++- packages/browser/CHANGELOG.md | 5 + packages/browser/LICENSE | 202 +++++++++++ packages/browser/README.md | 22 ++ packages/browser/package.json | 55 +++ packages/browser/src/logs.ts | 54 +++ packages/browser/src/metrics.ts | 48 +++ packages/browser/src/sdk.ts | 50 +++ packages/browser/src/traces.ts | 66 ++++ packages/browser/src/types.ts | 26 ++ packages/browser/src/utils.ts | 21 ++ packages/browser/tsconfig.json | 8 + packages/browser/tsdown.config.ts | 7 + packages/browser/vitest.config.ts | 16 + 14 files changed, 1119 insertions(+), 5 deletions(-) create mode 100644 packages/browser/CHANGELOG.md create mode 100644 packages/browser/LICENSE create mode 100644 packages/browser/README.md create mode 100644 packages/browser/package.json create mode 100644 packages/browser/src/logs.ts create mode 100644 packages/browser/src/metrics.ts create mode 100644 packages/browser/src/sdk.ts create mode 100644 packages/browser/src/traces.ts create mode 100644 packages/browser/src/types.ts create mode 100644 packages/browser/src/utils.ts create mode 100644 packages/browser/tsconfig.json create mode 100644 packages/browser/tsdown.config.ts create mode 100644 packages/browser/vitest.config.ts diff --git a/package-lock.json b/package-lock.json index 8010688..5599b4d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1531,9 +1531,9 @@ } }, "node_modules/@opentelemetry/api": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", - "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", + "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", "license": "Apache-2.0", "engines": { "node": ">=8.0.0" @@ -1551,6 +1551,10 @@ "node": ">=8.0.0" } }, + "node_modules/@opentelemetry/browser": { + "resolved": "packages/browser", + "link": true + }, "node_modules/@opentelemetry/browser-instrumentation": { "resolved": "packages/instrumentation", "link": true @@ -1570,6 +1574,186 @@ "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, + "node_modules/@opentelemetry/exporter-logs-otlp-http": { + "version": "0.214.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.214.0.tgz", + "integrity": "sha512-9qv2Tl/Hq6qc5pJCbzFJnzA0uvlb9DgM70yGJPYf3bA5LlLkRCpcn81i4JbcIH4grlQIWY6A+W7YG0LLvS1BAw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.214.0", + "@opentelemetry/core": "2.6.1", + "@opentelemetry/otlp-exporter-base": "0.214.0", + "@opentelemetry/otlp-transformer": "0.214.0", + "@opentelemetry/sdk-logs": "0.214.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-http/node_modules/@opentelemetry/api-logs": { + "version": "0.214.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.214.0.tgz", + "integrity": "sha512-40lSJeqYO8Uz2Yj7u94/SJWE/wONa7rmMKjI1ZcIjgf3MHNHv1OZUCrCETGuaRF62d5pQD1wKIW+L4lmSMTzZA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-http/node_modules/@opentelemetry/core": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.1.tgz", + "integrity": "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-http/node_modules/@opentelemetry/resources": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.6.1.tgz", + "integrity": "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.6.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-http/node_modules/@opentelemetry/sdk-logs": { + "version": "0.214.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.214.0.tgz", + "integrity": "sha512-zf6acnScjhsaBUU22zXZ/sLWim1dfhUAbGXdMmHmNG3LfBnQ3DKsOCITb2IZwoUsNNMTogqFKBnlIPPftUgGwA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.214.0", + "@opentelemetry/core": "2.6.1", + "@opentelemetry/resources": "2.6.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http": { + "version": "0.214.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.214.0.tgz", + "integrity": "sha512-Tx/59RmjBgkXJ3qnsD04rpDrVWL53LU/czpgLJh+Ab98nAroe91I7vZ3uGN9mxwPS0jsZEnmqmHygVwB2vRMlA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.6.1", + "@opentelemetry/otlp-exporter-base": "0.214.0", + "@opentelemetry/otlp-transformer": "0.214.0", + "@opentelemetry/resources": "2.6.1", + "@opentelemetry/sdk-metrics": "2.6.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/core": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.1.tgz", + "integrity": "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/resources": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.6.1.tgz", + "integrity": "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.6.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-http": { + "version": "0.214.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.214.0.tgz", + "integrity": "sha512-kIN8nTBMgV2hXzV/a20BCFilPZdAIMYYJGSgfMMRm/Xa+07y5hRDS2Vm12A/z8Cdu3Sq++ZvJfElokX2rkgGgw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.6.1", + "@opentelemetry/otlp-exporter-base": "0.214.0", + "@opentelemetry/otlp-transformer": "0.214.0", + "@opentelemetry/resources": "2.6.1", + "@opentelemetry/sdk-trace-base": "2.6.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-http/node_modules/@opentelemetry/core": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.1.tgz", + "integrity": "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-http/node_modules/@opentelemetry/resources": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.6.1.tgz", + "integrity": "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.6.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, "node_modules/@opentelemetry/instrumentation": { "version": "0.213.0", "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.213.0.tgz", @@ -1587,6 +1771,119 @@ "@opentelemetry/api": "^1.3.0" } }, + "node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.214.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.214.0.tgz", + "integrity": "sha512-u1Gdv0/E9wP+apqWf7Wv2npXmgJtxsW2XL0TEv9FZloTZRuMBKmu8cYVXwS4Hm3q/f/3FuCnPTgiwYvIqRSpRg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.6.1", + "@opentelemetry/otlp-transformer": "0.214.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-exporter-base/node_modules/@opentelemetry/core": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.1.tgz", + "integrity": "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer": { + "version": "0.214.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.214.0.tgz", + "integrity": "sha512-DSaYcuBRh6uozfsWN3R8HsN0yDhCuWP7tOFdkUOVaWD1KVJg8m4qiLUsg/tNhTLS9HUYUcwNpwL2eroLtsZZ/w==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.214.0", + "@opentelemetry/core": "2.6.1", + "@opentelemetry/resources": "2.6.1", + "@opentelemetry/sdk-logs": "0.214.0", + "@opentelemetry/sdk-metrics": "2.6.1", + "@opentelemetry/sdk-trace-base": "2.6.1", + "protobufjs": "^7.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/api-logs": { + "version": "0.214.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.214.0.tgz", + "integrity": "sha512-40lSJeqYO8Uz2Yj7u94/SJWE/wONa7rmMKjI1ZcIjgf3MHNHv1OZUCrCETGuaRF62d5pQD1wKIW+L4lmSMTzZA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/core": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.1.tgz", + "integrity": "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/resources": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.6.1.tgz", + "integrity": "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.6.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/sdk-logs": { + "version": "0.214.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.214.0.tgz", + "integrity": "sha512-zf6acnScjhsaBUU22zXZ/sLWim1dfhUAbGXdMmHmNG3LfBnQ3DKsOCITb2IZwoUsNNMTogqFKBnlIPPftUgGwA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.214.0", + "@opentelemetry/core": "2.6.1", + "@opentelemetry/resources": "2.6.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" + } + }, "node_modules/@opentelemetry/resources": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.6.0.tgz", @@ -1621,6 +1918,132 @@ "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, + "node_modules/@opentelemetry/sdk-metrics": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.6.1.tgz", + "integrity": "sha512-9t9hJHX15meBy2NmTJxL+NJfXmnausR2xUDvE19XQce0Qi/GBtDGamU8nS1RMbdgDmhgpm3VaOu2+fiS/SfTpQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.6.1", + "@opentelemetry/resources": "2.6.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-metrics/node_modules/@opentelemetry/core": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.1.tgz", + "integrity": "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-metrics/node_modules/@opentelemetry/resources": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.6.1.tgz", + "integrity": "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.6.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.6.1.tgz", + "integrity": "sha512-r86ut4T1e8vNwB35CqCcKd45yzqH6/6Wzvpk2/cZB8PsPLlZFTvrh8yfOS3CYZYcUmAx4hHTZJ8AO8Dj8nrdhw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.6.1", + "@opentelemetry/resources": "2.6.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base/node_modules/@opentelemetry/core": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.1.tgz", + "integrity": "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base/node_modules/@opentelemetry/resources": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.6.1.tgz", + "integrity": "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.6.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-web": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-web/-/sdk-trace-web-2.6.1.tgz", + "integrity": "sha512-JQevIjBlWGcKBfuwe7tdxGR/75RERsf1OOIvUzPKq86J8qhzkyjnLTTuPNPLRQF1xxEe65W5aI1Uwl6yWUGPQQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.6.1", + "@opentelemetry/sdk-trace-base": "2.6.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-web/node_modules/@opentelemetry/core": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.1.tgz", + "integrity": "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, "node_modules/@opentelemetry/semantic-conventions": { "version": "1.40.0", "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.40.0.tgz", @@ -1647,6 +2070,70 @@ "dev": true, "license": "MIT" }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, "node_modules/@publint/pack": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/@publint/pack/-/pack-0.1.4.tgz", @@ -2645,7 +3132,6 @@ "version": "25.5.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~7.18.0" @@ -2655,7 +3141,6 @@ "version": "7.18.2", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", - "dev": true, "license": "MIT" }, "node_modules/@types/tough-cookie": { @@ -5123,6 +5608,12 @@ "dev": true, "license": "MIT" }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, "node_modules/lru-cache": { "version": "11.2.7", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", @@ -5528,6 +6019,30 @@ "node": ">= 0.8.0" } }, + "node_modules/protobufjs": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/publint": { "version": "0.3.18", "resolved": "https://registry.npmjs.org/publint/-/publint-0.3.18.tgz", @@ -6994,6 +7509,25 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "packages/browser": { + "name": "@opentelemetry/browser", + "version": "0.1.0", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "^0.213.0", + "@opentelemetry/core": "^2.6.0", + "@opentelemetry/exporter-logs-otlp-http": "^0.214.0", + "@opentelemetry/exporter-metrics-otlp-http": "^0.214.0", + "@opentelemetry/exporter-trace-otlp-http": "^0.214.0", + "@opentelemetry/resources": "^2.6.0", + "@opentelemetry/sdk-metrics": "^2.6.1", + "@opentelemetry/sdk-trace-base": "^2.6.1", + "@opentelemetry/sdk-trace-web": "^2.6.1" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0" + } + }, "packages/instrumentation": { "name": "@opentelemetry/browser-instrumentation", "version": "0.2.0", diff --git a/packages/browser/CHANGELOG.md b/packages/browser/CHANGELOG.md new file mode 100644 index 0000000..d43cac1 --- /dev/null +++ b/packages/browser/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +### Features + +### Bug Fixes diff --git a/packages/browser/LICENSE b/packages/browser/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/packages/browser/LICENSE @@ -0,0 +1,202 @@ + + 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/packages/browser/README.md b/packages/browser/README.md new file mode 100644 index 0000000..6f4b112 --- /dev/null +++ b/packages/browser/README.md @@ -0,0 +1,22 @@ +# @opentelemetry/browser + +[![NPM Published Version][npm-img]][npm-url] +[![Apache License][license-image]][license-image] + + +## Installation + +```bash +npm install @opentelemetry/browser +``` + + +## License + +Apache 2.0 - See [LICENSE][license-url] for more information. + +[discussions-url]: https://github.com/open-telemetry/opentelemetry-browser/discussions/landing +[license-url]: https://github.com/open-telemetry/opentelemetry-browser/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/browser-instrumentation +[npm-img]: https://badge.fury.io/js/%40opentelemetry%2Fbrowser-instrumentation.svg diff --git a/packages/browser/package.json b/packages/browser/package.json new file mode 100644 index 0000000..069b5a7 --- /dev/null +++ b/packages/browser/package.json @@ -0,0 +1,55 @@ +{ + "name": "@opentelemetry/browser", + "version": "0.1.0", + "description": "OpenTelemetry browser main package.", + "keywords": [ + "opentelemetry", + "browser", + "web", + "sdk" + ], + "homepage": "https://github.com/open-telemetry/opentelemetry-browser", + "bugs": "https://github.com/open-telemetry/opentelemetry-browser/issues", + "license": "Apache-2.0", + "author": "OpenTelemetry Authors", + "repository": { + "type": "git", + "url": "git+https://github.com/open-telemetry/opentelemetry-browser.git", + "directory": "packages/browser" + }, + "type": "module", + "exports": { + "./logs": "./dist/logs.js", + "./metrics": "./dist/metrics.js", + "./traces": "./dist/traces.js", + "./sdk": "./dist/sdk.js" + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsdown", + "watch": "tsdown --watch --no-clean", + "check-types": "tsc", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest --coverage" + }, + "dependencies": { + "@opentelemetry/api-logs": "^0.213.0", + "@opentelemetry/core": "^2.6.0", + "@opentelemetry/exporter-logs-otlp-http": "^0.214.0", + "@opentelemetry/exporter-metrics-otlp-http": "^0.214.0", + "@opentelemetry/exporter-trace-otlp-http": "^0.214.0", + "@opentelemetry/resources": "^2.6.0", + "@opentelemetry/sdk-metrics": "^2.6.1", + "@opentelemetry/sdk-trace-base": "^2.6.1", + "@opentelemetry/sdk-trace-web": "^2.6.1" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/browser/src/logs.ts b/packages/browser/src/logs.ts new file mode 100644 index 0000000..fd90643 --- /dev/null +++ b/packages/browser/src/logs.ts @@ -0,0 +1,54 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { logs } from '@opentelemetry/api-logs'; +import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http'; +import { + BatchLogRecordProcessor, + LoggerProvider, +} from '@opentelemetry/sdk-logs'; + +import type { GlobalConfig, SignalSdk } from './types.ts'; +import { getExportUrl } from './utils.ts'; + +export interface LogsSdkConfig { + // Export + otlpLogsEndpoint?: string; + otlpLogsHeaders?: Record; + // Limits + logRecordAttrLenghtLimit?: number; + logRecordAttrCountLimit?: number; +} + +export class LogsSdk implements SignalSdk { + private _loggerProvider: LoggerProvider | undefined; + + start(config: GlobalConfig & LogsSdkConfig) { + const logsEndpoint = getExportUrl( + config.otlpLogsEndpoint, + config.otlpEndpoint, + '/v1/logs', + ); + + const logsProcessor = new BatchLogRecordProcessor( + new OTLPLogExporter({ + url: logsEndpoint, + headers: config.otlpLogsHeaders ?? config.otlpHeaders, + }), + ); + this._loggerProvider = new LoggerProvider({ + // TODO: should resource bubble to SDK config??? + // resource: config.resource, + processors: [logsProcessor], + }); + logs.setGlobalLoggerProvider(this._loggerProvider); + } + shutdown() { + if (this._loggerProvider) { + return this._loggerProvider.shutdown(); + } + return Promise.resolve(); + } +} diff --git a/packages/browser/src/metrics.ts b/packages/browser/src/metrics.ts new file mode 100644 index 0000000..03aca64 --- /dev/null +++ b/packages/browser/src/metrics.ts @@ -0,0 +1,48 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { metrics } from '@opentelemetry/api'; +import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http'; +import { + MeterProvider, + PeriodicExportingMetricReader, +} from '@opentelemetry/sdk-metrics'; + +import type { GlobalConfig, SignalSdk } from './types.ts'; +import { getExportUrl } from './utils.ts'; + +export interface MetricsSdkConfig { + otlpMetricsEndpoint?: string; + otlpMetricsHeaders?: Record; +} + +export class MetricsSdk implements SignalSdk { + private _meterProvider: MeterProvider | undefined; + + start(config: GlobalConfig & MetricsSdkConfig) { + const metricsEndpoint = getExportUrl( + config.otlpMetricsEndpoint, + config.otlpEndpoint, + '/v1/metrics', + ); + const metricsReader = new PeriodicExportingMetricReader({ + exporter: new OTLPMetricExporter({ + url: metricsEndpoint, + headers: config.otlpMetricsHeaders ?? config.otlpHeaders, + }), + }); + this._meterProvider = new MeterProvider({ + // resource: config.resource, + readers: [metricsReader], + }); + metrics.setGlobalMeterProvider(this._meterProvider); + } + shutdown() { + if (this._meterProvider) { + return this._meterProvider.shutdown(); + } + return Promise.resolve(); + } +} diff --git a/packages/browser/src/sdk.ts b/packages/browser/src/sdk.ts new file mode 100644 index 0000000..667471a --- /dev/null +++ b/packages/browser/src/sdk.ts @@ -0,0 +1,50 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { SDK_INFO } from '@opentelemetry/core'; +import { + defaultResource, + resourceFromAttributes, +} from '@opentelemetry/resources'; + +import type { GlobalConfig, SignalSdk } from './types.ts'; + +export class WebSdkBuilder { + private _signals: Array> = []; + + withSignal(signal: SignalSdk): WebSdkBuilder { + this._signals.push(signal); + return this as WebSdkBuilder; + } + build(config: T) { + // TODO: adjust some configuration before passing it to signals like + // - resource + // - ??? + + const signals = this._signals; + const conf = config as GlobalConfig; + + // Set resource + conf.resource = (conf.resource || defaultResource()).merge( + resourceFromAttributes({ ...SDK_INFO }), + ); + if (typeof conf.serviceName === 'string') { + conf.resource = conf.resource.merge( + resourceFromAttributes({ 'service.name': conf.serviceName }), + ); + } + + return { + start() { + for (const s of signals) { + s.start(conf); + } + }, + shutdown() { + return Promise.all(signals.map((s) => s.shutdown())); + }, + }; + } +} diff --git a/packages/browser/src/traces.ts b/packages/browser/src/traces.ts new file mode 100644 index 0000000..5af01e2 --- /dev/null +++ b/packages/browser/src/traces.ts @@ -0,0 +1,66 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; +import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base'; +import { WebTracerProvider } from '@opentelemetry/sdk-trace-web'; + +import type { GlobalConfig, SignalSdk } from './types.ts'; +import { getExportUrl } from './utils.ts'; + +export interface TracesSdkConfig { + // Processor + // TODO: impklement handling these + bspScheduleDelay?: number; + bspExportTimeout?: number; + bspMaxQueueSize?: number; + bspMaxExportBatchSize?: number; + // Export + otlpTracesEndpoint?: string; + otlpTracesHeaders?: Record; + // Limits + spanAttrLengthLimit?: number; + spanAttrCountLimit?: number; +} + +export class TracesSdk implements SignalSdk { + private _tracerProvider: WebTracerProvider | undefined; + + start(config: GlobalConfig & TracesSdkConfig) { + const tracesEndpoint = getExportUrl( + config.otlpTracesEndpoint, + config.otlpEndpoint, + '/v1/traces', + ); + + const spanProcessor = new BatchSpanProcessor( + new OTLPTraceExporter({ + url: tracesEndpoint, + headers: config.otlpTracesHeaders ?? config.otlpHeaders, + }), + ); + + this._tracerProvider = new WebTracerProvider({ + // resource: config.resource, + // sampler: new TraceIdRatioBasedSampler( + // typeof config.sampleRate === "number" ? config.sampleRate : 1, + // ), + spanLimits: { + attributeCountLimit: config.spanAttrCountLimit || config.attrCountLimit, + attributeValueLengthLimit: + config.spanAttrLengthLimit || config.attrLenghtLimit, + }, + spanProcessors: [spanProcessor], + }); + // TODO: allow context manager and propagatros??? + this._tracerProvider.register(); + } + shutdown() { + if (this._tracerProvider) { + return this._tracerProvider.shutdown(); + } + return Promise.resolve(); + } +} diff --git a/packages/browser/src/types.ts b/packages/browser/src/types.ts new file mode 100644 index 0000000..883cef1 --- /dev/null +++ b/packages/browser/src/types.ts @@ -0,0 +1,26 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { DiagLogLevel } from '@opentelemetry/api'; +import type { Resource } from '@opentelemetry/resources'; + +export interface GlobalConfig { + disabled?: boolean; + logLevel?: DiagLogLevel; + // Resource & ·Entities related + serviceName?: string; + resource?: Resource; + // Export + otlpEndpoint?: string; + otlpHeaders?: Record; + // Limits + attrLenghtLimit?: number; + attrCountLimit?: number; +} + +export interface SignalSdk { + start(config: GlobalConfig & SignalConfig): void; + shutdown(): Promise; +} diff --git a/packages/browser/src/utils.ts b/packages/browser/src/utils.ts new file mode 100644 index 0000000..b1f1b68 --- /dev/null +++ b/packages/browser/src/utils.ts @@ -0,0 +1,21 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +const DEFAULT_OTLP_ENDOINT = 'http://localhost:4318'; + +export function getExportUrl( + signalUrl: string | undefined, + genericUrl: string | undefined, + signalPath: string, +) { + if (typeof signalUrl === 'string') { + return signalUrl; + } + + const baseUrl = + typeof genericUrl === 'string' ? genericUrl : DEFAULT_OTLP_ENDOINT; + const url = new URL(signalPath, baseUrl); + return url.href; +} diff --git a/packages/browser/tsconfig.json b/packages/browser/tsconfig.json new file mode 100644 index 0000000..0ff2110 --- /dev/null +++ b/packages/browser/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "./src" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/browser/tsdown.config.ts b/packages/browser/tsdown.config.ts new file mode 100644 index 0000000..0ae211b --- /dev/null +++ b/packages/browser/tsdown.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'tsdown'; +import baseConfig from '../../tsdown.config.ts'; + +export default defineConfig({ + ...baseConfig, + entry: ['src/*.ts', '!src/{test-utils,utils}/index.ts'], +}); diff --git a/packages/browser/vitest.config.ts b/packages/browser/vitest.config.ts new file mode 100644 index 0000000..9f39f6f --- /dev/null +++ b/packages/browser/vitest.config.ts @@ -0,0 +1,16 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + projects: [ + { + test: { + name: 'unit', + environment: 'jsdom', + include: ['src/**/*.test.ts'], + browser: { enabled: false }, + }, + }, + ], + }, +}); From dafd5ff8eca26fc67e05c39dc7e01aac7f12cf38 Mon Sep 17 00:00:00 2001 From: David Luna Date: Tue, 31 Mar 2026 12:32:24 +0200 Subject: [PATCH 02/20] chore: pass resource dow to signals --- packages/browser/src/logs.ts | 8 ++++---- packages/browser/src/metrics.ts | 2 +- packages/browser/src/sdk.ts | 2 +- packages/browser/src/traces.ts | 12 ++++-------- 4 files changed, 10 insertions(+), 14 deletions(-) diff --git a/packages/browser/src/logs.ts b/packages/browser/src/logs.ts index fd90643..82d3bf2 100644 --- a/packages/browser/src/logs.ts +++ b/packages/browser/src/logs.ts @@ -5,6 +5,7 @@ import { logs } from '@opentelemetry/api-logs'; import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http'; +import type { LogRecordLimits } from '@opentelemetry/sdk-logs'; import { BatchLogRecordProcessor, LoggerProvider, @@ -18,8 +19,7 @@ export interface LogsSdkConfig { otlpLogsEndpoint?: string; otlpLogsHeaders?: Record; // Limits - logRecordAttrLenghtLimit?: number; - logRecordAttrCountLimit?: number; + logRecordLimits?: LogRecordLimits; } export class LogsSdk implements SignalSdk { @@ -39,8 +39,8 @@ export class LogsSdk implements SignalSdk { }), ); this._loggerProvider = new LoggerProvider({ - // TODO: should resource bubble to SDK config??? - // resource: config.resource, + resource: config.resource, + logRecordLimits: config.logRecordLimits, processors: [logsProcessor], }); logs.setGlobalLoggerProvider(this._loggerProvider); diff --git a/packages/browser/src/metrics.ts b/packages/browser/src/metrics.ts index 03aca64..3d1d65e 100644 --- a/packages/browser/src/metrics.ts +++ b/packages/browser/src/metrics.ts @@ -34,7 +34,7 @@ export class MetricsSdk implements SignalSdk { }), }); this._meterProvider = new MeterProvider({ - // resource: config.resource, + resource: config.resource, readers: [metricsReader], }); metrics.setGlobalMeterProvider(this._meterProvider); diff --git a/packages/browser/src/sdk.ts b/packages/browser/src/sdk.ts index 667471a..5a98dcf 100644 --- a/packages/browser/src/sdk.ts +++ b/packages/browser/src/sdk.ts @@ -14,7 +14,7 @@ import type { GlobalConfig, SignalSdk } from './types.ts'; export class WebSdkBuilder { private _signals: Array> = []; - withSignal(signal: SignalSdk): WebSdkBuilder { + withSignalSdk(signal: SignalSdk): WebSdkBuilder { this._signals.push(signal); return this as WebSdkBuilder; } diff --git a/packages/browser/src/traces.ts b/packages/browser/src/traces.ts index 5af01e2..73a4d47 100644 --- a/packages/browser/src/traces.ts +++ b/packages/browser/src/traces.ts @@ -4,6 +4,7 @@ */ import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; +import type { SpanLimits } from '@opentelemetry/sdk-trace-base'; import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base'; import { WebTracerProvider } from '@opentelemetry/sdk-trace-web'; @@ -21,8 +22,7 @@ export interface TracesSdkConfig { otlpTracesEndpoint?: string; otlpTracesHeaders?: Record; // Limits - spanAttrLengthLimit?: number; - spanAttrCountLimit?: number; + spanLimits?: SpanLimits; } export class TracesSdk implements SignalSdk { @@ -43,15 +43,11 @@ export class TracesSdk implements SignalSdk { ); this._tracerProvider = new WebTracerProvider({ - // resource: config.resource, // sampler: new TraceIdRatioBasedSampler( // typeof config.sampleRate === "number" ? config.sampleRate : 1, // ), - spanLimits: { - attributeCountLimit: config.spanAttrCountLimit || config.attrCountLimit, - attributeValueLengthLimit: - config.spanAttrLengthLimit || config.attrLenghtLimit, - }, + resource: config.resource, + spanLimits: config.spanLimits, spanProcessors: [spanProcessor], }); // TODO: allow context manager and propagatros??? From 44ed82561e475b57e001d518df2fb44f5d7a6091 Mon Sep 17 00:00:00 2001 From: David Luna Date: Tue, 31 Mar 2026 13:06:13 +0200 Subject: [PATCH 03/20] chore: add logs sdk test file --- package-lock.json | 1 + packages/browser/package.json | 1 + packages/browser/src/logs.test.ts | 36 +++++++++++++++++++++++++++++++ packages/browser/src/logs.ts | 12 +++++------ packages/browser/src/metrics.ts | 10 ++++----- packages/browser/src/traces.ts | 14 ++++++------ packages/browser/src/types.ts | 2 +- 7 files changed, 57 insertions(+), 19 deletions(-) create mode 100644 packages/browser/src/logs.test.ts diff --git a/package-lock.json b/package-lock.json index 5599b4d..3a91293 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7520,6 +7520,7 @@ "@opentelemetry/exporter-metrics-otlp-http": "^0.214.0", "@opentelemetry/exporter-trace-otlp-http": "^0.214.0", "@opentelemetry/resources": "^2.6.0", + "@opentelemetry/sdk-logs": "^0.213.0", "@opentelemetry/sdk-metrics": "^2.6.1", "@opentelemetry/sdk-trace-base": "^2.6.1", "@opentelemetry/sdk-trace-web": "^2.6.1" diff --git a/packages/browser/package.json b/packages/browser/package.json index 069b5a7..fd0b6ca 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -42,6 +42,7 @@ "@opentelemetry/exporter-metrics-otlp-http": "^0.214.0", "@opentelemetry/exporter-trace-otlp-http": "^0.214.0", "@opentelemetry/resources": "^2.6.0", + "@opentelemetry/sdk-logs": "^0.213.0", "@opentelemetry/sdk-metrics": "^2.6.1", "@opentelemetry/sdk-trace-base": "^2.6.1", "@opentelemetry/sdk-trace-web": "^2.6.1" diff --git a/packages/browser/src/logs.test.ts b/packages/browser/src/logs.test.ts new file mode 100644 index 0000000..9ec4f13 --- /dev/null +++ b/packages/browser/src/logs.test.ts @@ -0,0 +1,36 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { logs } from '@opentelemetry/api-logs'; +import { + BatchLogRecordProcessor, + LoggerProvider, +} from '@opentelemetry/sdk-logs'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { LogsSdk } from './logs.ts'; + +describe('LogsSdk', () => { + const setGlobalLoggerProviderSpy = vi.spyOn(logs, 'setGlobalLoggerProvider'); + let logsSdk: LogsSdk; + + beforeEach(() => { + setGlobalLoggerProviderSpy.mockClear(); + }); + + it('should register a LoggerProvider with a BatchLogRecordProcessor', () => { + // Act + logsSdk = new LogsSdk(); + logsSdk.start(); + + // Assert + const loggerProvider = setGlobalLoggerProviderSpy.mock.lastCall?.[0]; + expect(setGlobalLoggerProviderSpy).callCount(1); + expect(loggerProvider instanceof LoggerProvider); + // @ts-expect-error -- accessing private properties + const processors = loggerProvider['_sharedState']['processors']; + expect(processors.length).toBe(1); + expect(processors[0]).toBeInstanceOf(BatchLogRecordProcessor); + }); +}); diff --git a/packages/browser/src/logs.ts b/packages/browser/src/logs.ts index 82d3bf2..09ae1df 100644 --- a/packages/browser/src/logs.ts +++ b/packages/browser/src/logs.ts @@ -25,22 +25,22 @@ export interface LogsSdkConfig { export class LogsSdk implements SignalSdk { private _loggerProvider: LoggerProvider | undefined; - start(config: GlobalConfig & LogsSdkConfig) { + start(config?: GlobalConfig & LogsSdkConfig) { const logsEndpoint = getExportUrl( - config.otlpLogsEndpoint, - config.otlpEndpoint, + config?.otlpLogsEndpoint, + config?.otlpEndpoint, '/v1/logs', ); const logsProcessor = new BatchLogRecordProcessor( new OTLPLogExporter({ url: logsEndpoint, - headers: config.otlpLogsHeaders ?? config.otlpHeaders, + headers: config?.otlpLogsHeaders ?? config?.otlpHeaders, }), ); this._loggerProvider = new LoggerProvider({ - resource: config.resource, - logRecordLimits: config.logRecordLimits, + resource: config?.resource, + logRecordLimits: config?.logRecordLimits, processors: [logsProcessor], }); logs.setGlobalLoggerProvider(this._loggerProvider); diff --git a/packages/browser/src/metrics.ts b/packages/browser/src/metrics.ts index 3d1d65e..a23e58e 100644 --- a/packages/browser/src/metrics.ts +++ b/packages/browser/src/metrics.ts @@ -21,20 +21,20 @@ export interface MetricsSdkConfig { export class MetricsSdk implements SignalSdk { private _meterProvider: MeterProvider | undefined; - start(config: GlobalConfig & MetricsSdkConfig) { + start(config?: GlobalConfig & MetricsSdkConfig) { const metricsEndpoint = getExportUrl( - config.otlpMetricsEndpoint, - config.otlpEndpoint, + config?.otlpMetricsEndpoint, + config?.otlpEndpoint, '/v1/metrics', ); const metricsReader = new PeriodicExportingMetricReader({ exporter: new OTLPMetricExporter({ url: metricsEndpoint, - headers: config.otlpMetricsHeaders ?? config.otlpHeaders, + headers: config?.otlpMetricsHeaders ?? config?.otlpHeaders, }), }); this._meterProvider = new MeterProvider({ - resource: config.resource, + resource: config?.resource, readers: [metricsReader], }); metrics.setGlobalMeterProvider(this._meterProvider); diff --git a/packages/browser/src/traces.ts b/packages/browser/src/traces.ts index 73a4d47..2551d41 100644 --- a/packages/browser/src/traces.ts +++ b/packages/browser/src/traces.ts @@ -28,26 +28,26 @@ export interface TracesSdkConfig { export class TracesSdk implements SignalSdk { private _tracerProvider: WebTracerProvider | undefined; - start(config: GlobalConfig & TracesSdkConfig) { + start(config?: GlobalConfig & TracesSdkConfig) { const tracesEndpoint = getExportUrl( - config.otlpTracesEndpoint, - config.otlpEndpoint, + config?.otlpTracesEndpoint, + config?.otlpEndpoint, '/v1/traces', ); const spanProcessor = new BatchSpanProcessor( new OTLPTraceExporter({ url: tracesEndpoint, - headers: config.otlpTracesHeaders ?? config.otlpHeaders, + headers: config?.otlpTracesHeaders ?? config?.otlpHeaders, }), ); this._tracerProvider = new WebTracerProvider({ // sampler: new TraceIdRatioBasedSampler( - // typeof config.sampleRate === "number" ? config.sampleRate : 1, + // typeof config?.sampleRate === "number" ? config?.sampleRate : 1, // ), - resource: config.resource, - spanLimits: config.spanLimits, + resource: config?.resource, + spanLimits: config?.spanLimits, spanProcessors: [spanProcessor], }); // TODO: allow context manager and propagatros??? diff --git a/packages/browser/src/types.ts b/packages/browser/src/types.ts index 883cef1..77e0f65 100644 --- a/packages/browser/src/types.ts +++ b/packages/browser/src/types.ts @@ -21,6 +21,6 @@ export interface GlobalConfig { } export interface SignalSdk { - start(config: GlobalConfig & SignalConfig): void; + start(config?: GlobalConfig & SignalConfig): void; shutdown(): Promise; } From e5f01e0d067f95f3b4d59d9da32d6b47bc3fee5b Mon Sep 17 00:00:00 2001 From: David Luna Date: Tue, 31 Mar 2026 17:27:57 +0200 Subject: [PATCH 04/20] chore: update logs tests --- packages/browser/src/logs.test.ts | 117 +++++++++++++++++++++++++++--- packages/browser/src/logs.ts | 11 +++ packages/browser/vitest.config.ts | 11 ++- 3 files changed, 126 insertions(+), 13 deletions(-) diff --git a/packages/browser/src/logs.test.ts b/packages/browser/src/logs.test.ts index 9ec4f13..dbd4022 100644 --- a/packages/browser/src/logs.test.ts +++ b/packages/browser/src/logs.test.ts @@ -3,34 +3,131 @@ * SPDX-License-Identifier: Apache-2.0 */ +import type { LoggerProvider } from '@opentelemetry/api-logs'; import { logs } from '@opentelemetry/api-logs'; -import { - BatchLogRecordProcessor, - LoggerProvider, -} from '@opentelemetry/sdk-logs'; -import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { BatchLogRecordProcessor } from '@opentelemetry/sdk-logs'; +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest'; + import { LogsSdk } from './logs.ts'; +const BLRP_SCHEDULE_DELAY = 10; + describe('LogsSdk', () => { - const setGlobalLoggerProviderSpy = vi.spyOn(logs, 'setGlobalLoggerProvider'); + const response = { ok: true, json: async () => ({ ok: true }) } as Response; + const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(response); + const setGlobalLoggerProviderSpy = vi + .spyOn(logs, 'setGlobalLoggerProvider') + .mockImplementation((p) => { + loggerProvider = p; + return p; + }); + const getLoggerProviderSpy = vi + .spyOn(logs, 'getLoggerProvider') + .mockImplementation(() => loggerProvider); + let loggerProvider: LoggerProvider; let logsSdk: LogsSdk; + // NOTE: we mock the registration of the logger provider because + // the logs API only allow to register once. With the mock we can use + // a dedicated provider for the test + afterAll(() => { + setGlobalLoggerProviderSpy.mockRestore(); + getLoggerProviderSpy.mockRestore(); + fetchSpy.mockRestore(); + }); beforeEach(() => { setGlobalLoggerProviderSpy.mockClear(); + getLoggerProviderSpy.mockClear(); + fetchSpy.mockClear(); }); - it('should register a LoggerProvider with a BatchLogRecordProcessor', () => { + it('should register a LoggerProvider with a BatchLogRecordProcessor', async () => { // Act logsSdk = new LogsSdk(); logsSdk.start(); // Assert - const loggerProvider = setGlobalLoggerProviderSpy.mock.lastCall?.[0]; expect(setGlobalLoggerProviderSpy).callCount(1); - expect(loggerProvider instanceof LoggerProvider); // @ts-expect-error -- accessing private properties - const processors = loggerProvider['_sharedState']['processors']; + const processors = logs.getLoggerProvider()['_sharedState']['processors']; expect(processors.length).toBe(1); expect(processors[0]).toBeInstanceOf(BatchLogRecordProcessor); }); + + it('should use the default configuration for exporters', async () => { + // Act + logsSdk = new LogsSdk(); + logsSdk.start({ + // NOTE: we set a short delay to speed up tests and avoid test timeouts + blrpScheduleDelay: BLRP_SCHEDULE_DELAY, + }); + logs.getLogger('logs-sdk-test').emit({ eventName: 'test' }); + await new Promise((r) => setTimeout(r, BLRP_SCHEDULE_DELAY + 5)); + + // Assert + expect(fetchSpy).toHaveBeenCalledOnce(); + expect(fetchSpy.mock.lastCall?.[0]).toEqual( + 'http://localhost:4318/v1/logs', + ); + expect(fetchSpy.mock.lastCall?.[1]).containSubset({ + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }); + }); + + it('should accept signal generic OTLP endpoint and headers', async () => { + // Act + logsSdk = new LogsSdk(); + logsSdk.start({ + // NOTE: we set a short delay to speed up tests and avoid test timeouts + blrpScheduleDelay: BLRP_SCHEDULE_DELAY, + otlpEndpoint: 'http://otlp-endpoint:4318', + otlpHeaders: { foo: 'bar' }, + }); + logs.getLogger('logs-sdk-test').emit({ eventName: 'test' }); + await new Promise((r) => setTimeout(r, BLRP_SCHEDULE_DELAY + 5)); + + // Assert + expect(setGlobalLoggerProviderSpy).callCount(1); + expect(fetchSpy).toHaveBeenCalledOnce(); + expect(fetchSpy.mock.lastCall?.[0]).toEqual( + 'http://otlp-endpoint:4318/v1/logs', + ); + expect(fetchSpy.mock.lastCall?.[1]).containSubset({ + method: 'POST', + headers: { + 'Content-Type': 'application/json', + foo: 'bar', + }, + }); + }); + + it('should accept signal specific OTLP endpoint and headers', async () => { + // Act + logsSdk = new LogsSdk(); + logsSdk.start({ + // NOTE: we set a short delay to speed up tests and avoid test timeouts + blrpScheduleDelay: BLRP_SCHEDULE_DELAY, + otlpLogsEndpoint: 'http://otlp-signal-endpoint:4318/v1/logs', + otlpLogsHeaders: { bar: 'baz' }, + }); + logs.getLogger('logs-sdk-test').emit({ eventName: 'test' }); + await new Promise((r) => setTimeout(r, BLRP_SCHEDULE_DELAY + 5)); + + // Assert + expect(setGlobalLoggerProviderSpy).callCount(1); + expect(fetchSpy).toHaveBeenCalledOnce(); + expect(fetchSpy.mock.lastCall?.[0]).toEqual( + 'http://otlp-signal-endpoint:4318/v1/logs', + ); + expect(fetchSpy.mock.lastCall?.[1]).containSubset({ + method: 'POST', + headers: { + 'Content-Type': 'application/json', + bar: 'baz', + }, + }); + }); }); diff --git a/packages/browser/src/logs.ts b/packages/browser/src/logs.ts index 09ae1df..39fb305 100644 --- a/packages/browser/src/logs.ts +++ b/packages/browser/src/logs.ts @@ -15,6 +15,11 @@ import type { GlobalConfig, SignalSdk } from './types.ts'; import { getExportUrl } from './utils.ts'; export interface LogsSdkConfig { + // Processor + blrpScheduleDelay?: number; + blrpExportTimeout?: number; + blrpMaxQueueSize?: number; + blrpMaxExportBatchSize?: number; // Export otlpLogsEndpoint?: string; otlpLogsHeaders?: Record; @@ -37,6 +42,12 @@ export class LogsSdk implements SignalSdk { url: logsEndpoint, headers: config?.otlpLogsHeaders ?? config?.otlpHeaders, }), + { + scheduledDelayMillis: config?.blrpScheduleDelay, + exportTimeoutMillis: config?.blrpExportTimeout, + maxExportBatchSize: config?.blrpMaxExportBatchSize, + maxQueueSize: config?.blrpMaxQueueSize, + }, ); this._loggerProvider = new LoggerProvider({ resource: config?.resource, diff --git a/packages/browser/vitest.config.ts b/packages/browser/vitest.config.ts index 9f39f6f..4c442c6 100644 --- a/packages/browser/vitest.config.ts +++ b/packages/browser/vitest.config.ts @@ -1,3 +1,4 @@ +import { playwright } from '@vitest/browser-playwright'; import { defineConfig } from 'vitest/config'; export default defineConfig({ @@ -5,10 +6,14 @@ export default defineConfig({ projects: [ { test: { - name: 'unit', - environment: 'jsdom', + name: 'browser', include: ['src/**/*.test.ts'], - browser: { enabled: false }, + browser: { + provider: playwright(), + enabled: true, + headless: true, + instances: [{ browser: 'chromium' }], + }, }, }, ], From 09f31c6826a1326e1c09541cfb147885931ad5f7 Mon Sep 17 00:00:00 2001 From: David Luna Date: Wed, 8 Apr 2026 12:54:17 +0200 Subject: [PATCH 05/20] chore: refactor to use fnctions and not classes --- packages/browser/src/logs.test.ts | 46 +++--------- packages/browser/src/logs.ts | 80 +++++++++------------ packages/browser/src/metrics.ts | 53 ++++++-------- packages/browser/src/sdk.ts | 112 ++++++++++++++++++++---------- packages/browser/src/traces.ts | 73 +++++++------------ packages/browser/src/types.ts | 46 +++++++++++- packages/browser/src/utils.ts | 21 ------ 7 files changed, 204 insertions(+), 227 deletions(-) delete mode 100644 packages/browser/src/utils.ts diff --git a/packages/browser/src/logs.test.ts b/packages/browser/src/logs.test.ts index dbd4022..093312e 100644 --- a/packages/browser/src/logs.test.ts +++ b/packages/browser/src/logs.test.ts @@ -8,11 +8,12 @@ import { logs } from '@opentelemetry/api-logs'; import { BatchLogRecordProcessor } from '@opentelemetry/sdk-logs'; import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest'; -import { LogsSdk } from './logs.ts'; +import { startLogsSdk } from './logs.ts'; +import type { WebSdk } from './types.ts'; const BLRP_SCHEDULE_DELAY = 10; -describe('LogsSdk', () => { +describe('startLogsSdk', () => { const response = { ok: true, json: async () => ({ ok: true }) } as Response; const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(response); const setGlobalLoggerProviderSpy = vi @@ -25,7 +26,7 @@ describe('LogsSdk', () => { .spyOn(logs, 'getLoggerProvider') .mockImplementation(() => loggerProvider); let loggerProvider: LoggerProvider; - let logsSdk: LogsSdk; + let logsSdk: WebSdk; // NOTE: we mock the registration of the logger provider because // the logs API only allow to register once. With the mock we can use @@ -35,16 +36,16 @@ describe('LogsSdk', () => { getLoggerProviderSpy.mockRestore(); fetchSpy.mockRestore(); }); - beforeEach(() => { + beforeEach(async () => { setGlobalLoggerProviderSpy.mockClear(); getLoggerProviderSpy.mockClear(); fetchSpy.mockClear(); + await logsSdk?.shutdown(); }); it('should register a LoggerProvider with a BatchLogRecordProcessor', async () => { // Act - logsSdk = new LogsSdk(); - logsSdk.start(); + logsSdk = startLogsSdk(); // Assert expect(setGlobalLoggerProviderSpy).callCount(1); @@ -56,8 +57,7 @@ describe('LogsSdk', () => { it('should use the default configuration for exporters', async () => { // Act - logsSdk = new LogsSdk(); - logsSdk.start({ + logsSdk = startLogsSdk({ // NOTE: we set a short delay to speed up tests and avoid test timeouts blrpScheduleDelay: BLRP_SCHEDULE_DELAY, }); @@ -77,37 +77,9 @@ describe('LogsSdk', () => { }); }); - it('should accept signal generic OTLP endpoint and headers', async () => { - // Act - logsSdk = new LogsSdk(); - logsSdk.start({ - // NOTE: we set a short delay to speed up tests and avoid test timeouts - blrpScheduleDelay: BLRP_SCHEDULE_DELAY, - otlpEndpoint: 'http://otlp-endpoint:4318', - otlpHeaders: { foo: 'bar' }, - }); - logs.getLogger('logs-sdk-test').emit({ eventName: 'test' }); - await new Promise((r) => setTimeout(r, BLRP_SCHEDULE_DELAY + 5)); - - // Assert - expect(setGlobalLoggerProviderSpy).callCount(1); - expect(fetchSpy).toHaveBeenCalledOnce(); - expect(fetchSpy.mock.lastCall?.[0]).toEqual( - 'http://otlp-endpoint:4318/v1/logs', - ); - expect(fetchSpy.mock.lastCall?.[1]).containSubset({ - method: 'POST', - headers: { - 'Content-Type': 'application/json', - foo: 'bar', - }, - }); - }); - it('should accept signal specific OTLP endpoint and headers', async () => { // Act - logsSdk = new LogsSdk(); - logsSdk.start({ + logsSdk = startLogsSdk({ // NOTE: we set a short delay to speed up tests and avoid test timeouts blrpScheduleDelay: BLRP_SCHEDULE_DELAY, otlpLogsEndpoint: 'http://otlp-signal-endpoint:4318/v1/logs', diff --git a/packages/browser/src/logs.ts b/packages/browser/src/logs.ts index 39fb305..82755d4 100644 --- a/packages/browser/src/logs.ts +++ b/packages/browser/src/logs.ts @@ -5,61 +5,43 @@ import { logs } from '@opentelemetry/api-logs'; import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http'; -import type { LogRecordLimits } from '@opentelemetry/sdk-logs'; import { BatchLogRecordProcessor, LoggerProvider, } from '@opentelemetry/sdk-logs'; -import type { GlobalConfig, SignalSdk } from './types.ts'; -import { getExportUrl } from './utils.ts'; +import type { LogsConfig, WebSdk } from './types.ts'; -export interface LogsSdkConfig { - // Processor - blrpScheduleDelay?: number; - blrpExportTimeout?: number; - blrpMaxQueueSize?: number; - blrpMaxExportBatchSize?: number; - // Export - otlpLogsEndpoint?: string; - otlpLogsHeaders?: Record; - // Limits - logRecordLimits?: LogRecordLimits; -} - -export class LogsSdk implements SignalSdk { - private _loggerProvider: LoggerProvider | undefined; +const DEFAULT_LOGS_OTLP_ENDOINT = 'http://localhost:4318/v1/logs'; - start(config?: GlobalConfig & LogsSdkConfig) { - const logsEndpoint = getExportUrl( - config?.otlpLogsEndpoint, - config?.otlpEndpoint, - '/v1/logs', - ); +/** + * @param config The configuration for logs + * @returns {WebSdk} + */ +export function startLogsSdk(config?: LogsConfig): WebSdk { + const logsEndpoint = config?.otlpLogsEndpoint || DEFAULT_LOGS_OTLP_ENDOINT; + const logsProcessor = new BatchLogRecordProcessor( + new OTLPLogExporter({ + url: logsEndpoint, + headers: config?.otlpLogsHeaders, + }), + { + scheduledDelayMillis: config?.blrpScheduleDelay, + exportTimeoutMillis: config?.blrpExportTimeout, + maxExportBatchSize: config?.blrpMaxExportBatchSize, + maxQueueSize: config?.blrpMaxQueueSize, + }, + ); + const loggerProvider = new LoggerProvider({ + resource: config?.resource, + logRecordLimits: config?.logRecordLimits, + processors: [logsProcessor], + }); + logs.setGlobalLoggerProvider(loggerProvider); - const logsProcessor = new BatchLogRecordProcessor( - new OTLPLogExporter({ - url: logsEndpoint, - headers: config?.otlpLogsHeaders ?? config?.otlpHeaders, - }), - { - scheduledDelayMillis: config?.blrpScheduleDelay, - exportTimeoutMillis: config?.blrpExportTimeout, - maxExportBatchSize: config?.blrpMaxExportBatchSize, - maxQueueSize: config?.blrpMaxQueueSize, - }, - ); - this._loggerProvider = new LoggerProvider({ - resource: config?.resource, - logRecordLimits: config?.logRecordLimits, - processors: [logsProcessor], - }); - logs.setGlobalLoggerProvider(this._loggerProvider); - } - shutdown() { - if (this._loggerProvider) { - return this._loggerProvider.shutdown(); - } - return Promise.resolve(); - } + return { + shutdown() { + return loggerProvider.shutdown(); + }, + }; } diff --git a/packages/browser/src/metrics.ts b/packages/browser/src/metrics.ts index a23e58e..36010a5 100644 --- a/packages/browser/src/metrics.ts +++ b/packages/browser/src/metrics.ts @@ -10,39 +10,28 @@ import { PeriodicExportingMetricReader, } from '@opentelemetry/sdk-metrics'; -import type { GlobalConfig, SignalSdk } from './types.ts'; -import { getExportUrl } from './utils.ts'; +import type { MetricsConfig, WebSdk } from './types.ts'; -export interface MetricsSdkConfig { - otlpMetricsEndpoint?: string; - otlpMetricsHeaders?: Record; -} +const DEFAULT_METRICS_OTLP_ENDOINT = 'http://localhost:4318/v1/metrics'; -export class MetricsSdk implements SignalSdk { - private _meterProvider: MeterProvider | undefined; +export function startMetricsSdk(config?: MetricsConfig): WebSdk { + const metricsEndpoint = + config?.otlpMetricsEndpoint || DEFAULT_METRICS_OTLP_ENDOINT; + const metricsReader = new PeriodicExportingMetricReader({ + exporter: new OTLPMetricExporter({ + url: metricsEndpoint, + headers: config?.otlpMetricsHeaders, + }), + }); + const meterProvider = new MeterProvider({ + resource: config?.resource, + readers: [metricsReader], + }); + metrics.setGlobalMeterProvider(meterProvider); - start(config?: GlobalConfig & MetricsSdkConfig) { - const metricsEndpoint = getExportUrl( - config?.otlpMetricsEndpoint, - config?.otlpEndpoint, - '/v1/metrics', - ); - const metricsReader = new PeriodicExportingMetricReader({ - exporter: new OTLPMetricExporter({ - url: metricsEndpoint, - headers: config?.otlpMetricsHeaders ?? config?.otlpHeaders, - }), - }); - this._meterProvider = new MeterProvider({ - resource: config?.resource, - readers: [metricsReader], - }); - metrics.setGlobalMeterProvider(this._meterProvider); - } - shutdown() { - if (this._meterProvider) { - return this._meterProvider.shutdown(); - } - return Promise.resolve(); - } + return { + shutdown() { + return meterProvider.shutdown(); + }, + }; } diff --git a/packages/browser/src/sdk.ts b/packages/browser/src/sdk.ts index 5a98dcf..a3d5b1d 100644 --- a/packages/browser/src/sdk.ts +++ b/packages/browser/src/sdk.ts @@ -3,48 +3,84 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { SDK_INFO } from '@opentelemetry/core'; -import { - defaultResource, - resourceFromAttributes, -} from '@opentelemetry/resources'; - -import type { GlobalConfig, SignalSdk } from './types.ts'; - -export class WebSdkBuilder { - private _signals: Array> = []; - - withSignalSdk(signal: SignalSdk): WebSdkBuilder { - this._signals.push(signal); - return this as WebSdkBuilder; - } - build(config: T) { - // TODO: adjust some configuration before passing it to signals like - // - resource - // - ??? - - const signals = this._signals; - const conf = config as GlobalConfig; - - // Set resource - conf.resource = (conf.resource || defaultResource()).merge( - resourceFromAttributes({ ...SDK_INFO }), - ); - if (typeof conf.serviceName === 'string') { - conf.resource = conf.resource.merge( - resourceFromAttributes({ 'service.name': conf.serviceName }), - ); +import { defaultResource } from '@opentelemetry/resources'; +import type { + GlobalConfig, + LogsConfig, + MetricsConfig, + TracesConfig, + WebSdk, + WebSdkFactory, +} from './types.ts'; + +interface SdkFactories { + logs?: WebSdkFactory; + metrics?: WebSdkFactory; + traces?: WebSdkFactory; +} + +type ConfigsFor = Partial<{ + [K in keyof T]: T[K] extends WebSdkFactory ? C : never; +}>; + +const DEFAULT_OTLP_ENDOINT = 'http://localhost:4318'; + +export function combineSdks( + factories: T, +): WebSdkFactory> { + // The returned function will transform some of the global + // configuration options to signal specific ones if the SDK is available + return function startSdk(config?: ConfigsFor) { + // Check the global config and set defaults + const globalConfig = (config || {}) as GlobalConfig; + globalConfig.otlpEndpoint ??= DEFAULT_OTLP_ENDOINT; + + // TODO: accept resource detectors? + globalConfig.resource ??= defaultResource(); + + const sdks: WebSdk[] = []; + const otlpUrl = new URL(globalConfig.otlpEndpoint); + + // Start logs + if (factories.logs) { + const logsConfig = (config?.logs || {}) as LogsConfig; + if (!logsConfig.otlpLogsEndpoint) { + otlpUrl.pathname = 'v1/logs'; + logsConfig.otlpLogsEndpoint = otlpUrl.href; + } + logsConfig.otlpLogsHeaders ??= globalConfig.otlpHeaders; + logsConfig.resource ??= globalConfig.resource; + sdks.push(factories.logs(logsConfig)); + } + + // Start metrics + if (factories.metrics) { + const metricsConfig = (config?.metrics || {}) as MetricsConfig; + if (!metricsConfig.otlpMetricsEndpoint) { + otlpUrl.pathname = 'v1/metrics'; + metricsConfig.otlpMetricsEndpoint = otlpUrl.href; + } + metricsConfig.otlpMetricsHeaders ??= globalConfig.otlpHeaders; + metricsConfig.resource ??= globalConfig.resource; + sdks.push(factories.metrics(metricsConfig)); + } + + // Start traces + if (factories.traces) { + const tracesConfig = (config?.traces || {}) as TracesConfig; + if (!tracesConfig.otlpTracesEndpoint) { + otlpUrl.pathname = 'v1/traces'; + tracesConfig.otlpTracesEndpoint = otlpUrl.href; + } + tracesConfig.otlpTracesHeaders ??= globalConfig.otlpHeaders; + tracesConfig.resource ??= globalConfig.resource; + sdks.push(factories.traces(tracesConfig)); } return { - start() { - for (const s of signals) { - s.start(conf); - } - }, shutdown() { - return Promise.all(signals.map((s) => s.shutdown())); + return Promise.all(sdks.map((s) => s.shutdown())).then(() => undefined); }, }; - } + }; } diff --git a/packages/browser/src/traces.ts b/packages/browser/src/traces.ts index 2551d41..d73d057 100644 --- a/packages/browser/src/traces.ts +++ b/packages/browser/src/traces.ts @@ -4,59 +4,38 @@ */ import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; -import type { SpanLimits } from '@opentelemetry/sdk-trace-base'; import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base'; import { WebTracerProvider } from '@opentelemetry/sdk-trace-web'; -import type { GlobalConfig, SignalSdk } from './types.ts'; -import { getExportUrl } from './utils.ts'; +import type { TracesConfig, WebSdk } from './types.ts'; -export interface TracesSdkConfig { - // Processor - // TODO: impklement handling these - bspScheduleDelay?: number; - bspExportTimeout?: number; - bspMaxQueueSize?: number; - bspMaxExportBatchSize?: number; - // Export - otlpTracesEndpoint?: string; - otlpTracesHeaders?: Record; - // Limits - spanLimits?: SpanLimits; -} +const DEFAULT_TRACES_OTLP_ENDOINT = 'http://localhost:4318/v1/metrics'; -export class TracesSdk implements SignalSdk { - private _tracerProvider: WebTracerProvider | undefined; +export function startTracesSdk(config?: TracesConfig): WebSdk { + const tracesEndpoint = + config?.otlpTracesEndpoint || DEFAULT_TRACES_OTLP_ENDOINT; - start(config?: GlobalConfig & TracesSdkConfig) { - const tracesEndpoint = getExportUrl( - config?.otlpTracesEndpoint, - config?.otlpEndpoint, - '/v1/traces', - ); + const spanProcessor = new BatchSpanProcessor( + new OTLPTraceExporter({ + url: tracesEndpoint, + headers: config?.otlpTracesHeaders, + }), + ); - const spanProcessor = new BatchSpanProcessor( - new OTLPTraceExporter({ - url: tracesEndpoint, - headers: config?.otlpTracesHeaders ?? config?.otlpHeaders, - }), - ); + const tracerProvider = new WebTracerProvider({ + // sampler: new TraceIdRatioBasedSampler( + // typeof config?.sampleRate === "number" ? config?.sampleRate : 1, + // ), + resource: config?.resource, + spanLimits: config?.spanLimits, + spanProcessors: [spanProcessor], + }); + // TODO: allow context manager and propagatros??? + tracerProvider.register(); - this._tracerProvider = new WebTracerProvider({ - // sampler: new TraceIdRatioBasedSampler( - // typeof config?.sampleRate === "number" ? config?.sampleRate : 1, - // ), - resource: config?.resource, - spanLimits: config?.spanLimits, - spanProcessors: [spanProcessor], - }); - // TODO: allow context manager and propagatros??? - this._tracerProvider.register(); - } - shutdown() { - if (this._tracerProvider) { - return this._tracerProvider.shutdown(); - } - return Promise.resolve(); - } + return { + shutdown() { + return tracerProvider.shutdown(); + }, + }; } diff --git a/packages/browser/src/types.ts b/packages/browser/src/types.ts index 77e0f65..d5e45e0 100644 --- a/packages/browser/src/types.ts +++ b/packages/browser/src/types.ts @@ -5,11 +5,13 @@ import type { DiagLogLevel } from '@opentelemetry/api'; import type { Resource } from '@opentelemetry/resources'; +import type { LogRecordLimits } from '@opentelemetry/sdk-logs'; +import type { SpanLimits } from '@opentelemetry/sdk-trace-base'; export interface GlobalConfig { disabled?: boolean; logLevel?: DiagLogLevel; - // Resource & ·Entities related + // Resource & Entities related serviceName?: string; resource?: Resource; // Export @@ -20,7 +22,45 @@ export interface GlobalConfig { attrCountLimit?: number; } -export interface SignalSdk { - start(config?: GlobalConfig & SignalConfig): void; +export interface LogsConfig { + // Resource & Entities related + resource?: Resource; + // Processor + blrpScheduleDelay?: number; + blrpExportTimeout?: number; + blrpMaxQueueSize?: number; + blrpMaxExportBatchSize?: number; + // Export + otlpLogsEndpoint?: string; + otlpLogsHeaders?: Record; + // Limits + logRecordLimits?: LogRecordLimits; +} + +export interface MetricsConfig { + // Resource & Entities related + resource?: Resource; + // Export + otlpMetricsEndpoint?: string; + otlpMetricsHeaders?: Record; +} + +export interface TracesConfig { + // Resource & Entities related + resource?: Resource; + // Processor + bspScheduleDelay?: number; + bspExportTimeout?: number; + bspMaxQueueSize?: number; + bspMaxExportBatchSize?: number; + // Export + otlpTracesEndpoint?: string; + otlpTracesHeaders?: Record; + // Limits + spanLimits?: SpanLimits; +} + +export interface WebSdk { shutdown(): Promise; } +export type WebSdkFactory = (config?: T) => WebSdk; diff --git a/packages/browser/src/utils.ts b/packages/browser/src/utils.ts deleted file mode 100644 index b1f1b68..0000000 --- a/packages/browser/src/utils.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -const DEFAULT_OTLP_ENDOINT = 'http://localhost:4318'; - -export function getExportUrl( - signalUrl: string | undefined, - genericUrl: string | undefined, - signalPath: string, -) { - if (typeof signalUrl === 'string') { - return signalUrl; - } - - const baseUrl = - typeof genericUrl === 'string' ? genericUrl : DEFAULT_OTLP_ENDOINT; - const url = new URL(signalPath, baseUrl); - return url.href; -} From a3d00632ed16c721dd33a5ca20edfdac7dbf39bf Mon Sep 17 00:00:00 2001 From: David Luna Date: Wed, 8 Apr 2026 13:20:49 +0200 Subject: [PATCH 06/20] chore: remove metrics --- packages/browser/package.json | 1 - packages/browser/src/metrics.ts | 37 --------------------------------- packages/browser/src/sdk.ts | 14 ------------- packages/browser/src/types.ts | 8 ------- 4 files changed, 60 deletions(-) delete mode 100644 packages/browser/src/metrics.ts diff --git a/packages/browser/package.json b/packages/browser/package.json index fd0b6ca..8314b5a 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -20,7 +20,6 @@ "type": "module", "exports": { "./logs": "./dist/logs.js", - "./metrics": "./dist/metrics.js", "./traces": "./dist/traces.js", "./sdk": "./dist/sdk.js" }, diff --git a/packages/browser/src/metrics.ts b/packages/browser/src/metrics.ts deleted file mode 100644 index 36010a5..0000000 --- a/packages/browser/src/metrics.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { metrics } from '@opentelemetry/api'; -import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http'; -import { - MeterProvider, - PeriodicExportingMetricReader, -} from '@opentelemetry/sdk-metrics'; - -import type { MetricsConfig, WebSdk } from './types.ts'; - -const DEFAULT_METRICS_OTLP_ENDOINT = 'http://localhost:4318/v1/metrics'; - -export function startMetricsSdk(config?: MetricsConfig): WebSdk { - const metricsEndpoint = - config?.otlpMetricsEndpoint || DEFAULT_METRICS_OTLP_ENDOINT; - const metricsReader = new PeriodicExportingMetricReader({ - exporter: new OTLPMetricExporter({ - url: metricsEndpoint, - headers: config?.otlpMetricsHeaders, - }), - }); - const meterProvider = new MeterProvider({ - resource: config?.resource, - readers: [metricsReader], - }); - metrics.setGlobalMeterProvider(meterProvider); - - return { - shutdown() { - return meterProvider.shutdown(); - }, - }; -} diff --git a/packages/browser/src/sdk.ts b/packages/browser/src/sdk.ts index a3d5b1d..280294c 100644 --- a/packages/browser/src/sdk.ts +++ b/packages/browser/src/sdk.ts @@ -7,7 +7,6 @@ import { defaultResource } from '@opentelemetry/resources'; import type { GlobalConfig, LogsConfig, - MetricsConfig, TracesConfig, WebSdk, WebSdkFactory, @@ -15,7 +14,6 @@ import type { interface SdkFactories { logs?: WebSdkFactory; - metrics?: WebSdkFactory; traces?: WebSdkFactory; } @@ -53,18 +51,6 @@ export function combineSdks( sdks.push(factories.logs(logsConfig)); } - // Start metrics - if (factories.metrics) { - const metricsConfig = (config?.metrics || {}) as MetricsConfig; - if (!metricsConfig.otlpMetricsEndpoint) { - otlpUrl.pathname = 'v1/metrics'; - metricsConfig.otlpMetricsEndpoint = otlpUrl.href; - } - metricsConfig.otlpMetricsHeaders ??= globalConfig.otlpHeaders; - metricsConfig.resource ??= globalConfig.resource; - sdks.push(factories.metrics(metricsConfig)); - } - // Start traces if (factories.traces) { const tracesConfig = (config?.traces || {}) as TracesConfig; diff --git a/packages/browser/src/types.ts b/packages/browser/src/types.ts index d5e45e0..2ee37bd 100644 --- a/packages/browser/src/types.ts +++ b/packages/browser/src/types.ts @@ -37,14 +37,6 @@ export interface LogsConfig { logRecordLimits?: LogRecordLimits; } -export interface MetricsConfig { - // Resource & Entities related - resource?: Resource; - // Export - otlpMetricsEndpoint?: string; - otlpMetricsHeaders?: Record; -} - export interface TracesConfig { // Resource & Entities related resource?: Resource; From fa7d9a348ce3f3bcfb26e278eb40ff58561f1418 Mon Sep 17 00:00:00 2001 From: David Luna Date: Wed, 8 Apr 2026 13:42:02 +0200 Subject: [PATCH 07/20] chore: exclide tests files from build --- packages/browser/tsdown.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/browser/tsdown.config.ts b/packages/browser/tsdown.config.ts index 0ae211b..82b6103 100644 --- a/packages/browser/tsdown.config.ts +++ b/packages/browser/tsdown.config.ts @@ -3,5 +3,5 @@ import baseConfig from '../../tsdown.config.ts'; export default defineConfig({ ...baseConfig, - entry: ['src/*.ts', '!src/{test-utils,utils}/index.ts'], + entry: ['src/*.ts', '!src/*.test.ts'], }); From 480eaf86604eaae07ea08310afc02f766f95c9e1 Mon Sep 17 00:00:00 2001 From: David Luna Date: Wed, 15 Apr 2026 20:30:37 +0200 Subject: [PATCH 08/20] Update packages/browser/src/types.ts Co-authored-by: Jared Freeze --- packages/browser/src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/browser/src/types.ts b/packages/browser/src/types.ts index 2ee37bd..b128729 100644 --- a/packages/browser/src/types.ts +++ b/packages/browser/src/types.ts @@ -18,7 +18,7 @@ export interface GlobalConfig { otlpEndpoint?: string; otlpHeaders?: Record; // Limits - attrLenghtLimit?: number; + attrLengthLimit?: number; attrCountLimit?: number; } From dab558ec3d0cd1413188dc548453036cbef3c4ec Mon Sep 17 00:00:00 2001 From: David Luna Date: Wed, 15 Apr 2026 20:30:46 +0200 Subject: [PATCH 09/20] Update packages/browser/src/logs.ts Co-authored-by: Jared Freeze --- packages/browser/src/logs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/browser/src/logs.ts b/packages/browser/src/logs.ts index 82755d4..40d4354 100644 --- a/packages/browser/src/logs.ts +++ b/packages/browser/src/logs.ts @@ -12,7 +12,7 @@ import { import type { LogsConfig, WebSdk } from './types.ts'; -const DEFAULT_LOGS_OTLP_ENDOINT = 'http://localhost:4318/v1/logs'; +const DEFAULT_LOGS_OTLP_ENDPOINT = 'http://localhost:4318/v1/logs'; /** * @param config The configuration for logs From b144d6fe76b3644fae2d47a2264c97d376e0047b Mon Sep 17 00:00:00 2001 From: David Luna Date: Wed, 15 Apr 2026 20:30:56 +0200 Subject: [PATCH 10/20] Update packages/browser/src/traces.ts Co-authored-by: Jared Freeze --- packages/browser/src/traces.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/browser/src/traces.ts b/packages/browser/src/traces.ts index d73d057..98b2e35 100644 --- a/packages/browser/src/traces.ts +++ b/packages/browser/src/traces.ts @@ -9,7 +9,7 @@ import { WebTracerProvider } from '@opentelemetry/sdk-trace-web'; import type { TracesConfig, WebSdk } from './types.ts'; -const DEFAULT_TRACES_OTLP_ENDOINT = 'http://localhost:4318/v1/metrics'; +const DEFAULT_TRACES_OTLP_ENDOINT = 'http://localhost:4318/v1/traces'; export function startTracesSdk(config?: TracesConfig): WebSdk { const tracesEndpoint = From 10279c9117c4f29fd080104b314000f66ab33838 Mon Sep 17 00:00:00 2001 From: David Luna Date: Wed, 15 Apr 2026 20:31:09 +0200 Subject: [PATCH 11/20] Update packages/browser/src/sdk.ts Co-authored-by: Jared Freeze --- packages/browser/src/sdk.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/browser/src/sdk.ts b/packages/browser/src/sdk.ts index 280294c..aae2f92 100644 --- a/packages/browser/src/sdk.ts +++ b/packages/browser/src/sdk.ts @@ -65,7 +65,7 @@ export function combineSdks( return { shutdown() { - return Promise.all(sdks.map((s) => s.shutdown())).then(() => undefined); + return Promise.allSettled(sdks.map((s) => s.shutdown())).then(() => undefined); }, }; }; From a396f14b4d8bddd1928435a8a453634b63472373 Mon Sep 17 00:00:00 2001 From: David Luna Date: Wed, 15 Apr 2026 20:31:39 +0200 Subject: [PATCH 12/20] Update packages/browser/src/sdk.ts Co-authored-by: Jared Freeze --- packages/browser/src/sdk.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/browser/src/sdk.ts b/packages/browser/src/sdk.ts index aae2f92..8655f86 100644 --- a/packages/browser/src/sdk.ts +++ b/packages/browser/src/sdk.ts @@ -55,7 +55,7 @@ export function combineSdks( if (factories.traces) { const tracesConfig = (config?.traces || {}) as TracesConfig; if (!tracesConfig.otlpTracesEndpoint) { - otlpUrl.pathname = 'v1/traces'; + otlpUrl.pathname = '/v1/traces'; tracesConfig.otlpTracesEndpoint = otlpUrl.href; } tracesConfig.otlpTracesHeaders ??= globalConfig.otlpHeaders; From f37a56735be49eb860e4c8a3aad3f4c35c5babe6 Mon Sep 17 00:00:00 2001 From: David Luna Date: Wed, 15 Apr 2026 20:31:53 +0200 Subject: [PATCH 13/20] Update packages/browser/src/sdk.ts Co-authored-by: Jared Freeze --- packages/browser/src/sdk.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/browser/src/sdk.ts b/packages/browser/src/sdk.ts index 8655f86..fc00545 100644 --- a/packages/browser/src/sdk.ts +++ b/packages/browser/src/sdk.ts @@ -43,7 +43,7 @@ export function combineSdks( if (factories.logs) { const logsConfig = (config?.logs || {}) as LogsConfig; if (!logsConfig.otlpLogsEndpoint) { - otlpUrl.pathname = 'v1/logs'; + otlpUrl.pathname = '/v1/logs'; logsConfig.otlpLogsEndpoint = otlpUrl.href; } logsConfig.otlpLogsHeaders ??= globalConfig.otlpHeaders; From 6097f1ed5c6e3cebb45e57b13d9a3c8bf7ba2f31 Mon Sep 17 00:00:00 2001 From: David Luna Date: Thu, 16 Apr 2026 14:53:48 +0200 Subject: [PATCH 14/20] chore: add default context manager and propagator --- package-lock.json | 34 +------- packages/browser/package.json | 3 +- packages/browser/src/context.test.ts | 112 +++++++++++++++++++++++++++ packages/browser/src/context.ts | 66 ++++++++++++++++ packages/browser/src/propagator.ts | 17 ++++ packages/browser/src/traces.ts | 10 +-- packages/browser/src/types.ts | 10 ++- 7 files changed, 211 insertions(+), 41 deletions(-) create mode 100644 packages/browser/src/context.test.ts create mode 100644 packages/browser/src/context.ts create mode 100644 packages/browser/src/propagator.ts diff --git a/package-lock.json b/package-lock.json index 4409f29..380fa87 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2018,37 +2018,6 @@ "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/@opentelemetry/sdk-trace-web": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-web/-/sdk-trace-web-2.6.1.tgz", - "integrity": "sha512-JQevIjBlWGcKBfuwe7tdxGR/75RERsf1OOIvUzPKq86J8qhzkyjnLTTuPNPLRQF1xxEe65W5aI1Uwl6yWUGPQQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.6.1", - "@opentelemetry/sdk-trace-base": "2.6.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-trace-web/node_modules/@opentelemetry/core": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.1.tgz", - "integrity": "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, "node_modules/@opentelemetry/semantic-conventions": { "version": "1.40.0", "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.40.0.tgz", @@ -7174,8 +7143,7 @@ "@opentelemetry/resources": "^2.6.0", "@opentelemetry/sdk-logs": "^0.213.0", "@opentelemetry/sdk-metrics": "^2.6.1", - "@opentelemetry/sdk-trace-base": "^2.6.1", - "@opentelemetry/sdk-trace-web": "^2.6.1" + "@opentelemetry/sdk-trace-base": "^2.6.1" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0" diff --git a/packages/browser/package.json b/packages/browser/package.json index 8314b5a..19f40ad 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -43,8 +43,7 @@ "@opentelemetry/resources": "^2.6.0", "@opentelemetry/sdk-logs": "^0.213.0", "@opentelemetry/sdk-metrics": "^2.6.1", - "@opentelemetry/sdk-trace-base": "^2.6.1", - "@opentelemetry/sdk-trace-web": "^2.6.1" + "@opentelemetry/sdk-trace-base": "^2.6.1" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0" diff --git a/packages/browser/src/context.test.ts b/packages/browser/src/context.test.ts new file mode 100644 index 0000000..4c95fde --- /dev/null +++ b/packages/browser/src/context.test.ts @@ -0,0 +1,112 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { ContextManager } from '@opentelemetry/api'; +import { createContextKey, ROOT_CONTEXT } from '@opentelemetry/api'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { getDefaultContextManager } from './context.ts'; + +describe('Default ContextManager', () => { + const key1 = createContextKey('test key 1'); + let contextManager: ContextManager; + + beforeEach(() => { + contextManager = getDefaultContextManager(); + contextManager.enable(); + }); + + afterEach(() => { + contextManager.disable(); + }); + + describe('.enable()', () => { + it('should work', () => { + expect(contextManager.enable()).toBe(contextManager); + expect(contextManager.active()).toBe(ROOT_CONTEXT); + }); + }); + + describe('.disable()', () => { + it('should work', () => { + expect(contextManager.disable()).toBe(contextManager); + expect(contextManager.active()).toBe(ROOT_CONTEXT); + }); + }); + + describe('.with()', () => { + it('should run the callback (null as context)', () => { + return new Promise((done) => { + contextManager.with(null as any, done as any); + }); + }); + + it('should run the callback (object as target)', () => { + const test = ROOT_CONTEXT.setValue(key1, 1); + + return new Promise((done) => { + contextManager.with(test, () => { + expect(contextManager.active()).toBe(test); + return done(null); + }); + }); + }); + + it('should run the callback (when disabled)', () => { + contextManager.disable(); + return new Promise((done) => { + contextManager.with(null as any, () => { + contextManager.enable(); + return done(null); + }); + }); + }); + + it('should rethrow errors', () => { + expect(() => { + contextManager.with(contextManager.active(), () => { + throw new Error('This should be rethrown'); + }); + }).throws(); + }); + + it('should stack and restore context', () => { + const ctx1 = ROOT_CONTEXT.setValue(key1, 'ctx1'); + const ctx2 = ROOT_CONTEXT.setValue(key1, 'ctx2'); + const ctx3 = ROOT_CONTEXT.setValue(key1, 'ctx3'); + contextManager.with(ctx1, () => { + expect(contextManager.active()).toBe(ctx1); + contextManager.with(ctx2, () => { + expect(contextManager.active()).toBe(ctx2); + contextManager.with(ctx3, () => { + expect(contextManager.active()).toBe(ctx3); + }); + expect(contextManager.active()).toBe(ctx2); + }); + expect(contextManager.active()).toBe(ctx1); + }); + expect(contextManager.active()).toBe(ROOT_CONTEXT); + }); + + it('should forward this, arguments and return value', () => { + function fnWithThis(this: string, a: string, b: number): string { + expect(this).toEqual('that'); + expect(arguments.length).toEqual(2); + expect(a).toEqual('one'); + expect(b).toEqual(2); + return 'done'; + } + + const res = contextManager.with( + ROOT_CONTEXT, + fnWithThis, + 'that', + 'one', + 2, + ); + expect(res).toEqual('done'); + expect(contextManager.with(ROOT_CONTEXT, () => 3.14)).toEqual(3.14); + }); + }); +}); diff --git a/packages/browser/src/context.ts b/packages/browser/src/context.ts new file mode 100644 index 0000000..3166407 --- /dev/null +++ b/packages/browser/src/context.ts @@ -0,0 +1,66 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { Context, ContextManager } from '@opentelemetry/api'; +import { ROOT_CONTEXT } from '@opentelemetry/api'; + +/** + * Returns a simple context manager which stack + * @returns {ContextManager} + */ +export function getDefaultContextManager(): ContextManager { + let _currentContext = ROOT_CONTEXT; + let _enabled = false; + + return { + active: (): Context => _currentContext, + with: ReturnType>( + context: Context, + fn: F, + thisArg?: ThisParameterType, + ...args: A + ): ReturnType => { + const previousContext = _currentContext; + _currentContext = context || ROOT_CONTEXT; + try { + return fn.call(thisArg, ...args); + } finally { + _currentContext = previousContext; + } + }, + bind: function (context: Context, target: T): T { + // if no specific context to propagate is given, we use the current one + if (context === undefined) { + context = this.active(); + } + if (typeof target === 'function') { + const manager = this; + const contextWrapper = function (this: unknown, ...args: unknown[]) { + return manager.with(context, () => target.apply(this, args)); + }; + Object.defineProperty(contextWrapper, 'length', { + enumerable: false, + configurable: true, + writable: false, + value: target.length, + }); + return contextWrapper as unknown as T; + } + return target; + }, + enable: function (): ContextManager { + if (!_enabled) { + _currentContext = ROOT_CONTEXT; + _enabled = true; + } + return this; + }, + disable: function (): ContextManager { + _currentContext = ROOT_CONTEXT; + _enabled = false; + return this; + }, + }; +} diff --git a/packages/browser/src/propagator.ts b/packages/browser/src/propagator.ts new file mode 100644 index 0000000..096654a --- /dev/null +++ b/packages/browser/src/propagator.ts @@ -0,0 +1,17 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { TextMapPropagator } from '@opentelemetry/api'; +import { + CompositePropagator, + W3CBaggagePropagator, + W3CTraceContextPropagator, +} from '@opentelemetry/core'; + +export function getDefaultPropagator(): TextMapPropagator { + return new CompositePropagator({ + propagators: [new W3CTraceContextPropagator(), new W3CBaggagePropagator()], + }); +} diff --git a/packages/browser/src/traces.ts b/packages/browser/src/traces.ts index d73d057..fc37a83 100644 --- a/packages/browser/src/traces.ts +++ b/packages/browser/src/traces.ts @@ -4,8 +4,10 @@ */ import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; -import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base'; -import { WebTracerProvider } from '@opentelemetry/sdk-trace-web'; +import { + BasicTracerProvider, + BatchSpanProcessor, +} from '@opentelemetry/sdk-trace-base'; import type { TracesConfig, WebSdk } from './types.ts'; @@ -22,7 +24,7 @@ export function startTracesSdk(config?: TracesConfig): WebSdk { }), ); - const tracerProvider = new WebTracerProvider({ + const tracerProvider = new BasicTracerProvider({ // sampler: new TraceIdRatioBasedSampler( // typeof config?.sampleRate === "number" ? config?.sampleRate : 1, // ), @@ -30,8 +32,6 @@ export function startTracesSdk(config?: TracesConfig): WebSdk { spanLimits: config?.spanLimits, spanProcessors: [spanProcessor], }); - // TODO: allow context manager and propagatros??? - tracerProvider.register(); return { shutdown() { diff --git a/packages/browser/src/types.ts b/packages/browser/src/types.ts index 2ee37bd..74c2103 100644 --- a/packages/browser/src/types.ts +++ b/packages/browser/src/types.ts @@ -3,7 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type { DiagLogLevel } from '@opentelemetry/api'; +import type { + ContextManager, + DiagLogLevel, + TextMapPropagator, +} from '@opentelemetry/api'; import type { Resource } from '@opentelemetry/resources'; import type { LogRecordLimits } from '@opentelemetry/sdk-logs'; import type { SpanLimits } from '@opentelemetry/sdk-trace-base'; @@ -38,6 +42,10 @@ export interface LogsConfig { } export interface TracesConfig { + // Context and Propagation + contextManager?: ContextManager; + textMapPropagator?: TextMapPropagator; + // Resource & Entities related resource?: Resource; // Processor From 6f68519dbfa9e88ecc2826f6efb74df4e7cc2a20 Mon Sep 17 00:00:00 2001 From: David Luna Date: Thu, 16 Apr 2026 15:48:52 +0200 Subject: [PATCH 15/20] chore: add tests for traces sdk --- packages/browser/src/propagator.ts | 6 ++ packages/browser/src/traces.test.ts | 108 ++++++++++++++++++++++++++++ packages/browser/src/traces.ts | 18 ++++- 3 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 packages/browser/src/traces.test.ts diff --git a/packages/browser/src/propagator.ts b/packages/browser/src/propagator.ts index 096654a..4318dbd 100644 --- a/packages/browser/src/propagator.ts +++ b/packages/browser/src/propagator.ts @@ -10,6 +10,12 @@ import { W3CTraceContextPropagator, } from '@opentelemetry/core'; +/** + * Returns the default propagator which is a composition of: + * - W3CTraceContextPropagator + * -W3CBaggagePropagator + * @returns {TextMapPropagator} + */ export function getDefaultPropagator(): TextMapPropagator { return new CompositePropagator({ propagators: [new W3CTraceContextPropagator(), new W3CBaggagePropagator()], diff --git a/packages/browser/src/traces.test.ts b/packages/browser/src/traces.test.ts new file mode 100644 index 0000000..f0f6d10 --- /dev/null +++ b/packages/browser/src/traces.test.ts @@ -0,0 +1,108 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { TracerProvider } from '@opentelemetry/api'; +import { trace } from '@opentelemetry/api'; +import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base'; +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest'; + +import { startTracesSdk } from './traces.ts'; +import type { WebSdk } from './types.ts'; + +const BSP_SCHEDULE_DELAY = 10; + +describe('startTracesSdk', () => { + const response = { ok: true, json: async () => ({ ok: true }) } as Response; + const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(response); + const setGlobalTracerProviderSpy = vi + .spyOn(trace, 'setGlobalTracerProvider') + .mockImplementation((p) => { + tracerProvider = p; + return true; + }); + const getLoggerProviderSpy = vi + .spyOn(trace, 'getTracerProvider') + .mockImplementation(() => tracerProvider); + let tracerProvider: TracerProvider; + let tracesSdk: WebSdk; + + // NOTE: we mock the registration of the tracer provider because + // the trace API only allow to register once. With the mock we can use + // a dedicated provider for the test + afterAll(() => { + setGlobalTracerProviderSpy.mockRestore(); + getLoggerProviderSpy.mockRestore(); + fetchSpy.mockRestore(); + }); + beforeEach(async () => { + setGlobalTracerProviderSpy.mockClear(); + getLoggerProviderSpy.mockClear(); + fetchSpy.mockClear(); + await tracesSdk?.shutdown(); + }); + + it('should register a TracerProvider with a BatchSpanProcessor', async () => { + // Act + tracesSdk = startTracesSdk(); + + // Assert + expect(setGlobalTracerProviderSpy).callCount(1); + const key = '_activeSpanProcessor'; + const subkey = '_spanProcessors'; + // @ts-expect-error -- accessing private properties + const processors = trace.getTracerProvider()[key][subkey]; + expect(processors.length).toBe(1); + expect(processors[0]).toBeInstanceOf(BatchSpanProcessor); + }); + + it('should use the default configuration for exporters', async () => { + // Act + tracesSdk = startTracesSdk({ + // NOTE: we set a short delay to speed up tests and avoid test timeouts + bspScheduleDelay: BSP_SCHEDULE_DELAY, + }); + + trace.getTracer('traces-sdk-test').startSpan('test').end(); + await new Promise((r) => setTimeout(r, BSP_SCHEDULE_DELAY + 5)); + + // Assert + expect(fetchSpy).toHaveBeenCalledOnce(); + expect(fetchSpy.mock.lastCall?.[0]).toEqual( + 'http://localhost:4318/v1/traces', + ); + expect(fetchSpy.mock.lastCall?.[1]).containSubset({ + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }); + }); + + it('should accept signal specific OTLP endpoint and headers', async () => { + // Act + tracesSdk = startTracesSdk({ + // NOTE: we set a short delay to speed up tests and avoid test timeouts + bspScheduleDelay: BSP_SCHEDULE_DELAY, + otlpTracesEndpoint: 'http://otlp-signal-endpoint:4318/v1/traces', + otlpTracesHeaders: { bar: 'baz' }, + }); + trace.getTracer('traces-sdk-test').startSpan('test').end(); + await new Promise((r) => setTimeout(r, BSP_SCHEDULE_DELAY + 5)); + + // Assert + expect(setGlobalTracerProviderSpy).callCount(1); + expect(fetchSpy).toHaveBeenCalledOnce(); + expect(fetchSpy.mock.lastCall?.[0]).toEqual( + 'http://otlp-signal-endpoint:4318/v1/traces', + ); + expect(fetchSpy.mock.lastCall?.[1]).containSubset({ + method: 'POST', + headers: { + 'Content-Type': 'application/json', + bar: 'baz', + }, + }); + }); +}); diff --git a/packages/browser/src/traces.ts b/packages/browser/src/traces.ts index fc37a83..b8659c7 100644 --- a/packages/browser/src/traces.ts +++ b/packages/browser/src/traces.ts @@ -3,6 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { context, propagation, trace } from '@opentelemetry/api'; import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; import { BasicTracerProvider, @@ -11,7 +12,7 @@ import { import type { TracesConfig, WebSdk } from './types.ts'; -const DEFAULT_TRACES_OTLP_ENDOINT = 'http://localhost:4318/v1/metrics'; +const DEFAULT_TRACES_OTLP_ENDOINT = 'http://localhost:4318/v1/traces'; export function startTracesSdk(config?: TracesConfig): WebSdk { const tracesEndpoint = @@ -22,6 +23,12 @@ export function startTracesSdk(config?: TracesConfig): WebSdk { url: tracesEndpoint, headers: config?.otlpTracesHeaders, }), + { + scheduledDelayMillis: config?.bspScheduleDelay, + exportTimeoutMillis: config?.bspExportTimeout, + maxExportBatchSize: config?.bspMaxExportBatchSize, + maxQueueSize: config?.bspMaxQueueSize, + }, ); const tracerProvider = new BasicTracerProvider({ @@ -32,6 +39,15 @@ export function startTracesSdk(config?: TracesConfig): WebSdk { spanLimits: config?.spanLimits, spanProcessors: [spanProcessor], }); + trace.setGlobalTracerProvider(tracerProvider); + + if (config?.textMapPropagator) { + propagation.setGlobalPropagator(config.textMapPropagator); + } + + if (config?.contextManager) { + context.setGlobalContextManager(config.contextManager); + } return { shutdown() { From 18a839b69fb735b0ded85baa21f39a918b2fc08f Mon Sep 17 00:00:00 2001 From: David Luna Date: Fri, 17 Apr 2026 12:53:14 +0200 Subject: [PATCH 16/20] fix: fix types and bad const reference --- .gitignore | 1 + packages/browser/src/context.ts | 3 ++- packages/browser/src/logs.ts | 2 +- packages/browser/src/sdk.ts | 8 +++++--- packages/browser/src/types.ts | 15 ++++++++++++--- 5 files changed, 21 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index f572984..65b1295 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ node_modules __screenshots__ dist *.tgz +.vitest-attachments # dotenv environment variable files .env diff --git a/packages/browser/src/context.ts b/packages/browser/src/context.ts index 3166407..c47b445 100644 --- a/packages/browser/src/context.ts +++ b/packages/browser/src/context.ts @@ -7,7 +7,8 @@ import type { Context, ContextManager } from '@opentelemetry/api'; import { ROOT_CONTEXT } from '@opentelemetry/api'; /** - * Returns a simple context manager which stack + * Returns a simple context manager which stacks the parent context + * when a function is being called. * @returns {ContextManager} */ export function getDefaultContextManager(): ContextManager { diff --git a/packages/browser/src/logs.ts b/packages/browser/src/logs.ts index 40d4354..dba3904 100644 --- a/packages/browser/src/logs.ts +++ b/packages/browser/src/logs.ts @@ -19,7 +19,7 @@ const DEFAULT_LOGS_OTLP_ENDPOINT = 'http://localhost:4318/v1/logs'; * @returns {WebSdk} */ export function startLogsSdk(config?: LogsConfig): WebSdk { - const logsEndpoint = config?.otlpLogsEndpoint || DEFAULT_LOGS_OTLP_ENDOINT; + const logsEndpoint = config?.otlpLogsEndpoint || DEFAULT_LOGS_OTLP_ENDPOINT; const logsProcessor = new BatchLogRecordProcessor( new OTLPLogExporter({ url: logsEndpoint, diff --git a/packages/browser/src/sdk.ts b/packages/browser/src/sdk.ts index fc00545..ad44140 100644 --- a/packages/browser/src/sdk.ts +++ b/packages/browser/src/sdk.ts @@ -25,10 +25,10 @@ const DEFAULT_OTLP_ENDOINT = 'http://localhost:4318'; export function combineSdks( factories: T, -): WebSdkFactory> { +): WebSdkFactory> { // The returned function will transform some of the global // configuration options to signal specific ones if the SDK is available - return function startSdk(config?: ConfigsFor) { + return function startSdk(config?: GlobalConfig & ConfigsFor) { // Check the global config and set defaults const globalConfig = (config || {}) as GlobalConfig; globalConfig.otlpEndpoint ??= DEFAULT_OTLP_ENDOINT; @@ -65,7 +65,9 @@ export function combineSdks( return { shutdown() { - return Promise.allSettled(sdks.map((s) => s.shutdown())).then(() => undefined); + return Promise.allSettled(sdks.map((s) => s.shutdown())).then( + () => undefined, + ); }, }; }; diff --git a/packages/browser/src/types.ts b/packages/browser/src/types.ts index 5460bed..d0595a4 100644 --- a/packages/browser/src/types.ts +++ b/packages/browser/src/types.ts @@ -10,7 +10,7 @@ import type { } from '@opentelemetry/api'; import type { Resource } from '@opentelemetry/resources'; import type { LogRecordLimits } from '@opentelemetry/sdk-logs'; -import type { SpanLimits } from '@opentelemetry/sdk-trace-base'; +import type { Sampler, SpanLimits } from '@opentelemetry/sdk-trace-base'; export interface GlobalConfig { disabled?: boolean; @@ -21,9 +21,17 @@ export interface GlobalConfig { // Export otlpEndpoint?: string; otlpHeaders?: Record; - // Limits + // add other globals for queue/batch size + + // Global Limits attrLengthLimit?: number; attrCountLimit?: number; + + // Basic options that could translate to more complex ones + // in specific signals like + // 1. `sampleRate` becomes a TraceIdRatioBasedSampler for traces + // and maybe somethign else for other signals??? (sampling logs?) + // sampleRate?: number; } export interface LogsConfig { @@ -45,9 +53,10 @@ export interface TracesConfig { // Context and Propagation contextManager?: ContextManager; textMapPropagator?: TextMapPropagator; - // Resource & Entities related resource?: Resource; + // Sampler + sampler?: Sampler; // Processor bspScheduleDelay?: number; bspExportTimeout?: number; From 73e33f9d5c106cd73dcb387a365b4ea71b27b445 Mon Sep 17 00:00:00 2001 From: David Luna Date: Fri, 17 Apr 2026 13:37:05 +0200 Subject: [PATCH 17/20] chore: update readme --- packages/browser/README.md | 100 +++++++++++++++++++++++++++++++++++-- 1 file changed, 96 insertions(+), 4 deletions(-) diff --git a/packages/browser/README.md b/packages/browser/README.md index 6f4b112..151b453 100644 --- a/packages/browser/README.md +++ b/packages/browser/README.md @@ -3,20 +3,112 @@ [![NPM Published Version][npm-img]][npm-url] [![Apache License][license-image]][license-image] +Browser-oriented helpers to start OpenTelemetry **logs** and **traces** with OTLP HTTP export. Configuration is split by signal so you import only what you use, which helps bundlers tree-shake unused code. A small **`combineSdks`** helper merges multiple signal starters into one `start`/`shutdown` object with shared global options (endpoint, headers, resource). ## Installation ```bash -npm install @opentelemetry/browser +npm install @opentelemetry/browser @opentelemetry/api ``` +`@opentelemetry/api` is a **peer dependency** (v1.9+). Install it alongside this package. + +## Package layout (subpath exports) + +The package does not rely on a single heavy entry point. Use explicit subpaths: + +| Subpath | Purpose | +| ------------------------------- | -------------------------------------------- | +| `@opentelemetry/browser/logs` | `startLogsSdk` — logs pipeline + OTLP export | +| `@opentelemetry/browser/traces` | `startTracesSdk` — traces + OTLP export | +| `@opentelemetry/browser/sdk` | `combineSdks` — compose multiple starters | + +## Core types + +- **`WebSdk`** — `{ shutdown(): Promise }` — returned by every starter; call `shutdown()` when the app unloads or telemetry should stop. +- **`WebSdkFactory`** — `(config?: T) => WebSdk` — type of functions like `startLogsSdk` / `startTracesSdk` when passed into `combineSdks`. + +Configuration interfaces are defined in the implementation (`GlobalConfig`, `LogsConfig`, `TracesConfig`). Highlights: + +- **`GlobalConfig`** (for `combineSdks` only): shared OTLP base URL (`otlpEndpoint`, default `http://localhost:4318`), `otlpHeaders`, optional `resource`, plus fields reserved for future use (e.g. diagnostics, limits). +- **`LogsConfig`**: `resource`, OTLP URL `otlpLogsEndpoint` (default `http://localhost:4318/v1/logs`), `otlpLogsHeaders`, batch processor tuning (`blrpScheduleDelay`, `blrpExportTimeout`, `blrpMaxQueueSize`, `blrpMaxExportBatchSize`), `logRecordLimits`. +- **`TracesConfig`**: optional `contextManager` and `textMapPropagator` (see `@opentelemetry/api`), `resource`, `sampler`, `spanLimits`, OTLP URL `otlpTracesEndpoint` (default `http://localhost:4318/v1/traces`), `otlpTracesHeaders`, batch span processor tuning (`bspScheduleDelay`, `bspExportTimeout`, `bspMaxQueueSize`, `bspMaxExportBatchSize`). + +## Usage + +### Logs only + +```ts +import { startLogsSdk } from '@opentelemetry/browser/logs'; + +const logsSdk = startLogsSdk({ + // e.g. otlpLogsEndpoint, otlpLogsHeaders, resource, logRecordLimits, … +}); + +// when tearing down (e.g. page unload) +await logsSdk.shutdown(); +``` + +`startLogsSdk` registers a global logger provider (`@opentelemetry/api-logs`) with a batch processor and OTLP HTTP exporter. + +### Traces only + +```ts +import { startTracesSdk } from '@opentelemetry/browser/traces'; + +const tracesSdk = startTracesSdk({ + // e.g. otlpTracesEndpoint, sampler, contextManager, textMapPropagator, … +}); + +await tracesSdk.shutdown(); +``` + +`startTracesSdk` registers a global tracer provider and optional context manager / propagator when provided. + +### Multiple signals with shared settings + +Use **`combineSdks`** from `@opentelemetry/browser/sdk` to pass one merged config (global options plus nested `logs` / `traces` sections) and get a single `shutdown`: + +```ts +import { combineSdks } from '@opentelemetry/browser/sdk'; +import { startLogsSdk } from '@opentelemetry/browser/logs'; +import { startTracesSdk } from '@opentelemetry/browser/traces'; + +const startMySdk = combineSdks({ + logs: startLogsSdk, + traces: startTracesSdk, +}); + +const mySdk = startMySdk({ + otlpEndpoint: 'https://otel.example.com:4318', + otlpHeaders: { Authorization: 'Bearer …' }, + logs: { + otlpLogsHeaders: { 'x-logs': 'foo' }, + }, + traces: { + otlpTracesHeaders: { 'x-traces': 'bar' }, + }, +}); + +await mySdk.shutdown(); +``` + +Behavior notes: + +- If you omit signal-specific OTLP URLs, **`otlpEndpoint`** is used as a base and paths `/v1/logs` and `/v1/traces` are applied automatically. +- **`otlpHeaders`** at the global level is applied to each signal unless overridden by `otlpLogsHeaders` / `otlpTracesHeaders`. +- A default **`resource`** is applied when using `combineSdks` if you do not set `resource` globally or per signal. + +## After startup + +Use the standard OpenTelemetry APIs for your signals, for example `@opentelemetry/api` for traces and `@opentelemetry/api-logs` for logs, now that global providers are registered. ## License -Apache 2.0 - See [LICENSE][license-url] for more information. +Apache 2.0 — see [LICENSE][license-url]. [discussions-url]: https://github.com/open-telemetry/opentelemetry-browser/discussions/landing [license-url]: https://github.com/open-telemetry/opentelemetry-browser/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/browser-instrumentation -[npm-img]: https://badge.fury.io/js/%40opentelemetry%2Fbrowser-instrumentation.svg +[npm-url]: https://www.npmjs.com/package/@opentelemetry/browser +[npm-img]: https://badge.fury.io/js/%40opentelemetry%2Fbrowser.svg From 27bff9ae7e29eb1a9e3b55330f05eb05d581b02d Mon Sep 17 00:00:00 2001 From: David Luna Date: Fri, 8 May 2026 16:51:56 +0200 Subject: [PATCH 18/20] chore: group processor and export config options --- packages/browser/src/logs.test.ts | 18 ++++++---- packages/browser/src/logs.ts | 22 ++++++++----- packages/browser/src/sdk.ts | 41 +++++++++++++++++------ packages/browser/src/traces.test.ts | 18 ++++++---- packages/browser/src/traces.ts | 21 +++++++----- packages/browser/src/types.ts | 51 ++++++++++++++++++----------- 6 files changed, 113 insertions(+), 58 deletions(-) diff --git a/packages/browser/src/logs.test.ts b/packages/browser/src/logs.test.ts index 093312e..64c7872 100644 --- a/packages/browser/src/logs.test.ts +++ b/packages/browser/src/logs.test.ts @@ -58,8 +58,10 @@ describe('startLogsSdk', () => { it('should use the default configuration for exporters', async () => { // Act logsSdk = startLogsSdk({ - // NOTE: we set a short delay to speed up tests and avoid test timeouts - blrpScheduleDelay: BLRP_SCHEDULE_DELAY, + processorConfig: { + // NOTE: we set a short delay to speed up tests and avoid test timeouts + scheduledDelayMillis: BLRP_SCHEDULE_DELAY, + }, }); logs.getLogger('logs-sdk-test').emit({ eventName: 'test' }); await new Promise((r) => setTimeout(r, BLRP_SCHEDULE_DELAY + 5)); @@ -80,10 +82,14 @@ describe('startLogsSdk', () => { it('should accept signal specific OTLP endpoint and headers', async () => { // Act logsSdk = startLogsSdk({ - // NOTE: we set a short delay to speed up tests and avoid test timeouts - blrpScheduleDelay: BLRP_SCHEDULE_DELAY, - otlpLogsEndpoint: 'http://otlp-signal-endpoint:4318/v1/logs', - otlpLogsHeaders: { bar: 'baz' }, + processorConfig: { + // NOTE: we set a short delay to speed up tests and avoid test timeouts + scheduledDelayMillis: BLRP_SCHEDULE_DELAY, + }, + exportConfig: { + url: 'http://otlp-signal-endpoint:4318/v1/logs', + headers: { bar: 'baz' }, + }, }); logs.getLogger('logs-sdk-test').emit({ eventName: 'test' }); await new Promise((r) => setTimeout(r, BLRP_SCHEDULE_DELAY + 5)); diff --git a/packages/browser/src/logs.ts b/packages/browser/src/logs.ts index dba3904..1310049 100644 --- a/packages/browser/src/logs.ts +++ b/packages/browser/src/logs.ts @@ -19,18 +19,24 @@ const DEFAULT_LOGS_OTLP_ENDPOINT = 'http://localhost:4318/v1/logs'; * @returns {WebSdk} */ export function startLogsSdk(config?: LogsConfig): WebSdk { - const logsEndpoint = config?.otlpLogsEndpoint || DEFAULT_LOGS_OTLP_ENDPOINT; + const logsEndpoint = config?.exportConfig?.url || DEFAULT_LOGS_OTLP_ENDPOINT; + const logsProcessor = new BatchLogRecordProcessor( new OTLPLogExporter({ url: logsEndpoint, - headers: config?.otlpLogsHeaders, + headers: config?.exportConfig?.headers, }), - { - scheduledDelayMillis: config?.blrpScheduleDelay, - exportTimeoutMillis: config?.blrpExportTimeout, - maxExportBatchSize: config?.blrpMaxExportBatchSize, - maxQueueSize: config?.blrpMaxQueueSize, - }, + Object.assign( + // defaults + { + scheduledDelayMillis: 1_000, + exportTimeoutMillis: 30_000, + maxExportBatchSize: 512, + maxQueueSize: 2048, + }, + // user defined + config?.processorConfig, + ), ); const loggerProvider = new LoggerProvider({ resource: config?.resource, diff --git a/packages/browser/src/sdk.ts b/packages/browser/src/sdk.ts index ad44140..949c2f7 100644 --- a/packages/browser/src/sdk.ts +++ b/packages/browser/src/sdk.ts @@ -31,22 +31,35 @@ export function combineSdks( return function startSdk(config?: GlobalConfig & ConfigsFor) { // Check the global config and set defaults const globalConfig = (config || {}) as GlobalConfig; - globalConfig.otlpEndpoint ??= DEFAULT_OTLP_ENDOINT; + + // Export + globalConfig.exportConfig = Object.assign( + { endpoint: DEFAULT_OTLP_ENDOINT }, + globalConfig.exportConfig, + ); // TODO: accept resource detectors? globalConfig.resource ??= defaultResource(); const sdks: WebSdk[] = []; - const otlpUrl = new URL(globalConfig.otlpEndpoint); + const endpointUrl = new URL(globalConfig.exportConfig!.url!); // Start logs if (factories.logs) { const logsConfig = (config?.logs || {}) as LogsConfig; - if (!logsConfig.otlpLogsEndpoint) { - otlpUrl.pathname = '/v1/logs'; - logsConfig.otlpLogsEndpoint = otlpUrl.href; + const isGenericEndpoint = !logsConfig.exportConfig?.url; + + // Merge export configs + logsConfig.exportConfig = Object.assign( + {}, + globalConfig.exportConfig, + logsConfig.exportConfig, + ); + // Set the path if endpoint comes from general config + if (isGenericEndpoint) { + endpointUrl.pathname = '/v1/logs'; + logsConfig.exportConfig.url = endpointUrl.href; } - logsConfig.otlpLogsHeaders ??= globalConfig.otlpHeaders; logsConfig.resource ??= globalConfig.resource; sdks.push(factories.logs(logsConfig)); } @@ -54,11 +67,19 @@ export function combineSdks( // Start traces if (factories.traces) { const tracesConfig = (config?.traces || {}) as TracesConfig; - if (!tracesConfig.otlpTracesEndpoint) { - otlpUrl.pathname = '/v1/traces'; - tracesConfig.otlpTracesEndpoint = otlpUrl.href; + const isGenericEndpoint = !tracesConfig.exportConfig?.url; + + // Merge export configs + tracesConfig.exportConfig = Object.assign( + {}, + globalConfig.exportConfig, + tracesConfig.exportConfig, + ); + // Set the path if endpoint comes from general config + if (isGenericEndpoint) { + endpointUrl.pathname = '/v1/traces'; + tracesConfig.exportConfig.url = endpointUrl.href; } - tracesConfig.otlpTracesHeaders ??= globalConfig.otlpHeaders; tracesConfig.resource ??= globalConfig.resource; sdks.push(factories.traces(tracesConfig)); } diff --git a/packages/browser/src/traces.test.ts b/packages/browser/src/traces.test.ts index f0f6d10..8e00ce2 100644 --- a/packages/browser/src/traces.test.ts +++ b/packages/browser/src/traces.test.ts @@ -60,8 +60,10 @@ describe('startTracesSdk', () => { it('should use the default configuration for exporters', async () => { // Act tracesSdk = startTracesSdk({ - // NOTE: we set a short delay to speed up tests and avoid test timeouts - bspScheduleDelay: BSP_SCHEDULE_DELAY, + processorConfig: { + // NOTE: we set a short delay to speed up tests and avoid test timeouts + scheduledDelayMillis: BSP_SCHEDULE_DELAY, + }, }); trace.getTracer('traces-sdk-test').startSpan('test').end(); @@ -83,10 +85,14 @@ describe('startTracesSdk', () => { it('should accept signal specific OTLP endpoint and headers', async () => { // Act tracesSdk = startTracesSdk({ - // NOTE: we set a short delay to speed up tests and avoid test timeouts - bspScheduleDelay: BSP_SCHEDULE_DELAY, - otlpTracesEndpoint: 'http://otlp-signal-endpoint:4318/v1/traces', - otlpTracesHeaders: { bar: 'baz' }, + processorConfig: { + // NOTE: we set a short delay to speed up tests and avoid test timeouts + scheduledDelayMillis: BSP_SCHEDULE_DELAY, + }, + exportConfig: { + url: 'http://otlp-signal-endpoint:4318/v1/traces', + headers: { bar: 'baz' }, + }, }); trace.getTracer('traces-sdk-test').startSpan('test').end(); await new Promise((r) => setTimeout(r, BSP_SCHEDULE_DELAY + 5)); diff --git a/packages/browser/src/traces.ts b/packages/browser/src/traces.ts index b8659c7..c7a4fec 100644 --- a/packages/browser/src/traces.ts +++ b/packages/browser/src/traces.ts @@ -16,19 +16,24 @@ const DEFAULT_TRACES_OTLP_ENDOINT = 'http://localhost:4318/v1/traces'; export function startTracesSdk(config?: TracesConfig): WebSdk { const tracesEndpoint = - config?.otlpTracesEndpoint || DEFAULT_TRACES_OTLP_ENDOINT; + config?.exportConfig?.url || DEFAULT_TRACES_OTLP_ENDOINT; const spanProcessor = new BatchSpanProcessor( new OTLPTraceExporter({ url: tracesEndpoint, - headers: config?.otlpTracesHeaders, + headers: config?.exportConfig?.headers, }), - { - scheduledDelayMillis: config?.bspScheduleDelay, - exportTimeoutMillis: config?.bspExportTimeout, - maxExportBatchSize: config?.bspMaxExportBatchSize, - maxQueueSize: config?.bspMaxQueueSize, - }, + Object.assign( + // defaults + { + scheduledDelayMillis: 1_000, + exportTimeoutMillis: 30_000, + maxExportBatchSize: 512, + maxQueueSize: 2048, + }, + // user defined + config?.processorConfig, + ), ); const tracerProvider = new BasicTracerProvider({ diff --git a/packages/browser/src/types.ts b/packages/browser/src/types.ts index d0595a4..4552e39 100644 --- a/packages/browser/src/types.ts +++ b/packages/browser/src/types.ts @@ -10,7 +10,29 @@ import type { } from '@opentelemetry/api'; import type { Resource } from '@opentelemetry/resources'; import type { LogRecordLimits } from '@opentelemetry/sdk-logs'; -import type { Sampler, SpanLimits } from '@opentelemetry/sdk-trace-base'; +import type { + GeneralLimits, + Sampler, + SpanLimits, +} from '@opentelemetry/sdk-trace-base'; + +/** + * Export configuration. Can be used globally or per signal + */ +export interface ExportConfig { + url?: string; + headers?: Record; +} + +/** + * Batch processor configuration. Can be used globally or per signal + */ +export interface ProcessorConfig { + scheduledDelayMillis?: number; + exportTimeoutMillis?: number; + maxQueueSize?: number; + maxExportBatchSize?: number; +} export interface GlobalConfig { disabled?: boolean; @@ -19,13 +41,11 @@ export interface GlobalConfig { serviceName?: string; resource?: Resource; // Export - otlpEndpoint?: string; - otlpHeaders?: Record; - // add other globals for queue/batch size + exportConfig?: ExportConfig; + // add other globals for queue/batch size? - // Global Limits - attrLengthLimit?: number; - attrCountLimit?: number; + // General Limits + generalLimits: GeneralLimits; // Basic options that could translate to more complex ones // in specific signals like @@ -33,18 +53,13 @@ export interface GlobalConfig { // and maybe somethign else for other signals??? (sampling logs?) // sampleRate?: number; } - export interface LogsConfig { // Resource & Entities related resource?: Resource; // Processor - blrpScheduleDelay?: number; - blrpExportTimeout?: number; - blrpMaxQueueSize?: number; - blrpMaxExportBatchSize?: number; + processorConfig?: ProcessorConfig; // Export - otlpLogsEndpoint?: string; - otlpLogsHeaders?: Record; + exportConfig?: ExportConfig; // Limits logRecordLimits?: LogRecordLimits; } @@ -58,13 +73,9 @@ export interface TracesConfig { // Sampler sampler?: Sampler; // Processor - bspScheduleDelay?: number; - bspExportTimeout?: number; - bspMaxQueueSize?: number; - bspMaxExportBatchSize?: number; + processorConfig?: ProcessorConfig; // Export - otlpTracesEndpoint?: string; - otlpTracesHeaders?: Record; + exportConfig?: ExportConfig; // Limits spanLimits?: SpanLimits; } From b185cf31278fa991d4523b9216c13121cfaa2294 Mon Sep 17 00:00:00 2001 From: Jared Freeze Date: Fri, 22 May 2026 11:31:39 -0500 Subject: [PATCH 19/20] fix(deps): pin fflate via @arethetypeswrong/core override (#287) --- package-lock.json | 2 +- package.json | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 43f43e4..16fc4c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6945,7 +6945,7 @@ }, "packages/instrumentation": { "name": "@opentelemetry/browser-instrumentation", - "version": "0.5.1", + "version": "0.5.2", "license": "Apache-2.0", "dependencies": { "@opentelemetry/api-logs": "^0.218.0", diff --git a/package.json b/package.json index 2416004..a9fd673 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,11 @@ "peerDependencies": { "typescript": "^6.0.3" }, + "overrides": { + "@arethetypeswrong/core": { + "fflate": "0.8.2" + } + }, "workspaces": [ "examples/*", "packages/*", From d46ae7394963b08703f488695b99550fa34504a5 Mon Sep 17 00:00:00 2001 From: David Luna Date: Fri, 22 May 2026 19:03:37 +0200 Subject: [PATCH 20/20] chore: add startBrowserSdk method --- packages/browser/README.md | 48 +++++++++---------- packages/browser/package.json | 1 + packages/browser/src/logs.ts | 12 +---- .../src/{propagator.ts => propagation.ts} | 11 ++--- packages/browser/src/sdk.ts | 18 ++++++- packages/browser/src/traces.ts | 18 ++----- packages/browser/src/types.ts | 11 +++-- 7 files changed, 58 insertions(+), 61 deletions(-) rename packages/browser/src/{propagator.ts => propagation.ts} (51%) diff --git a/packages/browser/README.md b/packages/browser/README.md index 151b453..a1a71e1 100644 --- a/packages/browser/README.md +++ b/packages/browser/README.md @@ -21,18 +21,15 @@ The package does not rely on a single heavy entry point. Use explicit subpaths: | ------------------------------- | -------------------------------------------- | | `@opentelemetry/browser/logs` | `startLogsSdk` — logs pipeline + OTLP export | | `@opentelemetry/browser/traces` | `startTracesSdk` — traces + OTLP export | -| `@opentelemetry/browser/sdk` | `combineSdks` — compose multiple starters | +| `@opentelemetry/browser/sdk` | `startBrowserSdk` — both signals composed | ## Core types -- **`WebSdk`** — `{ shutdown(): Promise }` — returned by every starter; call `shutdown()` when the app unloads or telemetry should stop. -- **`WebSdkFactory`** — `(config?: T) => WebSdk` — type of functions like `startLogsSdk` / `startTracesSdk` when passed into `combineSdks`. - Configuration interfaces are defined in the implementation (`GlobalConfig`, `LogsConfig`, `TracesConfig`). Highlights: -- **`GlobalConfig`** (for `combineSdks` only): shared OTLP base URL (`otlpEndpoint`, default `http://localhost:4318`), `otlpHeaders`, optional `resource`, plus fields reserved for future use (e.g. diagnostics, limits). -- **`LogsConfig`**: `resource`, OTLP URL `otlpLogsEndpoint` (default `http://localhost:4318/v1/logs`), `otlpLogsHeaders`, batch processor tuning (`blrpScheduleDelay`, `blrpExportTimeout`, `blrpMaxQueueSize`, `blrpMaxExportBatchSize`), `logRecordLimits`. -- **`TracesConfig`**: optional `contextManager` and `textMapPropagator` (see `@opentelemetry/api`), `resource`, `sampler`, `spanLimits`, OTLP URL `otlpTracesEndpoint` (default `http://localhost:4318/v1/traces`), `otlpTracesHeaders`, batch span processor tuning (`bspScheduleDelay`, `bspExportTimeout`, `bspMaxQueueSize`, `bspMaxExportBatchSize`). +- **`GlobalConfig`** (for `startBrowserSdk` only): shared OTLP export configuration (base `url`, default `http://localhost:4318`), optional `resource`, plus fields reserved for future use (e.g. diagnostics, limits). +- **`LogsConfig`**: `resource`, OTLP export configuration (`url`, default `http://localhost:4318/v1/logs`), batch processor tuning `logRecordLimits`. +- **`TracesConfig`**: optional `contextManager` and `textMapPropagator` (see `@opentelemetry/api`), `resource`, `sampler`, `spanLimits`, OTLP export configuration (`url`, default `http://localhost:4318/v1/traces`), batch span processor tuning . ## Usage @@ -42,7 +39,7 @@ Configuration interfaces are defined in the implementation (`GlobalConfig`, `Log import { startLogsSdk } from '@opentelemetry/browser/logs'; const logsSdk = startLogsSdk({ - // e.g. otlpLogsEndpoint, otlpLogsHeaders, resource, logRecordLimits, … + // e.g. exportConfig, resource, logRecordLimits, … }); // when tearing down (e.g. page unload) @@ -57,7 +54,7 @@ await logsSdk.shutdown(); import { startTracesSdk } from '@opentelemetry/browser/traces'; const tracesSdk = startTracesSdk({ - // e.g. otlpTracesEndpoint, sampler, contextManager, textMapPropagator, … + // e.g. exportConfig, sampler, contextManager, textMapPropagator, … }); await tracesSdk.shutdown(); @@ -67,26 +64,25 @@ await tracesSdk.shutdown(); ### Multiple signals with shared settings -Use **`combineSdks`** from `@opentelemetry/browser/sdk` to pass one merged config (global options plus nested `logs` / `traces` sections) and get a single `shutdown`: +Use **`startBrowserSdk`** from `@opentelemetry/browser/sdk` to pass one merged config (global options plus nested `logs` / `traces` sections) and get a single `shutdown`: ```ts -import { combineSdks } from '@opentelemetry/browser/sdk'; -import { startLogsSdk } from '@opentelemetry/browser/logs'; -import { startTracesSdk } from '@opentelemetry/browser/traces'; +import { startBrowserSdk } from '@opentelemetry/browser/sdk'; -const startMySdk = combineSdks({ - logs: startLogsSdk, - traces: startTracesSdk, -}); - -const mySdk = startMySdk({ - otlpEndpoint: 'https://otel.example.com:4318', - otlpHeaders: { Authorization: 'Bearer …' }, +const mySdk = startBrowserSdk({ + exportConfg: { + url: 'https://otel.example.com:4318', + headers: { Authorization: 'Bearer …' }, + }, logs: { - otlpLogsHeaders: { 'x-logs': 'foo' }, + exportConfig: { + headers: { 'x-logs': 'foo' }, + }, }, traces: { - otlpTracesHeaders: { 'x-traces': 'bar' }, + exportConfig: { + headers: { 'x-traces': 'bar' }, + }, }, }); @@ -95,9 +91,9 @@ await mySdk.shutdown(); Behavior notes: -- If you omit signal-specific OTLP URLs, **`otlpEndpoint`** is used as a base and paths `/v1/logs` and `/v1/traces` are applied automatically. -- **`otlpHeaders`** at the global level is applied to each signal unless overridden by `otlpLogsHeaders` / `otlpTracesHeaders`. -- A default **`resource`** is applied when using `combineSdks` if you do not set `resource` globally or per signal. +- If you omit signal-specific OTLP export configuration, the global export URL is used as a base and paths `/v1/logs` and `/v1/traces` are applied automatically. +- **`headers`** at the global level is applied to each signal unless overridden by signal specific ones. +- A default **`resource`** is applied when using `startBrowserSdk` if you do not set `resource` globally or per signal. ## After startup diff --git a/packages/browser/package.json b/packages/browser/package.json index 19f40ad..89d134f 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -19,6 +19,7 @@ }, "type": "module", "exports": { + "./propagation": "./dist/propagation.js", "./logs": "./dist/logs.js", "./traces": "./dist/traces.js", "./sdk": "./dist/sdk.js" diff --git a/packages/browser/src/logs.ts b/packages/browser/src/logs.ts index 1310049..09bec59 100644 --- a/packages/browser/src/logs.ts +++ b/packages/browser/src/logs.ts @@ -26,17 +26,7 @@ export function startLogsSdk(config?: LogsConfig): WebSdk { url: logsEndpoint, headers: config?.exportConfig?.headers, }), - Object.assign( - // defaults - { - scheduledDelayMillis: 1_000, - exportTimeoutMillis: 30_000, - maxExportBatchSize: 512, - maxQueueSize: 2048, - }, - // user defined - config?.processorConfig, - ), + config?.processorConfig, ); const loggerProvider = new LoggerProvider({ resource: config?.resource, diff --git a/packages/browser/src/propagator.ts b/packages/browser/src/propagation.ts similarity index 51% rename from packages/browser/src/propagator.ts rename to packages/browser/src/propagation.ts index 4318dbd..1af0921 100644 --- a/packages/browser/src/propagator.ts +++ b/packages/browser/src/propagation.ts @@ -5,19 +5,16 @@ import type { TextMapPropagator } from '@opentelemetry/api'; import { - CompositePropagator, W3CBaggagePropagator, W3CTraceContextPropagator, } from '@opentelemetry/core'; /** - * Returns the default propagator which is a composition of: + * Returns the default propagators: * - W3CTraceContextPropagator - * -W3CBaggagePropagator + * - W3CBaggagePropagator * @returns {TextMapPropagator} */ -export function getDefaultPropagator(): TextMapPropagator { - return new CompositePropagator({ - propagators: [new W3CTraceContextPropagator(), new W3CBaggagePropagator()], - }); +export function getDefaultPropagators(): TextMapPropagator[] { + return [new W3CTraceContextPropagator(), new W3CBaggagePropagator()]; } diff --git a/packages/browser/src/sdk.ts b/packages/browser/src/sdk.ts index 949c2f7..62f25f0 100644 --- a/packages/browser/src/sdk.ts +++ b/packages/browser/src/sdk.ts @@ -4,6 +4,8 @@ */ import { defaultResource } from '@opentelemetry/resources'; +import { startLogsSdk } from './logs.ts'; +import { startTracesSdk } from './traces.ts'; import type { GlobalConfig, LogsConfig, @@ -23,7 +25,11 @@ type ConfigsFor = Partial<{ const DEFAULT_OTLP_ENDOINT = 'http://localhost:4318'; -export function combineSdks( +/** + * Combines different SDK factory functions into a single one + * which accepts a global configuration along + */ +function combineSdks( factories: T, ): WebSdkFactory> { // The returned function will transform some of the global @@ -93,3 +99,13 @@ export function combineSdks( }; }; } + +/** + * Combination of all singal SDKs into one. A shorthand for users to + * start with all signals allowing them to pass some global configuration + * options. + */ +export const startBrowserSdk = combineSdks({ + logs: startLogsSdk, + traces: startTracesSdk, +}); diff --git a/packages/browser/src/traces.ts b/packages/browser/src/traces.ts index c7a4fec..569a6f6 100644 --- a/packages/browser/src/traces.ts +++ b/packages/browser/src/traces.ts @@ -4,6 +4,7 @@ */ import { context, propagation, trace } from '@opentelemetry/api'; +import { CompositePropagator } from '@opentelemetry/core'; import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; import { BasicTracerProvider, @@ -23,17 +24,7 @@ export function startTracesSdk(config?: TracesConfig): WebSdk { url: tracesEndpoint, headers: config?.exportConfig?.headers, }), - Object.assign( - // defaults - { - scheduledDelayMillis: 1_000, - exportTimeoutMillis: 30_000, - maxExportBatchSize: 512, - maxQueueSize: 2048, - }, - // user defined - config?.processorConfig, - ), + config?.processorConfig, ); const tracerProvider = new BasicTracerProvider({ @@ -46,8 +37,9 @@ export function startTracesSdk(config?: TracesConfig): WebSdk { }); trace.setGlobalTracerProvider(tracerProvider); - if (config?.textMapPropagator) { - propagation.setGlobalPropagator(config.textMapPropagator); + if (config?.propagators) { + const { propagators } = config; + propagation.setGlobalPropagator(new CompositePropagator({ propagators })); } if (config?.contextManager) { diff --git a/packages/browser/src/types.ts b/packages/browser/src/types.ts index 4552e39..04a89d6 100644 --- a/packages/browser/src/types.ts +++ b/packages/browser/src/types.ts @@ -34,16 +34,21 @@ export interface ProcessorConfig { maxExportBatchSize?: number; } +/** + * The global configuration of the SDK. This type is enhanced + * by the `combineSdks` function by adding a key for each + * signal used (logs, traces). Do not add a "logs" or "traces" key + * here to avoid type collision. + */ export interface GlobalConfig { disabled?: boolean; logLevel?: DiagLogLevel; // Resource & Entities related serviceName?: string; + serviceVersion?: string; resource?: Resource; // Export exportConfig?: ExportConfig; - // add other globals for queue/batch size? - // General Limits generalLimits: GeneralLimits; @@ -67,7 +72,7 @@ export interface LogsConfig { export interface TracesConfig { // Context and Propagation contextManager?: ContextManager; - textMapPropagator?: TextMapPropagator; + propagators?: TextMapPropagator[]; // Resource & Entities related resource?: Resource; // Sampler