diff --git a/examples/tracer-web/examples/zipkin/index.html b/examples/tracer-web/examples/zipkin/index.html
new file mode 100644
index 0000000000..bf98b07433
--- /dev/null
+++ b/examples/tracer-web/examples/zipkin/index.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+ Zipkin Exporter Example
+
+
+
+
+
+ Example of using Web Tracer with Zipkin Exporter
+
+
+
+
+
+
+
diff --git a/examples/tracer-web/examples/zipkin/index.js b/examples/tracer-web/examples/zipkin/index.js
new file mode 100644
index 0000000000..410749b00a
--- /dev/null
+++ b/examples/tracer-web/examples/zipkin/index.js
@@ -0,0 +1,23 @@
+import { ConsoleSpanExporter, SimpleSpanProcessor } from '@opentelemetry/tracing';
+import { WebTracerProvider } from '@opentelemetry/web';
+import { ZipkinExporter } from '@opentelemetry/exporter-zipkin';
+
+const provider = new WebTracerProvider();
+provider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter()));
+provider.addSpanProcessor(new SimpleSpanProcessor(new ZipkinExporter()));
+
+provider.register();
+
+const tracer = provider.getTracer('example-tracer-web');
+
+const prepareClickEvent = () => {
+ const element = document.getElementById('button1');
+
+ const onClick = () => {
+ const span = tracer.startSpan('foo');
+ span.end();
+ };
+ element.addEventListener('click', onClick);
+};
+
+window.addEventListener('load', prepareClickEvent);
diff --git a/examples/tracer-web/package.json b/examples/tracer-web/package.json
index 005ca3216a..711d2e5292 100644
--- a/examples/tracer-web/package.json
+++ b/examples/tracer-web/package.json
@@ -37,6 +37,7 @@
"@opentelemetry/context-zone": "^0.11.0",
"@opentelemetry/core": "^0.11.0",
"@opentelemetry/exporter-collector": "^0.11.0",
+ "@opentelemetry/exporter-zipkin": "^0.11.0",
"@opentelemetry/metrics": "^0.11.0",
"@opentelemetry/plugin-document-load": "^0.9.0",
"@opentelemetry/plugin-fetch": "^0.11.0",
diff --git a/examples/tracer-web/webpack.config.js b/examples/tracer-web/webpack.config.js
index 3859f2f8fd..00773db28b 100644
--- a/examples/tracer-web/webpack.config.js
+++ b/examples/tracer-web/webpack.config.js
@@ -12,6 +12,7 @@ const common = {
fetch: 'examples/fetch/index.js',
'xml-http-request': 'examples/xml-http-request/index.js',
'user-interaction': 'examples/user-interaction/index.js',
+ zipkin: 'examples/zipkin/index.js',
},
output: {
path: path.resolve(__dirname, 'dist'),
diff --git a/packages/opentelemetry-exporter-zipkin/.eslintrc.js b/packages/opentelemetry-exporter-zipkin/.eslintrc.js
index f726f3becb..9dfe62f9b8 100644
--- a/packages/opentelemetry-exporter-zipkin/.eslintrc.js
+++ b/packages/opentelemetry-exporter-zipkin/.eslintrc.js
@@ -1,7 +1,9 @@
module.exports = {
"env": {
"mocha": true,
- "node": true
+ "commonjs": true,
+ "node": true,
+ "browser": true
},
...require('../../eslint.config.js')
}
diff --git a/packages/opentelemetry-exporter-zipkin/README.md b/packages/opentelemetry-exporter-zipkin/README.md
index ccd4404c67..1c1466ae5e 100644
--- a/packages/opentelemetry-exporter-zipkin/README.md
+++ b/packages/opentelemetry-exporter-zipkin/README.md
@@ -16,7 +16,7 @@ OpenTelemetry Zipkin Trace Exporter allows the user to send collected traces to
npm install --save @opentelemetry/exporter-zipkin
```
-## Usage
+## Usage in Node and Browser
Install the exporter on your application and pass the options. `serviceName` is an optional string. If omitted, the exporter will first try to get the service name from the Resource. If no service name can be detected on the Resource, a fallback name of "OpenTelemetry Service" will be used.
diff --git a/packages/opentelemetry-exporter-zipkin/karma.conf.js b/packages/opentelemetry-exporter-zipkin/karma.conf.js
new file mode 100644
index 0000000000..455b1437c8
--- /dev/null
+++ b/packages/opentelemetry-exporter-zipkin/karma.conf.js
@@ -0,0 +1,26 @@
+/*!
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const karmaWebpackConfig = require('../../karma.webpack');
+const karmaBaseConfig = require('../../karma.base');
+
+module.exports = (config) => {
+ config.set(Object.assign({}, karmaBaseConfig, {
+ webpack: karmaWebpackConfig,
+ files: ['test/browser/index-webpack.ts'],
+ preprocessors: { 'test/browser/index-webpack.ts': ['webpack'] }
+ }))
+};
diff --git a/packages/opentelemetry-exporter-zipkin/package.json b/packages/opentelemetry-exporter-zipkin/package.json
index a662012391..57d002ea48 100644
--- a/packages/opentelemetry-exporter-zipkin/package.json
+++ b/packages/opentelemetry-exporter-zipkin/package.json
@@ -5,21 +5,29 @@
"main": "build/src/index.js",
"types": "build/src/index.d.ts",
"repository": "open-telemetry/opentelemetry-js",
+ "browser": {
+ "./src/platform/index.ts": "./src/platform/browser/index.ts",
+ "./build/src/platform/index.js": "./build/src/platform/browser/index.js"
+ },
"scripts": {
- "test": "nyc ts-mocha -p tsconfig.json 'test/**/*.test.ts'",
- "tdd": "npm run test -- --watch-extensions ts --watch",
"clean": "rimraf build/*",
+ "codecov": "nyc report --reporter=json && codecov -f coverage/*.json -p ../../",
+ "codecov:browser": "nyc report --reporter=json && codecov -f coverage/*.json -p ../../",
+ "compile": "npm run version:update && tsc -p .",
"lint": "eslint . --ext .ts",
"lint:fix": "eslint . --ext .ts --fix",
- "codecov": "nyc report --reporter=json && codecov -f coverage/*.json -p ../../",
"precompile": "tsc --version",
+ "prepare": "npm run compile",
+ "tdd": "npm run test -- --watch-extensions ts --watch",
+ "test": "nyc ts-mocha -p tsconfig.json 'test/**/*.test.ts' --exclude 'test/browser/**/*.ts'",
+ "test:browser": "nyc karma start --single-run",
"version:update": "node ../../scripts/version-update.js",
- "compile": "npm run version:update && tsc -p .",
- "prepare": "npm run compile"
+ "watch": "tsc -w"
},
"keywords": [
"opentelemetry",
"nodejs",
+ "browser",
"tracing",
"profiling"
],
@@ -40,17 +48,33 @@
"access": "public"
},
"devDependencies": {
+ "@babel/core": "7.11.0",
"@types/mocha": "8.0.2",
"@types/node": "14.0.27",
+ "@types/sinon": "9.0.4",
+ "@types/webpack-env": "1.15.2",
+ "babel-loader": "8.1.0",
"codecov": "3.7.2",
"gts": "2.0.2",
+ "istanbul-instrumenter-loader": "3.0.1",
+ "karma": "5.1.1",
+ "karma-chrome-launcher": "3.1.0",
+ "karma-coverage-istanbul-reporter": "3.0.3",
+ "karma-mocha": "2.0.1",
+ "karma-spec-reporter": "0.0.32",
+ "karma-webpack": "4.0.2",
"mocha": "7.2.0",
"nock": "12.0.3",
"nyc": "15.1.0",
"rimraf": "3.0.2",
+ "sinon": "9.0.2",
+ "ts-loader": "8.0.1",
"ts-mocha": "7.0.0",
"ts-node": "8.10.2",
- "typescript": "3.9.7"
+ "typescript": "3.9.7",
+ "webpack": "4.44.1",
+ "webpack-cli": "3.3.12",
+ "webpack-merge": "5.0.9"
},
"dependencies": {
"@opentelemetry/api": "^0.11.0",
diff --git a/packages/opentelemetry-exporter-zipkin/src/index.ts b/packages/opentelemetry-exporter-zipkin/src/index.ts
index 9a35ec9d07..4879fea44f 100644
--- a/packages/opentelemetry-exporter-zipkin/src/index.ts
+++ b/packages/opentelemetry-exporter-zipkin/src/index.ts
@@ -15,3 +15,4 @@
*/
export * from './zipkin';
+export * from './platform';
diff --git a/packages/opentelemetry-exporter-zipkin/test/e2e.test.ts b/packages/opentelemetry-exporter-zipkin/src/platform/browser/index.ts
similarity index 87%
rename from packages/opentelemetry-exporter-zipkin/test/e2e.test.ts
rename to packages/opentelemetry-exporter-zipkin/src/platform/browser/index.ts
index 96181daa51..3786b94827 100644
--- a/packages/opentelemetry-exporter-zipkin/test/e2e.test.ts
+++ b/packages/opentelemetry-exporter-zipkin/src/platform/browser/index.ts
@@ -14,6 +14,4 @@
* limitations under the License.
*/
-describe('Zipkin Exporter E2E', () => {
- it('should report spans to Zipkin server');
-});
+export * from './util';
diff --git a/packages/opentelemetry-exporter-zipkin/src/platform/browser/util.ts b/packages/opentelemetry-exporter-zipkin/src/platform/browser/util.ts
new file mode 100644
index 0000000000..a954fb2d42
--- /dev/null
+++ b/packages/opentelemetry-exporter-zipkin/src/platform/browser/util.ts
@@ -0,0 +1,131 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import * as api from '@opentelemetry/api';
+import { ExportResult } from '@opentelemetry/core';
+import * as zipkinTypes from '../../types';
+import { OT_REQUEST_HEADER } from '../../utils';
+
+/**
+ * Prepares send function that will send spans to the remote Zipkin service.
+ */
+export function prepareSend(
+ logger: api.Logger,
+ urlStr: string,
+ headers?: Record
+) {
+ let xhrHeaders: Record;
+ const useBeacon = navigator.sendBeacon && !headers;
+ if (headers) {
+ xhrHeaders = {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+ [OT_REQUEST_HEADER]: '1',
+ ...headers,
+ };
+ }
+
+ /**
+ * Send spans to the remote Zipkin service.
+ */
+ return function send(
+ zipkinSpans: zipkinTypes.Span[],
+ done: (result: ExportResult) => void
+ ) {
+ if (zipkinSpans.length === 0) {
+ logger.debug('Zipkin send with empty spans');
+ return done(ExportResult.SUCCESS);
+ }
+ const payload = JSON.stringify(zipkinSpans);
+ if (useBeacon) {
+ sendWithBeacon(payload, done, urlStr, logger);
+ } else {
+ sendWithXhr(payload, done, urlStr, logger, xhrHeaders);
+ }
+ };
+}
+
+/**
+ * Sends data using beacon
+ * @param data
+ * @param done
+ * @param urlStr
+ * @param logger
+ */
+function sendWithBeacon(
+ data: string,
+ done: (result: ExportResult) => void,
+ urlStr: string,
+ logger: api.Logger
+) {
+ if (navigator.sendBeacon(urlStr, data)) {
+ logger.debug('sendBeacon - can send', data);
+ done(ExportResult.SUCCESS);
+ } else {
+ logger.error('sendBeacon - cannot send', data);
+ done(ExportResult.FAILED_NOT_RETRYABLE);
+ }
+}
+
+/**
+ * Sends data using XMLHttpRequest
+ * @param data
+ * @param done
+ * @param urlStr
+ * @param logger
+ * @param xhrHeaders
+ */
+function sendWithXhr(
+ data: string,
+ done: (result: ExportResult) => void,
+ urlStr: string,
+ logger: api.Logger,
+ xhrHeaders: Record = {}
+) {
+ const xhr = new window.XMLHttpRequest();
+ xhr.open('POST', urlStr);
+ Object.entries(xhrHeaders).forEach(([k, v]) => {
+ xhr.setRequestHeader(k, v);
+ });
+
+ xhr.onreadystatechange = () => {
+ if (xhr.readyState === XMLHttpRequest.DONE) {
+ const statusCode = xhr.status || 0;
+ logger.debug(
+ 'Zipkin response status code: %d, body: %s',
+ statusCode,
+ data
+ );
+
+ if (xhr.status >= 200 && xhr.status < 400) {
+ return done(ExportResult.SUCCESS);
+ } else if (statusCode < 500) {
+ return done(ExportResult.FAILED_NOT_RETRYABLE);
+ } else {
+ return done(ExportResult.FAILED_RETRYABLE);
+ }
+ }
+ };
+
+ xhr.onerror = err => {
+ logger.error('Zipkin request error', err);
+ return done(ExportResult.FAILED_RETRYABLE);
+ };
+
+ // Issue request to remote service
+ logger.debug('Zipkin request payload: %s', data);
+ xhr.send(data);
+}
diff --git a/packages/opentelemetry-exporter-zipkin/src/platform/index.ts b/packages/opentelemetry-exporter-zipkin/src/platform/index.ts
new file mode 100644
index 0000000000..cdaf8858ce
--- /dev/null
+++ b/packages/opentelemetry-exporter-zipkin/src/platform/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export * from './node';
diff --git a/packages/opentelemetry-exporter-zipkin/src/platform/node/index.ts b/packages/opentelemetry-exporter-zipkin/src/platform/node/index.ts
new file mode 100644
index 0000000000..3786b94827
--- /dev/null
+++ b/packages/opentelemetry-exporter-zipkin/src/platform/node/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export * from './util';
diff --git a/packages/opentelemetry-exporter-zipkin/src/platform/node/util.ts b/packages/opentelemetry-exporter-zipkin/src/platform/node/util.ts
new file mode 100644
index 0000000000..89f740b8e2
--- /dev/null
+++ b/packages/opentelemetry-exporter-zipkin/src/platform/node/util.ts
@@ -0,0 +1,97 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import * as api from '@opentelemetry/api';
+import { ExportResult } from '@opentelemetry/core';
+import * as http from 'http';
+import * as https from 'https';
+import * as url from 'url';
+import * as zipkinTypes from '../../types';
+import { OT_REQUEST_HEADER } from '../../utils';
+
+/**
+ * Prepares send function that will send spans to the remote Zipkin service.
+ */
+export function prepareSend(
+ logger: api.Logger,
+ urlStr: string,
+ headers?: Record
+) {
+ const urlOpts = url.parse(urlStr);
+
+ const reqOpts: http.RequestOptions = Object.assign(
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ [OT_REQUEST_HEADER]: 1,
+ ...headers,
+ },
+ },
+ urlOpts
+ );
+
+ /**
+ * Send spans to the remote Zipkin service.
+ */
+ return function send(
+ zipkinSpans: zipkinTypes.Span[],
+ done: (result: ExportResult) => void
+ ) {
+ if (zipkinSpans.length === 0) {
+ logger.debug('Zipkin send with empty spans');
+ return done(ExportResult.SUCCESS);
+ }
+
+ const { request } = reqOpts.protocol === 'http:' ? http : https;
+ const req = request(reqOpts, (res: http.IncomingMessage) => {
+ let rawData = '';
+ res.on('data', chunk => {
+ rawData += chunk;
+ });
+ res.on('end', () => {
+ const statusCode = res.statusCode || 0;
+ logger.debug(
+ 'Zipkin response status code: %d, body: %s',
+ statusCode,
+ rawData
+ );
+
+ // Consider 2xx and 3xx as success.
+ if (statusCode < 400) {
+ return done(ExportResult.SUCCESS);
+ // Consider 4xx as failed non-retriable.
+ } else if (statusCode < 500) {
+ return done(ExportResult.FAILED_NOT_RETRYABLE);
+ // Consider 5xx as failed retriable.
+ } else {
+ return done(ExportResult.FAILED_RETRYABLE);
+ }
+ });
+ });
+
+ req.on('error', (err: Error) => {
+ logger.error('Zipkin request error', err);
+ return done(ExportResult.FAILED_RETRYABLE);
+ });
+
+ // Issue request to remote service
+ const payload = JSON.stringify(zipkinSpans);
+ logger.debug('Zipkin request payload: %s', payload);
+ req.write(payload, 'utf8');
+ req.end();
+ };
+}
diff --git a/packages/opentelemetry-exporter-zipkin/src/types.ts b/packages/opentelemetry-exporter-zipkin/src/types.ts
index 0433351491..7eb7eaeaf2 100644
--- a/packages/opentelemetry-exporter-zipkin/src/types.ts
+++ b/packages/opentelemetry-exporter-zipkin/src/types.ts
@@ -15,6 +15,7 @@
*/
import * as api from '@opentelemetry/api';
+import { ExportResult } from '@opentelemetry/core';
/**
* Exporter config
@@ -177,3 +178,11 @@ export enum SpanKind {
CONSUMER = 'CONSUMER',
PRODUCER = 'PRODUCER',
}
+
+/**
+ * interface for function that will send zipkin spans
+ */
+export type SendFunction = (
+ zipkinSpans: Span[],
+ done: (result: ExportResult) => void
+) => void;
diff --git a/packages/opentelemetry-exporter-zipkin/src/zipkin.ts b/packages/opentelemetry-exporter-zipkin/src/zipkin.ts
index 54010b3af3..18188565c4 100644
--- a/packages/opentelemetry-exporter-zipkin/src/zipkin.ts
+++ b/packages/opentelemetry-exporter-zipkin/src/zipkin.ts
@@ -15,19 +15,17 @@
*/
import * as api from '@opentelemetry/api';
-import * as http from 'http';
-import * as https from 'https';
-import * as url from 'url';
import { ExportResult, NoopLogger } from '@opentelemetry/core';
import { SpanExporter, ReadableSpan } from '@opentelemetry/tracing';
+import { prepareSend } from './platform/index';
import * as zipkinTypes from './types';
import {
toZipkinSpan,
statusCodeTagName,
statusDescriptionTagName,
} from './transform';
-import { OT_REQUEST_HEADER } from './utils';
import { SERVICE_RESOURCE } from '@opentelemetry/resources';
+
/**
* Zipkin Exporter
*/
@@ -37,26 +35,14 @@ export class ZipkinExporter implements SpanExporter {
private readonly _logger: api.Logger;
private readonly _statusCodeTagName: string;
private readonly _statusDescriptionTagName: string;
- private readonly _reqOpts: http.RequestOptions;
+ private _send: zipkinTypes.SendFunction;
private _serviceName?: string;
private _isShutdown: boolean;
constructor(config: zipkinTypes.ExporterConfig = {}) {
const urlStr = config.url || ZipkinExporter.DEFAULT_URL;
- const urlOpts = url.parse(urlStr);
-
this._logger = config.logger || new NoopLogger();
- this._reqOpts = Object.assign(
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- [OT_REQUEST_HEADER]: 1,
- ...config.headers,
- },
- },
- urlOpts
- );
+ this._send = prepareSend(this._logger, urlStr, config.headers);
this._serviceName = config.serviceName;
this._statusCodeTagName = config.statusCodeTagName || statusCodeTagName;
this._statusDescriptionTagName =
@@ -118,55 +104,4 @@ export class ZipkinExporter implements SpanExporter {
}
});
}
-
- /**
- * Send spans to the remote Zipkin service.
- */
- private _send(
- zipkinSpans: zipkinTypes.Span[],
- done: (result: ExportResult) => void
- ) {
- if (zipkinSpans.length === 0) {
- this._logger.debug('Zipkin send with empty spans');
- return done(ExportResult.SUCCESS);
- }
-
- const { request } = this._reqOpts.protocol === 'http:' ? http : https;
- const req = request(this._reqOpts, (res: http.IncomingMessage) => {
- let rawData = '';
- res.on('data', chunk => {
- rawData += chunk;
- });
- res.on('end', () => {
- const statusCode = res.statusCode || 0;
- this._logger.debug(
- 'Zipkin response status code: %d, body: %s',
- statusCode,
- rawData
- );
-
- // Consider 2xx and 3xx as success.
- if (statusCode < 400) {
- return done(ExportResult.SUCCESS);
- // Consider 4xx as failed non-retriable.
- } else if (statusCode < 500) {
- return done(ExportResult.FAILED_NOT_RETRYABLE);
- // Consider 5xx as failed retriable.
- } else {
- return done(ExportResult.FAILED_RETRYABLE);
- }
- });
- });
-
- req.on('error', (err: Error) => {
- this._logger.error('Zipkin request error', err);
- return done(ExportResult.FAILED_RETRYABLE);
- });
-
- // Issue request to remote service
- const payload = JSON.stringify(zipkinSpans);
- this._logger.debug('Zipkin request payload: %s', payload);
- req.write(payload, 'utf8');
- req.end();
- }
}
diff --git a/packages/opentelemetry-exporter-zipkin/test/browser/index-webpack.ts b/packages/opentelemetry-exporter-zipkin/test/browser/index-webpack.ts
new file mode 100644
index 0000000000..99100a0f6e
--- /dev/null
+++ b/packages/opentelemetry-exporter-zipkin/test/browser/index-webpack.ts
@@ -0,0 +1,23 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+const testsContext = require.context('../browser', true, /test$/);
+testsContext.keys().forEach(testsContext);
+
+const testsContextCommon = require.context('../common', true, /test$/);
+testsContextCommon.keys().forEach(testsContextCommon);
+
+const srcContext = require.context('.', true, /src$/);
+srcContext.keys().forEach(srcContext);
diff --git a/packages/opentelemetry-exporter-zipkin/test/browser/zipkin.test.ts b/packages/opentelemetry-exporter-zipkin/test/browser/zipkin.test.ts
new file mode 100644
index 0000000000..c47e69fa57
--- /dev/null
+++ b/packages/opentelemetry-exporter-zipkin/test/browser/zipkin.test.ts
@@ -0,0 +1,157 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { NoopLogger } from '@opentelemetry/core';
+import { ReadableSpan } from '@opentelemetry/tracing';
+import * as assert from 'assert';
+import * as sinon from 'sinon';
+import { ZipkinExporter } from '../../src';
+import * as zipkinTypes from '../../src/types';
+import {
+ ensureHeadersContain,
+ ensureSpanIsCorrect,
+ mockedReadableSpan,
+} from '../helper';
+
+const sendBeacon = navigator.sendBeacon;
+
+describe('Zipkin Exporter - web', () => {
+ let zipkinExporter: ZipkinExporter;
+ let zipkinConfig: zipkinTypes.ExporterConfig = {};
+ let spySend: sinon.SinonSpy;
+ let spyBeacon: sinon.SinonSpy;
+ let spans: ReadableSpan[];
+ let sandbox: sinon.SinonSandbox;
+
+ beforeEach(() => {
+ sandbox = sinon.createSandbox();
+ spySend = sandbox.stub(XMLHttpRequest.prototype, 'send');
+ spyBeacon = sandbox.stub(navigator, 'sendBeacon');
+ spans = [];
+ spans.push(Object.assign({}, mockedReadableSpan));
+ });
+
+ afterEach(() => {
+ sandbox.restore();
+ navigator.sendBeacon = sendBeacon;
+ });
+
+ describe('export', () => {
+ describe('when "sendBeacon" is available', () => {
+ beforeEach(() => {
+ zipkinExporter = new ZipkinExporter(zipkinConfig);
+ });
+
+ it('should successfully send the spans using sendBeacon', done => {
+ zipkinExporter.export(spans, () => {});
+
+ setTimeout(() => {
+ const args = spyBeacon.args[0];
+ const body = args[1];
+ const json = JSON.parse(body) as any;
+ ensureSpanIsCorrect(json[0]);
+ assert.strictEqual(spyBeacon.callCount, 1);
+ assert.strictEqual(spySend.callCount, 0);
+
+ done();
+ });
+ });
+ });
+
+ describe('when "sendBeacon" is NOT available', () => {
+ let server: any;
+ beforeEach(() => {
+ (window.navigator as any).sendBeacon = false;
+ zipkinExporter = new ZipkinExporter(zipkinConfig);
+ server = sinon.fakeServer.create();
+ });
+ afterEach(() => {
+ server.restore();
+ });
+
+ it('should successfully send the spans using XMLHttpRequest', done => {
+ zipkinExporter.export(spans, () => {});
+
+ setTimeout(() => {
+ const request = server.requests[0];
+ const body = request.requestBody;
+ const json = JSON.parse(body) as any;
+ ensureSpanIsCorrect(json[0]);
+
+ done();
+ });
+ });
+ });
+ });
+
+ describe('export with custom headers', () => {
+ let server: any;
+ const customHeaders = {
+ foo: 'bar',
+ bar: 'baz',
+ };
+
+ beforeEach(() => {
+ zipkinConfig = {
+ logger: new NoopLogger(),
+ headers: customHeaders,
+ };
+ server = sinon.fakeServer.create();
+ });
+
+ afterEach(() => {
+ server.restore();
+ });
+
+ describe('when "sendBeacon" is available', () => {
+ beforeEach(() => {
+ zipkinExporter = new ZipkinExporter(zipkinConfig);
+ });
+ it('should successfully send custom headers using XMLHTTPRequest', done => {
+ zipkinExporter.export(spans, () => {});
+
+ setTimeout(() => {
+ const [{ requestHeaders }] = server.requests;
+
+ ensureHeadersContain(requestHeaders, customHeaders);
+ assert.strictEqual(spyBeacon.callCount, 0);
+
+ done();
+ });
+ });
+ });
+
+ describe('when "sendBeacon" is NOT available', () => {
+ beforeEach(() => {
+ (window.navigator as any).sendBeacon = false;
+ zipkinExporter = new ZipkinExporter(zipkinConfig);
+ });
+
+ it('should successfully send custom headers using XMLHTTPRequest', done => {
+ zipkinExporter.export(spans, () => {});
+
+ setTimeout(() => {
+ const [{ requestHeaders }] = server.requests;
+
+ ensureHeadersContain(requestHeaders, customHeaders);
+ assert.strictEqual(spyBeacon.callCount, 0);
+
+ done();
+ });
+ });
+ });
+ });
+});
diff --git a/packages/opentelemetry-exporter-zipkin/test/transform.test.ts b/packages/opentelemetry-exporter-zipkin/test/common/transform.test.ts
similarity index 95%
rename from packages/opentelemetry-exporter-zipkin/test/transform.test.ts
rename to packages/opentelemetry-exporter-zipkin/test/common/transform.test.ts
index 05cbbbfd0d..ad4472066f 100644
--- a/packages/opentelemetry-exporter-zipkin/test/transform.test.ts
+++ b/packages/opentelemetry-exporter-zipkin/test/common/transform.test.ts
@@ -29,14 +29,16 @@ import {
_toZipkinAnnotations,
statusCodeTagName,
statusDescriptionTagName,
-} from '../src/transform';
-import * as zipkinTypes from '../src/types';
-import { Resource } from '@opentelemetry/resources';
-
+} from '../../src/transform';
+import * as zipkinTypes from '../../src/types';
+import { Resource, TELEMETRY_SDK_RESOURCE } from '@opentelemetry/resources';
const logger = new NoopLogger();
const tracer = new BasicTracerProvider({
logger,
}).getTracer('default');
+
+const language = tracer.resource.attributes[TELEMETRY_SDK_RESOURCE.LANGUAGE];
+
const parentId = '5c1c63257de34c67';
const spanContext: api.SpanContext = {
traceId: 'd4cda95b652f4a1592b449d5929fda1b',
@@ -94,7 +96,7 @@ describe('transform', () => {
key1: 'value1',
key2: 'value2',
[statusCodeTagName]: 'OK',
- 'telemetry.sdk.language': 'nodejs',
+ 'telemetry.sdk.language': language,
'telemetry.sdk.name': 'opentelemetry',
'telemetry.sdk.version': VERSION,
},
@@ -131,7 +133,7 @@ describe('transform', () => {
parentId: undefined,
tags: {
[statusCodeTagName]: 'OK',
- 'telemetry.sdk.language': 'nodejs',
+ 'telemetry.sdk.language': language,
'telemetry.sdk.name': 'opentelemetry',
'telemetry.sdk.version': VERSION,
},
@@ -173,7 +175,7 @@ describe('transform', () => {
parentId: undefined,
tags: {
[statusCodeTagName]: 'OK',
- 'telemetry.sdk.language': 'nodejs',
+ 'telemetry.sdk.language': language,
'telemetry.sdk.name': 'opentelemetry',
'telemetry.sdk.version': VERSION,
},
diff --git a/packages/opentelemetry-exporter-zipkin/test/helper.ts b/packages/opentelemetry-exporter-zipkin/test/helper.ts
new file mode 100644
index 0000000000..8fa1cfe1e4
--- /dev/null
+++ b/packages/opentelemetry-exporter-zipkin/test/helper.ts
@@ -0,0 +1,78 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { TraceFlags } from '@opentelemetry/api';
+import { ReadableSpan } from '@opentelemetry/tracing';
+import { Resource } from '@opentelemetry/resources';
+import * as assert from 'assert';
+import { Span } from '../src/types';
+
+export const mockedReadableSpan: ReadableSpan = {
+ name: 'documentFetch',
+ kind: 0,
+ spanContext: {
+ traceId: '1f1008dc8e270e85c40a0d7c3939b278',
+ spanId: '5e107261f64fa53e',
+ traceFlags: TraceFlags.SAMPLED,
+ },
+ parentSpanId: '78a8915098864388',
+ startTime: [1574120165, 429803070],
+ endTime: [1574120165, 438688070],
+ ended: true,
+ status: { code: 0 },
+ attributes: { component: 'foo' },
+ links: [],
+ events: [],
+ duration: [0, 8885000],
+ resource: new Resource({
+ service: 'ui',
+ version: 1,
+ cost: 112.12,
+ }),
+ instrumentationLibrary: { name: 'default', version: '0.0.1' },
+};
+
+export function ensureHeadersContain(
+ actual: { [key: string]: string },
+ expected: { [key: string]: string }
+) {
+ Object.entries(expected).forEach(([k, v]) => {
+ assert.strictEqual(
+ v,
+ actual[k],
+ `Expected ${actual} to contain ${k}: ${v}`
+ );
+ });
+}
+
+export function ensureSpanIsCorrect(span: Span) {
+ assert.deepStrictEqual(span, {
+ traceId: '1f1008dc8e270e85c40a0d7c3939b278',
+ parentId: '78a8915098864388',
+ name: 'documentFetch',
+ id: '5e107261f64fa53e',
+ timestamp: 1574120165429803,
+ duration: 8885,
+ localEndpoint: { serviceName: 'OpenTelemetry Service' },
+ tags: {
+ component: 'foo',
+ 'ot.status_code': 'OK',
+ service: 'ui',
+ version: 1,
+ cost: 112.12,
+ },
+ });
+}
diff --git a/packages/opentelemetry-exporter-zipkin/test/zipkin.test.ts b/packages/opentelemetry-exporter-zipkin/test/node/zipkin.test.ts
similarity index 95%
rename from packages/opentelemetry-exporter-zipkin/test/zipkin.test.ts
rename to packages/opentelemetry-exporter-zipkin/test/node/zipkin.test.ts
index 7dd50b8381..803f88838d 100644
--- a/packages/opentelemetry-exporter-zipkin/test/zipkin.test.ts
+++ b/packages/opentelemetry-exporter-zipkin/test/node/zipkin.test.ts
@@ -24,9 +24,9 @@ import {
} from '@opentelemetry/core';
import * as api from '@opentelemetry/api';
import { Resource } from '@opentelemetry/resources';
-import { ZipkinExporter } from '../src';
-import * as zipkinTypes from '../src/types';
-import { OT_REQUEST_HEADER } from '../src/utils';
+import { ZipkinExporter } from '../../src';
+import * as zipkinTypes from '../../src/types';
+import { OT_REQUEST_HEADER } from '../../src/utils';
import { TraceFlags } from '@opentelemetry/api';
import { SERVICE_RESOURCE } from '@opentelemetry/resources';
@@ -59,7 +59,7 @@ function getReadableSpan() {
return readableSpan;
}
-describe('ZipkinExporter', () => {
+describe('Zipkin Exporter - node', () => {
describe('constructor', () => {
it('should construct an exporter', () => {
const exporter = new ZipkinExporter({ serviceName: 'my-service' });
@@ -98,21 +98,6 @@ describe('ZipkinExporter', () => {
assert.ok(typeof exporter.export === 'function');
assert.ok(typeof exporter.shutdown === 'function');
});
- it('should construct an exporter with headers', () => {
- const exporter = new ZipkinExporter({
- headers: {
- foo: 'bar',
- },
- });
- interface ExporterWithHeaders {
- _reqOpts: {
- headers: { [key: string]: string };
- };
- }
- const exporterWithHeaders = (exporter as unknown) as ExporterWithHeaders;
-
- assert.ok(exporterWithHeaders._reqOpts.headers['foo'] === 'bar');
- });
});
describe('export', () => {
@@ -510,8 +495,5 @@ describe('ZipkinExporter', () => {
after(() => {
nock.enableNetConnect();
});
-
- // @todo: implement
- it('should send by default');
});
});