diff --git a/examples/basic-tracer-node/README.md b/examples/basic-tracer-node/README.md
index d7e0feb9ea..ce75a56078 100644
--- a/examples/basic-tracer-node/README.md
+++ b/examples/basic-tracer-node/README.md
@@ -13,6 +13,9 @@ $ npm install
Setup [Zipkin Tracing](https://zipkin.io/pages/quickstart.html)
or
Setup [Jaeger Tracing](https://www.jaegertracing.io/docs/latest/getting-started/#all-in-one)
+or
+Setup [Collector Exporter](https://github.com/open-telemetry/opentelemetry-exporter-collector)
+
## Run the Application
@@ -57,6 +60,24 @@ Click on the trace to view its details.
+### Collector Exporter
+You can use the [opentelemetry-collector][opentelemetry-collector-url] docker container.
+For that please make sure you have [docker](https://docs.docker.com/) installed
+ - Run the docker container
+ ```sh
+ $ # from this directory
+ $ # open telemetry
+ $ npm run collector:docker:ot
+ $ # at any time you can stop it
+ $ npm run collector:docker:stop
+ ```
+
+#### Collector Exporter - Zipkin UI
+The [opentelemetry-collector][opentelemetry-collector-url]
+docker container is using [Zipkin Exporter](#zipkin).
+You can define more exporters without changing the instrumented code.
+To use default [Zipkin Exporter](#zipkin) please follow the section [Zipkin UI](#zipkin-ui) only
+
### Export to multiple exporters
- Run the sample
@@ -75,3 +96,6 @@ Click on the trace to view its details.
## LICENSE
Apache License 2.0
+
+
+[opentelemetry-collector-url]: https://github.com/open-telemetry/opentelemetry-exporter-collector
diff --git a/examples/basic-tracer-node/docker/ot/collector-config.yaml b/examples/basic-tracer-node/docker/ot/collector-config.yaml
new file mode 100644
index 0000000000..b4bc9cb9fc
--- /dev/null
+++ b/examples/basic-tracer-node/docker/ot/collector-config.yaml
@@ -0,0 +1,18 @@
+receivers:
+ opencensus:
+ endpoint: 0.0.0.0:55678
+
+exporters:
+ zipkin:
+ url: "http://zipkin-all-in-one:9411/api/v2/spans"
+
+processors:
+ batch:
+ queued_retry:
+
+service:
+ pipelines:
+ traces:
+ receivers: [opencensus]
+ exporters: [zipkin]
+ processors: [batch, queued_retry]
diff --git a/examples/basic-tracer-node/docker/ot/docker-compose.yaml b/examples/basic-tracer-node/docker/ot/docker-compose.yaml
new file mode 100644
index 0000000000..3e486bfb05
--- /dev/null
+++ b/examples/basic-tracer-node/docker/ot/docker-compose.yaml
@@ -0,0 +1,19 @@
+version: "2"
+services:
+
+ # Collector
+ collector:
+ image: otelcol:latest
+ command: ["--config=/conf/collector-config.yaml", "--log-level=DEBUG"]
+ volumes:
+ - ./collector-config.yaml:/conf/collector-config.yaml
+ ports:
+ - "55678:55678"
+ depends_on:
+ - zipkin-all-in-one
+
+ # Zipkin
+ zipkin-all-in-one:
+ image: openzipkin/zipkin:latest
+ ports:
+ - "9411:9411"
diff --git a/examples/basic-tracer-node/index.js b/examples/basic-tracer-node/index.js
index 19b2a7b881..61bbe33b54 100644
--- a/examples/basic-tracer-node/index.js
+++ b/examples/basic-tracer-node/index.js
@@ -2,10 +2,11 @@ const opentelemetry = require('@opentelemetry/core');
const { BasicTracer, SimpleSpanProcessor } = require('@opentelemetry/tracing');
const { JaegerExporter } = require('@opentelemetry/exporter-jaeger');
const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin');
+const { CollectorExporter } = require('@opentelemetry/exporter-collector');
const options = {
serviceName: 'basic-service'
-}
+};
// Initialize an exporter depending on how we were started
let exporter;
@@ -13,8 +14,10 @@ let exporter;
const EXPORTER = process.env.EXPORTER || '';
if (EXPORTER.toLowerCase().startsWith('z')) {
exporter = new ZipkinExporter(options);
-} else {
+} else if (EXPORTER.toLowerCase().startsWith('j')) {
exporter = new JaegerExporter(options);
+} else {
+ exporter = new CollectorExporter(options);
}
const tracer = new BasicTracer();
diff --git a/examples/basic-tracer-node/multi_exporter.js b/examples/basic-tracer-node/multi_exporter.js
index dc4c85474b..e5920a7e6b 100644
--- a/examples/basic-tracer-node/multi_exporter.js
+++ b/examples/basic-tracer-node/multi_exporter.js
@@ -2,6 +2,7 @@ const opentelemetry = require('@opentelemetry/core');
const { BasicTracer, BatchSpanProcessor, SimpleSpanProcessor } = require('@opentelemetry/tracing');
const { JaegerExporter } = require('@opentelemetry/exporter-jaeger');
const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin');
+const { CollectorExporter } = require('@opentelemetry/exporter-collector');
const tracer = new BasicTracer();
@@ -10,7 +11,8 @@ const jaegerExporter = new JaegerExporter({
serviceName: 'basic-service',
// The default flush interval is 5 seconds.
flushInterval: 2000
-})
+});
+const collectorExporter = new CollectorExporter({serviceName: 'basic-service'});
// It is recommended to use this BatchSpanProcessor for better performance
// and optimization, especially in production.
@@ -22,6 +24,8 @@ tracer.addSpanProcessor(new BatchSpanProcessor(zipkinExporter, {
// it's internal client already handles the spans with batching logic.
tracer.addSpanProcessor(new SimpleSpanProcessor(jaegerExporter));
+tracer.addSpanProcessor(new SimpleSpanProcessor(collectorExporter));
+
// Initialize the OpenTelemetry APIs to use the BasicTracer bindings
opentelemetry.initGlobalTracer(tracer);
@@ -36,6 +40,7 @@ span.end();
// flush and close the connection.
zipkinExporter.shutdown();
jaegerExporter.shutdown();
+collectorExporter.shutdown();
function doWork(parent) {
// Start another span. In this example, the main method already started a
diff --git a/examples/basic-tracer-node/package.json b/examples/basic-tracer-node/package.json
index 77417ab385..05063bd87a 100644
--- a/examples/basic-tracer-node/package.json
+++ b/examples/basic-tracer-node/package.json
@@ -7,6 +7,9 @@
"scripts": {
"zipkin:basic": "cross-env EXPORTER=zipkin node ./index.js",
"jaeger:basic": "cross-env EXPORTER=jaeger node ./index.js",
+ "collector:basic": "cross-env EXPORTER=collector node ./index.js",
+ "collector:docker:ot": "cd ./docker/ot && docker-compose down && docker-compose up",
+ "collector:docker:stop": "cd ./docker/ot && docker-compose down",
"multi_exporter": "node ./multi_exporter.js"
},
"repository": {
@@ -28,6 +31,7 @@
},
"dependencies": {
"@opentelemetry/core": "^0.2.0",
+ "@opentelemetry/exporter-collector": "^0.2.0",
"@opentelemetry/exporter-jaeger": "^0.2.0",
"@opentelemetry/exporter-zipkin": "^0.2.0",
"@opentelemetry/tracing": "^0.2.0"
diff --git a/examples/tracer-web/index.html b/examples/tracer-web/index.html
index 947630827c..395281f3e8 100644
--- a/examples/tracer-web/index.html
+++ b/examples/tracer-web/index.html
@@ -21,7 +21,7 @@
- Example of using Web Tracer with document load plugin and console exporter
+ Example of using Web Tracer with document load plugin with console exporter and collector exporter
diff --git a/examples/tracer-web/index.js b/examples/tracer-web/index.js
index 0e5e9956e3..c08a3c0f39 100644
--- a/examples/tracer-web/index.js
+++ b/examples/tracer-web/index.js
@@ -2,6 +2,7 @@ import { ConsoleSpanExporter, SimpleSpanProcessor } from '@opentelemetry/tracing
import { WebTracer } from '@opentelemetry/web';
import { DocumentLoad } from '@opentelemetry/plugin-document-load';
import { ZoneScopeManager } from '@opentelemetry/scope-zone';
+import { CollectorExporter } from '@opentelemetry/exporter-collector'
const webTracer = new WebTracer({
plugins: [
@@ -17,6 +18,7 @@ const webTracerWithZone = new WebTracer({
]
});
webTracerWithZone.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter()));
+webTracerWithZone.addSpanProcessor(new SimpleSpanProcessor(new CollectorExporter()));
console.log('Current span is window', webTracerWithZone.getCurrentSpan() === window);
diff --git a/examples/tracer-web/package.json b/examples/tracer-web/package.json
index 35c98c8240..13f561b121 100644
--- a/examples/tracer-web/package.json
+++ b/examples/tracer-web/package.json
@@ -34,6 +34,7 @@
"webpack-merge": "^4.2.2"
},
"dependencies": {
+ "@opentelemetry/exporter-collector": "^0.2.0",
"@opentelemetry/plugin-document-load": "^0.2.0",
"@opentelemetry/scope-zone": "^0.2.0",
"@opentelemetry/tracing": "^0.2.0",
diff --git a/packages/opentelemetry-core/package.json b/packages/opentelemetry-core/package.json
index 8b56e65d9c..915aa32cb1 100644
--- a/packages/opentelemetry-core/package.json
+++ b/packages/opentelemetry-core/package.json
@@ -20,9 +20,11 @@
"clean": "rimraf build/*",
"check": "gts check",
"precompile": "tsc --version",
- "compile": "tsc -p .",
+ "compile": "npm run version:update && tsc -p .",
"fix": "gts fix",
- "prepare": "npm run compile"
+ "prepare": "npm run compile",
+ "version:update": "node scripts/version-update.js",
+ "watch": "tsc -w"
},
"keywords": [
"opentelemetry",
diff --git a/packages/opentelemetry-core/scripts/version-update.js b/packages/opentelemetry-core/scripts/version-update.js
new file mode 100644
index 0000000000..3240a96b4b
--- /dev/null
+++ b/packages/opentelemetry-core/scripts/version-update.js
@@ -0,0 +1,47 @@
+/*!
+ * Copyright 2019, 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 license =
+`/*!
+ * Copyright 2019, 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 fs = require('fs');
+const path = require('path');
+
+const appRoot = path.resolve(__dirname);
+const fileUrl = path.resolve(`${appRoot}/../src/common/version.ts`);
+const packageJsonUrl = path.resolve(`${appRoot}/../package.json`);
+const pjson = require(packageJsonUrl);
+const content = `
+// this is autogenerated file, see scripts/version-update.js
+export const VERSION = '${pjson.version}';
+`;
+
+fs.writeFileSync(fileUrl, `${license}${content}`);
diff --git a/packages/opentelemetry-core/src/common/time.ts b/packages/opentelemetry-core/src/common/time.ts
index fcc564692e..861c9efc44 100644
--- a/packages/opentelemetry-core/src/common/time.ts
+++ b/packages/opentelemetry-core/src/common/time.ts
@@ -21,7 +21,10 @@ import { TimeOriginLegacy } from './types';
const NANOSECOND_DIGITS = 9;
const SECOND_TO_NANOSECONDS = Math.pow(10, NANOSECOND_DIGITS);
-// Converts a number to HrTime
+/**
+ * Converts a number to HrTime
+ * @param epochMillis
+ */
function numberToHrtime(epochMillis: number): types.HrTime {
const epochSeconds = epochMillis / 1000;
// Decimals only.
@@ -42,7 +45,10 @@ function getTimeOrigin(): number {
return timeOrigin;
}
-// Returns an hrtime calculated via performance component.
+/**
+ * Returns an hrtime calculated via performance component.
+ * @param performanceNow
+ */
export function hrTime(performanceNow?: number): types.HrTime {
const timeOrigin = numberToHrtime(getTimeOrigin());
const now = numberToHrtime(
@@ -61,7 +67,11 @@ export function hrTime(performanceNow?: number): types.HrTime {
return [seconds, nanos];
}
-// Converts a TimeInput to an HrTime, defaults to _hrtime().
+/**
+ *
+ * Converts a TimeInput to an HrTime, defaults to _hrtime().
+ * @param time
+ */
export function timeInputToHrTime(time: types.TimeInput): types.HrTime {
// process.hrtime
if (isTimeInputHrTime(time)) {
@@ -81,7 +91,11 @@ export function timeInputToHrTime(time: types.TimeInput): types.HrTime {
}
}
-// Returns a duration of two hrTime.
+/**
+ * Returns a duration of two hrTime.
+ * @param startTime
+ * @param endTime
+ */
export function hrTimeDuration(
startTime: types.HrTime,
endTime: types.HrTime
@@ -99,21 +113,46 @@ export function hrTimeDuration(
return [seconds, nanos];
}
-// Convert hrTime to nanoseconds.
+/**
+ * Convert hrTime to timestamp, for example "2019-05-14T17:00:00.000123456Z"
+ * @param hrTime
+ */
+export function hrTimeToTimeStamp(hrTime: types.HrTime): string {
+ const precision = NANOSECOND_DIGITS;
+ const tmp = `${'0'.repeat(precision)}${hrTime[1]}Z`;
+ const nanoString = tmp.substr(tmp.length - precision - 1);
+ const date = new Date(hrTime[0] * 1000).toISOString();
+ return date.replace('000Z', nanoString);
+}
+
+/**
+ * Convert hrTime to nanoseconds.
+ * @param hrTime
+ */
export function hrTimeToNanoseconds(hrTime: types.HrTime): number {
return hrTime[0] * SECOND_TO_NANOSECONDS + hrTime[1];
}
-// Convert hrTime to milliseconds.
+/**
+ * Convert hrTime to milliseconds.
+ * @param hrTime
+ */
export function hrTimeToMilliseconds(hrTime: types.HrTime): number {
return Math.round(hrTime[0] * 1e3 + hrTime[1] / 1e6);
}
-// Convert hrTime to microseconds.
+/**
+ * Convert hrTime to microseconds.
+ * @param hrTime
+ */
export function hrTimeToMicroseconds(hrTime: types.HrTime): number {
return Math.round(hrTime[0] * 1e6 + hrTime[1] / 1e3);
}
+/**
+ * check if time is HrTime
+ * @param value
+ */
export function isTimeInputHrTime(value: unknown) {
return (
Array.isArray(value) &&
@@ -123,7 +162,10 @@ export function isTimeInputHrTime(value: unknown) {
);
}
-// check if input value is a correct types.TimeInput
+/**
+ * check if input value is a correct types.TimeInput
+ * @param value
+ */
export function isTimeInput(value: unknown) {
return (
isTimeInputHrTime(value) ||
diff --git a/packages/opentelemetry-core/src/common/version.ts b/packages/opentelemetry-core/src/common/version.ts
new file mode 100644
index 0000000000..5e6eb85dbe
--- /dev/null
+++ b/packages/opentelemetry-core/src/common/version.ts
@@ -0,0 +1,18 @@
+/*!
+ * Copyright 2019, 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.
+ */
+
+// this is autogenerated file, see scripts/version-update.js
+export const VERSION = '0.2.0';
diff --git a/packages/opentelemetry-core/src/index.ts b/packages/opentelemetry-core/src/index.ts
index 581d9ab055..236ad9d2cd 100644
--- a/packages/opentelemetry-core/src/index.ts
+++ b/packages/opentelemetry-core/src/index.ts
@@ -18,6 +18,7 @@ export * from './common/ConsoleLogger';
export * from './common/NoopLogger';
export * from './common/time';
export * from './common/types';
+export * from './common/version';
export * from './context/propagation/B3Format';
export * from './context/propagation/BinaryTraceContext';
export * from './context/propagation/HttpTraceContext';
diff --git a/packages/opentelemetry-core/src/platform/browser/hex-to-base64.ts b/packages/opentelemetry-core/src/platform/browser/hex-to-base64.ts
new file mode 100644
index 0000000000..d0441b7772
--- /dev/null
+++ b/packages/opentelemetry-core/src/platform/browser/hex-to-base64.ts
@@ -0,0 +1,30 @@
+/*!
+ * Copyright 2019, 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.
+ */
+
+/**
+ * converts id string into base64
+ * @param hexStr - id of span
+ */
+export function hexToBase64(hexStr: string): string {
+ const hexStrLen = hexStr.length;
+ let hexAsciiCharsStr = '';
+ for (let i = 0; i < hexStrLen; i += 2) {
+ const hexPair = hexStr.substring(i, i + 2);
+ const hexVal = parseInt(hexPair, 16);
+ hexAsciiCharsStr += String.fromCharCode(hexVal);
+ }
+ return btoa(hexAsciiCharsStr);
+}
diff --git a/packages/opentelemetry-core/src/platform/browser/index.ts b/packages/opentelemetry-core/src/platform/browser/index.ts
index 8b1b9afe53..4668bb35aa 100644
--- a/packages/opentelemetry-core/src/platform/browser/index.ts
+++ b/packages/opentelemetry-core/src/platform/browser/index.ts
@@ -17,3 +17,4 @@
export * from './id';
export * from './performance';
export * from './timer-util';
+export * from './hex-to-base64';
diff --git a/packages/opentelemetry-core/src/platform/node/hex-to-base64.ts b/packages/opentelemetry-core/src/platform/node/hex-to-base64.ts
new file mode 100644
index 0000000000..1ed62f9cf9
--- /dev/null
+++ b/packages/opentelemetry-core/src/platform/node/hex-to-base64.ts
@@ -0,0 +1,31 @@
+/*!
+ * Copyright 2019, 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.
+ */
+
+/**
+ * converts id string into base64
+ * @param hexStr - id of span
+ */
+export function hexToBase64(hexStr: string): string {
+ const hexStrLen = hexStr.length;
+ let hexAsciiCharsStr = '';
+ for (let i = 0; i < hexStrLen; i += 2) {
+ const hexPair = hexStr.substring(i, i + 2);
+ const hexVal = parseInt(hexPair, 16);
+ hexAsciiCharsStr += String.fromCharCode(hexVal);
+ }
+
+ return Buffer.from(hexAsciiCharsStr, 'ascii').toString('base64');
+}
diff --git a/packages/opentelemetry-core/src/platform/node/index.ts b/packages/opentelemetry-core/src/platform/node/index.ts
index 8b1b9afe53..4668bb35aa 100644
--- a/packages/opentelemetry-core/src/platform/node/index.ts
+++ b/packages/opentelemetry-core/src/platform/node/index.ts
@@ -17,3 +17,4 @@
export * from './id';
export * from './performance';
export * from './timer-util';
+export * from './hex-to-base64';
diff --git a/packages/opentelemetry-core/test/common/time.test.ts b/packages/opentelemetry-core/test/common/time.test.ts
index 4eb82a9b78..a65af498fc 100644
--- a/packages/opentelemetry-core/test/common/time.test.ts
+++ b/packages/opentelemetry-core/test/common/time.test.ts
@@ -25,6 +25,7 @@ import {
hrTimeToNanoseconds,
hrTimeToMilliseconds,
hrTimeToMicroseconds,
+ hrTimeToTimeStamp,
isTimeInput,
} from '../../src/common/time';
@@ -156,6 +157,15 @@ describe('time', () => {
});
});
+ describe('#hrTimeToTimeStamp', () => {
+ it('should return timestamp', () => {
+ const time: types.HrTime = [1573513121, 123456];
+
+ const output = hrTimeToTimeStamp(time);
+ assert.deepStrictEqual(output, '2019-11-11T22:58:41.000123456Z');
+ });
+ });
+
describe('#hrTimeToNanoseconds', () => {
it('should return nanoseconds', () => {
const output = hrTimeToNanoseconds([1, 200000000]);
diff --git a/packages/opentelemetry-core/test/platform/hex-to-base64.test.ts b/packages/opentelemetry-core/test/platform/hex-to-base64.test.ts
new file mode 100644
index 0000000000..1165ef7b95
--- /dev/null
+++ b/packages/opentelemetry-core/test/platform/hex-to-base64.test.ts
@@ -0,0 +1,27 @@
+/*!
+ * Copyright 2019, OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import * as assert from 'assert';
+import { hexToBase64 } from '../../src/platform';
+
+describe('hexToBase64', () => {
+ it('convert hex to base64', () => {
+ const id1 = '7deb739e02e44ef2';
+ const id2 = '46cef837b919a16ff26e608c8cf42c80';
+ assert.strictEqual(hexToBase64(id1), 'fetzngLkTvI=');
+ assert.strictEqual(hexToBase64(id2), 'Rs74N7kZoW/ybmCMjPQsgA==');
+ });
+});
diff --git a/packages/opentelemetry-exporter-collector/.npmignore b/packages/opentelemetry-exporter-collector/.npmignore
new file mode 100644
index 0000000000..9505ba9450
--- /dev/null
+++ b/packages/opentelemetry-exporter-collector/.npmignore
@@ -0,0 +1,4 @@
+/bin
+/coverage
+/doc
+/test
diff --git a/packages/opentelemetry-exporter-collector/LICENSE b/packages/opentelemetry-exporter-collector/LICENSE
new file mode 100644
index 0000000000..261eeb9e9f
--- /dev/null
+++ b/packages/opentelemetry-exporter-collector/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/packages/opentelemetry-exporter-collector/README.md b/packages/opentelemetry-exporter-collector/README.md
new file mode 100644
index 0000000000..25574ea689
--- /dev/null
+++ b/packages/opentelemetry-exporter-collector/README.md
@@ -0,0 +1,77 @@
+# OpenTelemetry Collector Exporter for web and node
+[![Gitter chat][gitter-image]][gitter-url]
+[![NPM Published Version][npm-img]][npm-url]
+[![dependencies][dependencies-image]][dependencies-url]
+[![devDependencies][devDependencies-image]][devDependencies-url]
+[![Apache License][license-image]][license-image]
+
+This module provides exporter for web and node to be used with [opentelemetry-collector][opentelemetry-collector-url].
+
+## Installation
+
+```bash
+npm install --save @opentelemetry/exporter-collector
+```
+
+## Usage in Web
+```js
+import * as opentelemetry from '@opentelemetry/core';
+import { SimpleSpanProcessor } from '@opentelemetry/tracing';
+import { WebTracer } from '@opentelemetry/web';
+import { CollectorExporter } from '@opentelemetry/exporter-collector'
+
+const collectorOptions = {
+ url: '' // url is optional and can be omitted - default is http://localhost:55678/v1/trace
+};
+
+const tracer = new WebTracer();
+const exporter = new CollectorExporter(collectorOptions);
+tracer.addSpanProcessor(new SimpleSpanProcessor(exporter));
+
+opentelemetry.initGlobalTracer(tracer);
+
+```
+
+## Usage in Node
+```js
+const opentelemetry = require('@opentelemetry/core');
+const { BasicTracer, SimpleSpanProcessor } = require('@opentelemetry/tracing');
+const { CollectorExporter } = require('@opentelemetry/exporter-collector');
+
+const collectorOptions = {
+ url: '' // url is optional and can be omitted - default is http://localhost:55678/v1/trace
+};
+
+const tracer = new BasicTracer();
+const exporter = new CollectorExporter(collectorOptions);
+tracer.addSpanProcessor(new SimpleSpanProcessor(exporter));
+
+opentelemetry.initGlobalTracer(tracer);
+
+```
+
+## Running opentelemetry-collector locally to see the traces
+1. Go to examples/basic-tracer-node
+2. run `npm run collector:docker:ot`
+3. Open page at `http://localhost:9411/zipkin/` to observe the traces
+
+## Useful links
+- For more information on OpenTelemetry, visit:
+- For more about OpenTelemetry JavaScript:
+- For help or feedback on this project, join us on [gitter][gitter-url]
+
+## License
+
+Apache 2.0 - See [LICENSE][license-url] for more information.
+
+[gitter-image]: https://badges.gitter.im/open-telemetry/opentelemetry-js.svg
+[gitter-url]: https://gitter.im/open-telemetry/opentelemetry-node?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
+[license-url]: https://github.com/open-telemetry/opentelemetry-js/blob/master/LICENSE
+[license-image]: https://img.shields.io/badge/license-Apache_2.0-green.svg?style=flat
+[dependencies-image]: https://david-dm.org/open-telemetry/opentelemetry-js/status.svg?path=packages/opentelemetry-exporter-collector
+[dependencies-url]: https://david-dm.org/open-telemetry/opentelemetry-js?path=packages%2Fopentelemetry-exporter-collector
+[devDependencies-image]: https://david-dm.org/open-telemetry/opentelemetry-js/dev-status.svg?path=packages/opentelemetry-exporter-collector
+[devDependencies-url]: https://david-dm.org/open-telemetry/opentelemetry-js?path=packages%2Fopentelemetry-exporter-collector&type=dev
+[npm-url]: https://www.npmjs.com/package/@opentelemetry/exporter-collector
+[npm-img]: https://badge.fury.io/js/%40opentelemetry%exporter-collector.svg
+[opentelemetry-collector-url]: https://github.com/open-telemetry/opentelemetry-collector
diff --git a/packages/opentelemetry-exporter-collector/karma.conf.js b/packages/opentelemetry-exporter-collector/karma.conf.js
new file mode 100644
index 0000000000..86965a0095
--- /dev/null
+++ b/packages/opentelemetry-exporter-collector/karma.conf.js
@@ -0,0 +1,26 @@
+/*!
+ * Copyright 2019, 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-collector/package.json b/packages/opentelemetry-exporter-collector/package.json
new file mode 100644
index 0000000000..06e4eb8a51
--- /dev/null
+++ b/packages/opentelemetry-exporter-collector/package.json
@@ -0,0 +1,87 @@
+{
+ "name": "@opentelemetry/exporter-collector",
+ "version": "0.2.0",
+ "description": "OpenTelemetry Collector Exporter allows user to send collected traces to the OpenTelemetry Collector",
+ "main": "build/src/index.js",
+ "types": "build/src/index.d.ts",
+ "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": {
+ "check": "gts check",
+ "clean": "rimraf build/*",
+ "codecov:browser": "nyc report --reporter=json && codecov -f coverage/*.json -p ../../",
+ "precompile": "tsc --version",
+ "compile": "npm run version:update && tsc -p .",
+ "fix": "gts fix",
+ "prepare": "npm run compile",
+ "tdd": "yarn test -- --watch-extensions ts --watch",
+ "tdd:browser": "karma start",
+ "test": "nyc ts-mocha -p tsconfig.json 'test/**/*.ts' --exclude 'test/browser/**/*.ts'",
+ "test:browser": "nyc karma start --single-run",
+ "version:update": "node scripts/version-update.js",
+ "watch": "tsc -w"
+ },
+ "keywords": [
+ "opentelemetry",
+ "nodejs",
+ "browser",
+ "tracing",
+ "profiling",
+ "metrics",
+ "stats"
+ ],
+ "author": "OpenTelemetry Authors",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8.0.0"
+ },
+ "files": [
+ "build/src/**/*.js",
+ "build/src/**/*.d.ts",
+ "doc",
+ "LICENSE",
+ "README.md"
+ ],
+ "publishConfig": {
+ "access": "public"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.6.0",
+ "@types/mocha": "^5.2.5",
+ "@types/node": "^12.6.8",
+ "@types/sinon": "^7.0.13",
+ "@types/webpack-env": "1.13.9",
+ "babel-loader": "^8.0.6",
+ "codecov": "^3.1.0",
+ "gts": "^1.0.0",
+ "istanbul-instrumenter-loader": "^3.0.1",
+ "karma": "^4.4.1",
+ "karma-chrome-launcher": "^3.1.0",
+ "karma-coverage-istanbul-reporter": "^2.1.0",
+ "karma-mocha": "^1.3.0",
+ "karma-spec-reporter": "^0.0.32",
+ "karma-webpack": "^4.0.2",
+ "mocha": "^6.1.0",
+ "nyc": "^14.1.1",
+ "rimraf": "^3.0.0",
+ "sinon": "^7.5.0",
+ "ts-loader": "^6.0.4",
+ "ts-mocha": "^6.0.0",
+ "ts-node": "^8.0.0",
+ "tslint-consistent-codestyle": "^1.16.0",
+ "tslint-microsoft-contrib": "^6.2.0",
+ "typescript": "3.6.4",
+ "webpack": "^4.35.2",
+ "webpack-cli": "^3.3.9",
+ "webpack-merge": "^4.2.2"
+ },
+ "dependencies": {
+ "@opentelemetry/base": "^0.2.0",
+ "@opentelemetry/core": "^0.2.0",
+ "@opentelemetry/tracing": "^0.2.0",
+ "@opentelemetry/types": "^0.2.0"
+ }
+}
diff --git a/packages/opentelemetry-exporter-collector/scripts/version-update.js b/packages/opentelemetry-exporter-collector/scripts/version-update.js
new file mode 100644
index 0000000000..2bb382ce6d
--- /dev/null
+++ b/packages/opentelemetry-exporter-collector/scripts/version-update.js
@@ -0,0 +1,47 @@
+/*!
+ * Copyright 2019, 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 license =
+`/*!
+ * Copyright 2019, 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 fs = require('fs');
+const path = require('path');
+
+const appRoot = path.resolve(__dirname);
+const fileUrl = path.resolve(`${appRoot}/../src/version.ts`);
+const packageJsonUrl = path.resolve(`${appRoot}/../package.json`);
+const pjson = require(packageJsonUrl);
+const content = `
+// this is autogenerated file, see scripts/version-update.js
+export const VERSION = '${pjson.version}';
+`;
+
+fs.writeFileSync(fileUrl, `${license}${content}`);
diff --git a/packages/opentelemetry-exporter-collector/src/CollectorExporter.ts b/packages/opentelemetry-exporter-collector/src/CollectorExporter.ts
new file mode 100644
index 0000000000..c7fff44901
--- /dev/null
+++ b/packages/opentelemetry-exporter-collector/src/CollectorExporter.ts
@@ -0,0 +1,134 @@
+/*!
+ * Copyright 2019, 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 { ExportResult } from '@opentelemetry/base';
+import { NoopLogger } from '@opentelemetry/core';
+import { ReadableSpan, SpanExporter } from '@opentelemetry/tracing';
+import { Attributes, Logger } from '@opentelemetry/types';
+import * as collectorTypes from './types';
+import { toCollectorSpan } from './transform';
+import { onInit, onShutdown, sendSpans } from './platform/index';
+
+/**
+ * Collector Exporter Config
+ */
+export interface CollectorExporterConfig {
+ hostName?: string;
+ logger?: Logger;
+ serviceName?: string;
+ attributes?: Attributes;
+ url?: string;
+}
+
+const DEFAULT_SERVICE_NAME = 'collector-exporter';
+const DEFAULT_COLLECTOR_URL = 'http://localhost:55678/v1/trace';
+
+/**
+ * Collector Exporter
+ */
+export class CollectorExporter implements SpanExporter {
+ readonly serviceName: string;
+ readonly url: string;
+ readonly logger: Logger;
+ readonly hostName: string | undefined;
+ readonly attributes?: Attributes;
+ private _isShutdown: boolean = false;
+
+ /**
+ * @param config
+ */
+ constructor(config: CollectorExporterConfig = {}) {
+ this.serviceName = config.serviceName || DEFAULT_SERVICE_NAME;
+ this.url = config.url || DEFAULT_COLLECTOR_URL;
+ if (typeof config.hostName === 'string') {
+ this.hostName = config.hostName;
+ }
+
+ this.attributes = config.attributes;
+
+ this.logger = config.logger || new NoopLogger();
+
+ this.shutdown = this.shutdown.bind(this);
+
+ // platform dependent
+ onInit(this.shutdown);
+ }
+
+ /**
+ * Export spans.
+ * @param spans
+ * @param resultCallback
+ */
+ export(
+ spans: ReadableSpan[],
+ resultCallback: (result: ExportResult) => void
+ ) {
+ if (this._isShutdown) {
+ resultCallback(ExportResult.FAILED_NOT_RETRYABLE);
+ return;
+ }
+ this._exportSpans(spans)
+ .then(() => {
+ resultCallback(ExportResult.SUCCESS);
+ })
+ .catch((status: number = 0) => {
+ if (status < 500) {
+ resultCallback(ExportResult.FAILED_NOT_RETRYABLE);
+ } else {
+ resultCallback(ExportResult.FAILED_RETRYABLE);
+ }
+ });
+ }
+
+ private _exportSpans(spans: ReadableSpan[]): Promise {
+ return new Promise((resolve, reject) => {
+ try {
+ const spansToBeSent: collectorTypes.Span[] = spans.map(span =>
+ toCollectorSpan(span)
+ );
+ this.logger.debug('spans to be sent', spansToBeSent);
+
+ // Send spans to [opentelemetry collector]{@link https://github.com/open-telemetry/opentelemetry-collector}
+ // it will use the appropriate transport layer automatically depends on platform
+ sendSpans(spansToBeSent, resolve, reject, this);
+ } catch (e) {
+ reject(e);
+ }
+ });
+ }
+
+ /**
+ * Shutdown the exporter.
+ */
+ shutdown(): void {
+ if (this._isShutdown) {
+ this.logger.debug('shutdown already started');
+ return;
+ }
+ this._isShutdown = true;
+ this.logger.debug('shutdown started');
+
+ // platform dependent
+ onShutdown(this.shutdown);
+
+ // @TODO get spans from span processor (batch)
+ this._exportSpans([])
+ .then(() => {
+ this.logger.debug('shutdown completed');
+ })
+ .catch(() => {});
+ }
+}
diff --git a/packages/opentelemetry-exporter-collector/src/index.ts b/packages/opentelemetry-exporter-collector/src/index.ts
new file mode 100644
index 0000000000..df00fee615
--- /dev/null
+++ b/packages/opentelemetry-exporter-collector/src/index.ts
@@ -0,0 +1,17 @@
+/*!
+ * Copyright 2019, 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 './CollectorExporter';
diff --git a/packages/opentelemetry-exporter-collector/src/platform/browser/index.ts b/packages/opentelemetry-exporter-collector/src/platform/browser/index.ts
new file mode 100644
index 0000000000..e2ba0ac5a4
--- /dev/null
+++ b/packages/opentelemetry-exporter-collector/src/platform/browser/index.ts
@@ -0,0 +1,17 @@
+/*!
+ * Copyright 2019, 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 './sendSpans';
diff --git a/packages/opentelemetry-exporter-collector/src/platform/browser/sendSpans.ts b/packages/opentelemetry-exporter-collector/src/platform/browser/sendSpans.ts
new file mode 100644
index 0000000000..f7932a2afb
--- /dev/null
+++ b/packages/opentelemetry-exporter-collector/src/platform/browser/sendSpans.ts
@@ -0,0 +1,150 @@
+/*!
+ * Copyright 2019, 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 core from '@opentelemetry/core';
+import { Logger } from '@opentelemetry/types';
+import { CollectorExporter } from '../../CollectorExporter';
+import * as collectorTypes from '../../types';
+import { VERSION } from '../../version';
+
+/**
+ * function that is called once when {@link ExporterCollector} is initialised
+ * @param shutdownF shutdown method of {@link ExporterCollector}
+ */
+export function onInit(shutdownF: EventListener) {
+ window.addEventListener('unload', shutdownF);
+}
+
+/**
+ * function to be called once when {@link ExporterCollector} is shutdown
+ * @param shutdownF - shutdown method of {@link ExporterCollector}
+ */
+export function onShutdown(shutdownF: EventListener) {
+ window.removeEventListener('unload', shutdownF);
+}
+
+/**
+ * function to send spans to the [opentelemetry collector]{@link https://github.com/open-telemetry/opentelemetry-collector}
+ * using the standard http/https node module
+ * @param spans
+ * @param onSuccess
+ * @param onError
+ * @param collectorExporter
+ */
+export function sendSpans(
+ spans: collectorTypes.Span[],
+ onSuccess: () => void,
+ onError: (status?: number) => void,
+ collectorExporter: CollectorExporter
+) {
+ const exportTraceServiceRequest: collectorTypes.ExportTraceServiceRequest = {
+ node: {
+ identifier: {
+ hostName: collectorExporter.hostName || window.location.host,
+ startTimestamp: core.hrTimeToTimeStamp(core.hrTime()),
+ },
+ libraryInfo: {
+ language: collectorTypes.LibraryInfoLanguage.WEB_JS,
+ coreLibraryVersion: core.VERSION,
+ exporterVersion: VERSION,
+ },
+ serviceInfo: {
+ name: collectorExporter.serviceName,
+ },
+ attributes: collectorExporter.attributes,
+ },
+ // resource: '', not implemented
+ spans,
+ };
+
+ const body = JSON.stringify(exportTraceServiceRequest);
+
+ if (typeof navigator.sendBeacon === 'function') {
+ sendSpansWithBeacon(
+ body,
+ onSuccess,
+ onError,
+ collectorExporter.logger,
+ collectorExporter.url
+ );
+ } else {
+ sendSpansWithXhr(
+ body,
+ onSuccess,
+ onError,
+ collectorExporter.logger,
+ collectorExporter.url
+ );
+ }
+}
+
+/**
+ * function to send spans using browser navigator.sendBeacon
+ * @param body
+ * @param onSuccess
+ * @param onError
+ * @param logger
+ * @param collectorUrl
+ */
+function sendSpansWithBeacon(
+ body: string,
+ onSuccess: () => void,
+ onError: (status?: number) => void,
+ logger: Logger,
+ collectorUrl: string
+) {
+ if (navigator.sendBeacon(collectorUrl, body)) {
+ logger.debug('sendBeacon - can send', body);
+ onSuccess();
+ } else {
+ logger.error('sendBeacon - cannot send', body);
+ onError();
+ }
+}
+
+/**
+ * function to send spans using browser XMLHttpRequest
+ * used when navigator.sendBeacon is not available
+ * @param body
+ * @param onSuccess
+ * @param onError
+ * @param logger
+ * @param collectorUrl
+ */
+function sendSpansWithXhr(
+ body: string,
+ onSuccess: () => void,
+ onError: (status?: number) => void,
+ logger: Logger,
+ collectorUrl: string
+) {
+ const xhr = new XMLHttpRequest();
+ xhr.open('POST', collectorUrl);
+ xhr.setRequestHeader(collectorTypes.OT_REQUEST_HEADER, '1');
+ xhr.send(body);
+
+ xhr.onreadystatechange = () => {
+ if (xhr.readyState === XMLHttpRequest.DONE) {
+ if (xhr.status >= 200 && xhr.status <= 299) {
+ logger.debug('xhr success', body);
+ onSuccess();
+ } else {
+ logger.error('xhr error', xhr.status, body);
+ onError(xhr.status);
+ }
+ }
+ };
+}
diff --git a/packages/opentelemetry-exporter-collector/src/platform/index.ts b/packages/opentelemetry-exporter-collector/src/platform/index.ts
new file mode 100644
index 0000000000..16f6fd9be2
--- /dev/null
+++ b/packages/opentelemetry-exporter-collector/src/platform/index.ts
@@ -0,0 +1,17 @@
+/*!
+ * Copyright 2019, 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-collector/src/platform/node/index.ts b/packages/opentelemetry-exporter-collector/src/platform/node/index.ts
new file mode 100644
index 0000000000..e2ba0ac5a4
--- /dev/null
+++ b/packages/opentelemetry-exporter-collector/src/platform/node/index.ts
@@ -0,0 +1,17 @@
+/*!
+ * Copyright 2019, 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 './sendSpans';
diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts b/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts
new file mode 100644
index 0000000000..aaf23d2820
--- /dev/null
+++ b/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts
@@ -0,0 +1,107 @@
+/*!
+ * Copyright 2019, 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 http from 'http';
+import * as https from 'https';
+
+import { IncomingMessage } from 'http';
+import * as core from '@opentelemetry/core';
+import { CollectorExporter } from '../../CollectorExporter';
+
+import * as collectorTypes from '../../types';
+
+import * as url from 'url';
+import { VERSION } from '../../version';
+
+/**
+ * function that is called once when {@link ExporterCollector} is initialised
+ * in node version this is not used
+ * @param shutdownF shutdown method of {@link ExporterCollector}
+ */
+export function onInit(shutdownF: Function) {}
+
+/**
+ * function to be called once when {@link ExporterCollector} is shutdown
+ * in node version this is not used
+ * @param shutdownF - shutdown method of {@link ExporterCollector}
+ */
+export function onShutdown(shutdownF: Function) {}
+
+/**
+ * function to send spans to the [opentelemetry collector]{@link https://github.com/open-telemetry/opentelemetry-collector}
+ * using the standard http/https node module
+ * @param spans
+ * @param onSuccess
+ * @param onError
+ * @param collectorExporter
+ */
+export function sendSpans(
+ spans: collectorTypes.Span[],
+ onSuccess: () => void,
+ onError: (status?: number) => void,
+ collectorExporter: CollectorExporter
+) {
+ const exportTraceServiceRequest: collectorTypes.ExportTraceServiceRequest = {
+ node: {
+ identifier: {
+ hostName: collectorExporter.hostName,
+ startTimestamp: core.hrTimeToTimeStamp(core.hrTime()),
+ },
+ libraryInfo: {
+ language: collectorTypes.LibraryInfoLanguage.NODE_JS,
+ coreLibraryVersion: core.VERSION,
+ exporterVersion: VERSION,
+ },
+ serviceInfo: {
+ name: collectorExporter.serviceName,
+ },
+ attributes: collectorExporter.attributes,
+ },
+ // resource: '', not implemented
+ spans,
+ };
+ const body = JSON.stringify(exportTraceServiceRequest);
+ const parsedUrl = url.parse(collectorExporter.url);
+
+ const options = {
+ hostname: parsedUrl.hostname,
+ port: parsedUrl.port,
+ path: parsedUrl.path,
+ method: 'POST',
+ headers: {
+ 'Content-Length': Buffer.byteLength(body),
+ [collectorTypes.OT_REQUEST_HEADER]: 1,
+ },
+ };
+
+ const request = parsedUrl.protocol === 'http:' ? http.request : https.request;
+ const req = request(options, (res: IncomingMessage) => {
+ if (res.statusCode && res.statusCode < 299) {
+ collectorExporter.logger.debug(`statusCode: ${res.statusCode}`);
+ onSuccess();
+ } else {
+ collectorExporter.logger.error(`statusCode: ${res.statusCode}`);
+ onError(res.statusCode);
+ }
+ });
+
+ req.on('error', (error: Error) => {
+ collectorExporter.logger.error('error', error.message);
+ onError();
+ });
+ req.write(body);
+ req.end();
+}
diff --git a/packages/opentelemetry-exporter-collector/src/transform.ts b/packages/opentelemetry-exporter-collector/src/transform.ts
new file mode 100644
index 0000000000..aaf024b025
--- /dev/null
+++ b/packages/opentelemetry-exporter-collector/src/transform.ts
@@ -0,0 +1,218 @@
+/*!
+ * Copyright 2019, 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 { hexToBase64, hrTimeToTimeStamp } from '@opentelemetry/core';
+import { ReadableSpan } from '@opentelemetry/tracing';
+import { Attributes, Link, TimedEvent, TraceState } from '@opentelemetry/types';
+import * as collectorTypes from './types';
+
+const OT_MAX_STRING_LENGTH = 128;
+
+/**
+ * convert string to maximum length of 128, providing information of truncated bytes
+ * @param name - string to be converted
+ */
+export function toCollectorTruncatableString(
+ name: string
+): collectorTypes.TruncatableString {
+ const value = name.substr(0, OT_MAX_STRING_LENGTH);
+ const truncatedByteCount =
+ name.length > OT_MAX_STRING_LENGTH ? name.length - OT_MAX_STRING_LENGTH : 0;
+
+ return { value, truncatedByteCount };
+}
+
+/**
+ * convert attributes
+ * @param attributes
+ */
+export function toCollectorAttributes(
+ attributes: Attributes
+): collectorTypes.Attributes {
+ const attributeMap: collectorTypes.AttributeMap = {};
+ Object.keys(attributes || {}).forEach(key => {
+ attributeMap[key] = toCollectorEventValue(attributes[key]);
+ });
+
+ return {
+ droppedAttributesCount: 0,
+ attributeMap,
+ };
+}
+
+/**
+ * convert event value
+ * @param value event value
+ */
+export function toCollectorEventValue(
+ value: unknown
+): collectorTypes.AttributeValue {
+ const attributeValue: collectorTypes.AttributeValue = {};
+
+ if (typeof value === 'string') {
+ attributeValue.stringValue = toCollectorTruncatableString(value);
+ } else if (typeof value === 'boolean') {
+ attributeValue.boolValue = value;
+ } else if (typeof value === 'number') {
+ // all numbers will be treated as double
+ attributeValue.doubleValue = value;
+ }
+
+ return attributeValue;
+}
+
+/**
+ * convert events
+ * @param events array of events
+ * @param maxAttributes - maximum number of event attributes to be converted
+ */
+export function toCollectorEvents(
+ events: TimedEvent[]
+): collectorTypes.TimeEvents {
+ let droppedAnnotationsCount = 0;
+ let droppedMessageEventsCount = 0; // not counting yet as messageEvent is not implemented
+
+ const timeEvent: collectorTypes.TimeEvent[] = events.map(
+ (event: TimedEvent) => {
+ let attributes: collectorTypes.Attributes | undefined;
+
+ if (event && event.attributes) {
+ attributes = toCollectorAttributes(event.attributes);
+ droppedAnnotationsCount += attributes.droppedAttributesCount || 0;
+ }
+
+ let annotation: collectorTypes.Annotation = {};
+ if (event.name || attributes) {
+ annotation = {};
+ }
+
+ if (event.name) {
+ annotation.description = toCollectorTruncatableString(event.name);
+ }
+
+ if (typeof attributes !== 'undefined') {
+ annotation.attributes = attributes;
+ }
+
+ // @TODO convert from event.attributes into appropriate MessageEvent
+ // const messageEvent: collectorTypes.MessageEvent;
+
+ const timeEvent: collectorTypes.TimeEvent = {
+ time: hrTimeToTimeStamp(event.time),
+ // messageEvent,
+ };
+
+ if (annotation) {
+ timeEvent.annotation = annotation;
+ }
+
+ return timeEvent;
+ }
+ );
+
+ return {
+ timeEvent,
+ droppedAnnotationsCount,
+ droppedMessageEventsCount,
+ };
+}
+
+/**
+ * determines the type of link, only parent link type can be determined now
+ * @TODO refactor this once such data is directly available from {@link Link}
+ * @param span
+ * @param link
+ */
+export function toCollectorLinkType(
+ span: ReadableSpan,
+ link: Link
+): collectorTypes.LinkType {
+ const linkSpanId = link.spanContext.spanId;
+ const linkTraceId = link.spanContext.traceId;
+ const spanParentId = span.parentSpanId;
+ const spanTraceId = span.spanContext.traceId;
+
+ if (linkSpanId === spanParentId && linkTraceId === spanTraceId) {
+ return collectorTypes.LinkType.PARENT_LINKED_SPAN;
+ }
+ return collectorTypes.LinkType.UNSPECIFIED;
+}
+
+/**
+ * converts span links
+ * @param span
+ */
+export function toCollectorLinks(span: ReadableSpan): collectorTypes.Links {
+ const collectorLinks: collectorTypes.Link[] = span.links.map((link: Link) => {
+ const collectorLink: collectorTypes.Link = {
+ traceId: hexToBase64(link.spanContext.traceId),
+ spanId: hexToBase64(link.spanContext.spanId),
+ type: toCollectorLinkType(span, link),
+ };
+
+ if (link.attributes) {
+ collectorLink.attributes = toCollectorAttributes(link.attributes);
+ }
+
+ return collectorLink;
+ });
+
+ return {
+ link: collectorLinks,
+ droppedLinksCount: 0,
+ };
+}
+
+/**
+ * @param span
+ */
+export function toCollectorSpan(span: ReadableSpan): collectorTypes.Span {
+ return {
+ traceId: hexToBase64(span.spanContext.traceId),
+ spanId: hexToBase64(span.spanContext.spanId),
+ parentSpanId: span.parentSpanId
+ ? hexToBase64(span.parentSpanId)
+ : undefined,
+ tracestate: toCollectorTraceState(span.spanContext.traceState),
+ name: toCollectorTruncatableString(span.name),
+ kind: span.kind,
+ startTime: hrTimeToTimeStamp(span.startTime),
+ endTime: hrTimeToTimeStamp(span.endTime),
+ attributes: toCollectorAttributes(span.attributes),
+ // stackTrace: // not implemented
+ timeEvents: toCollectorEvents(span.events),
+ status: span.status,
+ sameProcessAsParentSpan: !!span.parentSpanId,
+ links: toCollectorLinks(span),
+ // childSpanCount: // not implemented
+ };
+}
+
+/**
+ * @param traceState
+ */
+function toCollectorTraceState(
+ traceState?: TraceState
+): collectorTypes.TraceState {
+ if (!traceState) return {};
+ const entries = traceState.serialize().split(',');
+ const apiTraceState: collectorTypes.TraceState = {};
+ for (const entry of entries) {
+ const [key, value] = entry.split('=');
+ apiTraceState[key] = value;
+ }
+ return apiTraceState;
+}
diff --git a/packages/opentelemetry-exporter-collector/src/types.ts b/packages/opentelemetry-exporter-collector/src/types.ts
new file mode 100644
index 0000000000..1e5cb13b3d
--- /dev/null
+++ b/packages/opentelemetry-exporter-collector/src/types.ts
@@ -0,0 +1,520 @@
+/*!
+ * Copyright 2019, 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 { SpanKind, Status } from '@opentelemetry/types';
+
+// header to prevent instrumentation on request
+export const OT_REQUEST_HEADER = 'x-opentelemetry-outgoing-request';
+
+/**
+ * {@link https://github.com/open-telemetry/opentelemetry-proto/blob/master/opentelemetry/proto/agent/common/v1/common.proto#L66}
+ */
+export const enum LibraryInfoLanguage {
+ LANGUAGE_UNSPECIFIED = 0,
+ NODE_JS = 6,
+ WEB_JS = 10,
+}
+
+export interface AttributeMap {
+ [key: string]: AttributeValue;
+}
+
+/**
+ * A text annotation with a set of attributes.
+ */
+export interface Annotation {
+ /**
+ * A user-supplied message describing the event.
+ */
+ description?: TruncatableString;
+ /**
+ * A set of attributes on the annotation.
+ */
+ attributes?: Attributes;
+}
+
+/**
+ * A set of attributes, each with a key and a value.
+ */
+export interface Attributes {
+ /**
+ * \"/instance_id\": \"my-instance\" \"/http/user_agent\": \"\"
+ * \"/http/server_latency\": 300 \"abc.com/myattribute\": true
+ */
+ attributeMap?: AttributeMap;
+ /**
+ * The number of attributes that were discarded. Attributes can be discarded
+ * because their keys are too long or because there are too many attributes.
+ * If this value is 0, then no attributes were dropped.
+ */
+ droppedAttributesCount?: number;
+}
+
+/**
+ * The value of an Attribute.
+ */
+export interface AttributeValue {
+ /**
+ * A string up to 256 bytes long.
+ */
+ stringValue?: TruncatableString;
+ /**
+ * A 64-bit signed integer. May be sent to the API as either number or string
+ * type (string is needed to accurately express some 64-bit ints).
+ */
+ intValue?: string | number;
+ /**
+ * A Boolean value represented by `true` or `false`.
+ */
+ boolValue?: boolean;
+ /**
+ * A double precision floating point value.
+ */
+ doubleValue?: number;
+}
+
+/**
+ * Format for an HTTP/JSON request to a grpc-gateway for a trace span exporter.
+ */
+export interface ExportTraceServiceRequest {
+ node?: Node;
+
+ /** A list of Spans that belong to the last received Node. */
+ spans?: Span[];
+
+ /**
+ * The resource for the spans in this message that do not have an explicit
+ * resource set.
+ * If unset, the most recently set resource in the RPC stream applies. It is
+ * valid to never be set within a stream, e.g. when no resource info is known.
+ */
+ resource?: Resource;
+}
+
+/** Information on OpenTelemetry library that produced the spans/metrics. */
+export interface LibraryInfo {
+ /** Language of OpenTelemetry Library. */
+ language?: LibraryInfoLanguage;
+
+ /** Version of collector exporter of Library. */
+ exporterVersion?: string;
+
+ /** Version of OpenTelemetry Library. */
+ coreLibraryVersion?: string;
+}
+
+/**
+ * A pointer from the current span to another span in the same trace or in a
+ * different trace. For example, this can be used in batching operations, where
+ * a single batch handler processes multiple requests from different traces or
+ * when the handler receives a request from a different project.
+ */
+export interface Link {
+ /**
+ * A unique identifier for a trace. All spans from the same trace share the
+ * same `trace_id`. The ID is a 16-byte array.
+ */
+ traceId?: string;
+ /**
+ * A unique identifier for a span within a trace, assigned when the span is
+ * created. The ID is an 8-byte array.
+ */
+ spanId?: string;
+ /**
+ * The relationship of the current span relative to the linked span.
+ */
+ type?: LinkType;
+ /**
+ * A set of attributes on the link.
+ */
+ attributes?: Attributes;
+}
+
+/**
+ * A collection of links, which are references from this span to a span in the
+ * same or different trace.
+ */
+export interface Links {
+ /**
+ * A collection of links.
+ */
+ link?: Link[];
+ /**
+ * The number of dropped links after the maximum size was enforced. If this
+ * value is 0, then no links were dropped.
+ */
+ droppedLinksCount?: number;
+}
+
+/**
+ * The relationship of the current span relative to the linked span: child,
+ * parent, or unspecified.
+ */
+export const enum LinkType {
+ /**
+ * The relationship of the two spans is unknown, or known but other than
+ * parent-child.
+ */
+ UNSPECIFIED,
+ /** The linked span is a child of the current span. */
+ CHILD_LINKED_SPAN,
+ /** The linked span is a parent of the current span. */
+ PARENT_LINKED_SPAN,
+}
+
+/**
+ * An event describing a message sent/received between Spans.
+ */
+export interface MessageEvent {
+ /**
+ * The type of MessageEvent. Indicates whether the message was sent or
+ * received.
+ */
+ type?: MessageEventType;
+ /**
+ * An identifier for the MessageEvent's message that can be used to match SENT
+ * and RECEIVED MessageEvents. For example, this field could represent a
+ * sequence ID for a streaming RPC. It is recommended to be unique within a
+ * Span.
+ */
+ id?: string | number;
+ /**
+ * The number of uncompressed bytes sent or received.
+ */
+ uncompressedSize?: string | number;
+ /**
+ * The number of compressed bytes sent or received. If zero, assumed to be the
+ * same size as uncompressed.
+ */
+ compressedSize?: string | number;
+}
+
+/** Indicates whether the message was sent or received. */
+export const enum MessageEventType {
+ /** Unknown message event type. */
+ MESSAGE_EVENT_TYPE_UNSPECIFIED,
+ /** Indicates a sent message. */
+ MESSAGE_EVENT_TYPE_SENT,
+ /** Indicates a received message. */
+ MESSAGE_EVENT_TYPE_RECEIVED,
+}
+
+/**
+ * A description of a binary module.
+ */
+export interface Module {
+ /**
+ * TODO: document the meaning of this field. For example: main binary, kernel
+ * modules, and dynamic libraries such as libc.so, sharedlib.so.
+ */
+ module?: TruncatableString;
+ /**
+ * A unique identifier for the module, usually a hash of its contents.
+ */
+ buildId?: TruncatableString;
+}
+
+/**
+ * Identifier metadata of the Node (Application instrumented with OpenTelemetry)
+ * that connects to OpenTelemetry Agent.
+ * In the future we plan to extend the identifier proto definition to support
+ * additional information (e.g cloud id, etc.)
+ */
+export interface Node {
+ /** Identifier that uniquely identifies a process within a VM/container. */
+ identifier?: ProcessIdentifier;
+
+ /** Information on the OpenTelemetry Library that initiates the stream. */
+ libraryInfo?: LibraryInfo;
+
+ /** Additional information on service. */
+ serviceInfo?: ServiceInfo;
+
+ /** Additional attributes. */
+ attributes?: { [key: string]: unknown };
+}
+
+/**
+ * Identifier that uniquely identifies a process within a VM/container.
+ * For OpenTelemetry Web, this identifies the domain name of the site.
+ */
+export interface ProcessIdentifier {
+ /**
+ * The host name. Usually refers to the machine/container name.
+ * For example: os.Hostname() in Go, socket.gethostname() in Python.
+ * This will be the value of `window.location.host` for OpenTelemetry Web.
+ */
+ hostName?: string;
+
+ /** Process id. Not used in OpenTelemetry Web. */
+ pid?: number;
+
+ /** Start time of this ProcessIdentifier. Represented in epoch time.. */
+ startTimestamp?: string;
+}
+
+/** Resource information. */
+export interface Resource {
+ /** Type identifier for the resource. */
+ type?: string;
+
+ /** Set of labels that describe the resource. */
+ labels?: { [key: string]: string };
+}
+
+/** Additional service information. */
+export interface ServiceInfo {
+ /** Name of the service. */
+ name?: string;
+}
+
+/**
+ * A span represents a single operation within a trace. Spans can be nested to
+ * form a trace tree. Often, a trace contains a root span that describes the
+ * end-to-end latency, and one or more subspans for its sub-operations. A trace
+ * can also contain multiple root spans, or none at all. Spans do not need to be
+ * contiguous - there may be gaps or overlaps between spans in a trace. The
+ * next id is 16.
+ */
+export interface Span {
+ /**
+ * A unique identifier for a trace. All spans from the same trace share the
+ * same `trace_id`. The ID is a 16-byte array. This field is required.
+ */
+ traceId: string;
+ /**
+ * A unique identifier for a span within a trace, assigned when the span is
+ * created. The ID is an 8-byte array. This field is required.
+ */
+ spanId: string;
+ /**
+ * The `tracestate` field conveys information about request position in
+ * multiple distributed tracing graphs. There can be a maximum of 32 members
+ * in the map. The key must begin with a lowercase letter, and can only
+ * contain lowercase letters 'a'-'z', digits '0'-'9', underscores '_', dashes
+ * '-', asterisks '*', and forward slashes '/'. For multi-tenant vendors
+ * scenarios '@' sign can be used to prefix vendor name. The maximum length
+ * for the key is 256 characters. The value is opaque string up to 256
+ * characters printable ASCII RFC0020 characters (i.e., the range 0x20 to
+ * 0x7E) except ',' and '='. Note that this also excludes tabs, newlines,
+ * carriage returns, etc. See the https://github.com/w3c/distributed-tracing
+ * for more details about this field.
+ */
+ tracestate?: TraceState;
+ /**
+ * The `span_id` of this span's parent span. If this is a root span, then this
+ * field must be empty. The ID is an 8-byte array.
+ */
+ parentSpanId?: string;
+ /**
+ * A description of the span's operation. For example, the name can be a
+ * qualified method name or a file name and a line number where the operation
+ * is called. A best practice is to use the same display name at the same call
+ * point in an application. This makes it easier to correlate spans in
+ * different traces. This field is required.
+ */
+ name?: TruncatableString;
+ /**
+ * Distinguishes between spans generated in a particular context. For example,
+ * two spans with the same name may be distinguished using `CLIENT` and
+ * `SERVER` to identify queueing latency associated with the span.
+ */
+ kind?: SpanKind;
+ /**
+ * The start time of the span. On the client side, this is the time kept by
+ * the local machine where the span execution starts. On the server side, this
+ * is the time when the server's application handler starts running.
+ * the format should be a timestamp for example '2019-11-15T18:59:36.489343982Z'
+ */
+ startTime?: string;
+ /**
+ * The end time of the span. On the client side, this is the time kept by the
+ * local machine where the span execution ends. On the server side, this is
+ * the time when the server application handler stops running.
+ * the format should be a timestamp for example '2019-11-15T18:59:36.489343982Z'
+ */
+ endTime?: string;
+ /**
+ * A set of attributes on the span.
+ */
+ attributes?: Attributes;
+ /**
+ * A stack trace captured at the start of the span.
+ * Currently not used
+ */
+ stackTrace?: StackTrace;
+ /**
+ * The included time events.
+ */
+ timeEvents?: TimeEvents;
+ /**
+ * An optional final status for this span.
+ */
+ status?: Status;
+ /**
+ * A highly recommended but not required flag that identifies when a trace
+ * crosses a process boundary. True when the parent_span belongs to the same
+ * process as the current span.
+ */
+ sameProcessAsParentSpan?: boolean;
+
+ //@TODO - do we use it in opentelemetry or it is not needed?
+ // /**
+ // * An optional number of child spans that were generated while this span was
+ // * active. If set, allows an implementation to detect missing child spans.
+ // */
+ // childSpanCount?: number;
+ /**
+ * The included links.
+ */
+ links?: Links;
+}
+
+/**
+ * A single stack frame in a stack trace.
+ */
+export interface StackFrame {
+ /**
+ * The fully-qualified name that uniquely identifies the function or method
+ * that is active in this frame.
+ */
+ functionName?: TruncatableString;
+ /**
+ * An un-mangled function name, if `function_name` is
+ * [mangled](http://www.avabodh.com/cxxin/namemangling.html). The name can be
+ * fully qualified.
+ */
+ originalFunctionName?: TruncatableString;
+ /**
+ * The name of the source file where the function call appears.
+ */
+ fileName?: TruncatableString;
+ /**
+ * The line number in `file_name` where the function call appears.
+ */
+ lineNumber?: string;
+ /**
+ * The column number where the function call appears, if available. This is
+ * important in JavaScript because of its anonymous functions.
+ */
+ columnNumber?: string;
+ /**
+ * The binary module from where the code was loaded.
+ */
+ loadModule?: Module;
+ /**
+ * The version of the deployed source code.
+ */
+ sourceVersion?: TruncatableString;
+}
+
+/**
+ * A collection of stack frames, which can be truncated.
+ */
+export interface StackFrames {
+ /**
+ * Stack frames in this call stack.
+ */
+ frame?: StackFrame[];
+ /**
+ * The number of stack frames that were dropped because there were too many
+ * stack frames. If this value is 0, then no stack frames were dropped.
+ */
+ droppedFramesCount?: number;
+}
+
+/**
+ * The call stack which originated this span.
+ */
+export interface StackTrace {
+ /**
+ * Stack frames in this stack trace.
+ */
+ stackFrames?: StackFrames;
+ /**
+ * The hash ID is used to conserve network bandwidth for duplicate stack
+ * traces within a single trace. Often multiple spans will have identical
+ * stack traces. The first occurrence of a stack trace should contain both
+ * `stack_frames` and a value in `stack_trace_hash_id`. Subsequent spans
+ * within the same request can refer to that stack trace by setting only
+ * `stack_trace_hash_id`.
+ */
+ stackTraceHashId?: string;
+}
+
+/**
+ * A time-stamped annotation or message event in the Span.
+ */
+export interface TimeEvent {
+ /**
+ * The time the event occurred.
+ */
+ time?: string;
+ /**
+ * A text annotation with a set of attributes.
+ */
+ annotation?: Annotation;
+ /**
+ * An event describing a message sent/received between Spans.
+ */
+ messageEvent?: MessageEvent;
+}
+
+/**
+ * A collection of `TimeEvent`s. A `TimeEvent` is a time-stamped annotation on
+ * the span, consisting of either user-supplied key-value pairs, or details of a
+ * message sent/received between Spans.
+ */
+export interface TimeEvents {
+ /**
+ * A collection of `TimeEvent`s.
+ */
+ timeEvent?: TimeEvent[];
+ /**
+ * The number of dropped annotations in all the included time events. If the
+ * value is 0, then no annotations were dropped.
+ */
+ droppedAnnotationsCount?: number;
+ /**
+ * The number of dropped message events in all the included time events. If
+ * the value is 0, then no message events were dropped.
+ */
+ droppedMessageEventsCount?: number;
+}
+
+/**
+ * A string that might be shortened to a specified length.
+ */
+export interface TruncatableString {
+ /**
+ * The shortened string. For example, if the original string was 500 bytes
+ * long and the limit of the string was 128 bytes, then this value contains
+ * the first 128 bytes of the 500-byte string. Note that truncation always
+ * happens on a character boundary, to ensure that a truncated string is still
+ * valid UTF-8. Because it may contain multi-byte characters, the size of the
+ * truncated string may be less than the truncation limit.
+ */
+ value?: string;
+ /**
+ * The number of bytes removed from the original string. If this value is 0,
+ * then the string was not shortened.
+ */
+ truncatedByteCount?: number;
+}
+
+export interface TraceState {
+ [key: string]: string;
+}
diff --git a/packages/opentelemetry-exporter-collector/src/version.ts b/packages/opentelemetry-exporter-collector/src/version.ts
new file mode 100644
index 0000000000..5e6eb85dbe
--- /dev/null
+++ b/packages/opentelemetry-exporter-collector/src/version.ts
@@ -0,0 +1,18 @@
+/*!
+ * Copyright 2019, 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.
+ */
+
+// this is autogenerated file, see scripts/version-update.js
+export const VERSION = '0.2.0';
diff --git a/packages/opentelemetry-exporter-collector/test/browser/CollectorExporter.test.ts b/packages/opentelemetry-exporter-collector/test/browser/CollectorExporter.test.ts
new file mode 100644
index 0000000000..0a11981256
--- /dev/null
+++ b/packages/opentelemetry-exporter-collector/test/browser/CollectorExporter.test.ts
@@ -0,0 +1,203 @@
+/*!
+ * Copyright 2019, 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 {
+ CollectorExporter,
+ CollectorExporterConfig,
+} from '../../src/CollectorExporter';
+import * as collectorTypes from '../../src/types';
+
+import {
+ ensureExportTraceServiceRequestIsSet,
+ ensureSpanIsCorrect,
+ mockedReadableSpan,
+} from '../helper';
+const sendBeacon = navigator.sendBeacon;
+
+describe('CollectorExporter - web', () => {
+ let collectorExporter: CollectorExporter;
+ let collectorExporterConfig: CollectorExporterConfig;
+ let spyOpen: any;
+ let spySend: any;
+ let spyBeacon: any;
+ let spans: ReadableSpan[];
+
+ beforeEach(() => {
+ spyOpen = sinon.stub(XMLHttpRequest.prototype, 'open');
+ spySend = sinon.stub(XMLHttpRequest.prototype, 'send');
+ spyBeacon = sinon.stub(navigator, 'sendBeacon');
+ collectorExporterConfig = {
+ hostName: 'foo',
+ logger: new NoopLogger(),
+ serviceName: 'bar',
+ attributes: {},
+ url: 'http://foo.bar.com',
+ };
+ collectorExporter = new CollectorExporter(collectorExporterConfig);
+ spans = [];
+ spans.push(Object.assign({}, mockedReadableSpan));
+ });
+
+ afterEach(() => {
+ navigator.sendBeacon = sendBeacon;
+ spyOpen.restore();
+ spySend.restore();
+ spyBeacon.restore();
+ });
+
+ describe('export', () => {
+ describe('when "sendBeacon" is available', () => {
+ it('should successfully send the spans using sendBeacon', done => {
+ collectorExporter.export(spans, function() {});
+
+ setTimeout(() => {
+ const args = spyBeacon.args[0];
+ const url = args[0];
+ const body = args[1];
+ const json = JSON.parse(
+ body
+ ) as collectorTypes.ExportTraceServiceRequest;
+ const span1 = json.spans && json.spans[0];
+
+ assert.ok(typeof span1 !== 'undefined', "span doesn't exist");
+ if (span1) {
+ ensureSpanIsCorrect(span1);
+ }
+ assert.strictEqual(url, 'http://foo.bar.com');
+ assert.strictEqual(spyBeacon.callCount, 1);
+
+ assert.strictEqual(spyOpen.callCount, 0);
+
+ ensureExportTraceServiceRequestIsSet(json, 10);
+
+ done();
+ });
+ });
+
+ it('should log the successful message', done => {
+ const spyLoggerDebug = sinon.stub(collectorExporter.logger, 'debug');
+ const spyLoggerError = sinon.stub(collectorExporter.logger, 'error');
+ spyBeacon.restore();
+ spyBeacon = sinon.stub(window.navigator, 'sendBeacon').returns(true);
+
+ collectorExporter.export(spans, function() {});
+
+ setTimeout(() => {
+ const response: any = spyLoggerDebug.args[1][0];
+ assert.strictEqual(response, 'sendBeacon - can send');
+ assert.strictEqual(spyLoggerError.args.length, 0);
+
+ done();
+ });
+ });
+
+ it('should log the error message', done => {
+ const spyLoggerDebug = sinon.stub(collectorExporter.logger, 'debug');
+ const spyLoggerError = sinon.stub(collectorExporter.logger, 'error');
+ spyBeacon.restore();
+ spyBeacon = sinon.stub(window.navigator, 'sendBeacon').returns(false);
+
+ collectorExporter.export(spans, function() {});
+
+ setTimeout(() => {
+ const response: any = spyLoggerError.args[0][0];
+ assert.strictEqual(response, 'sendBeacon - cannot send');
+ assert.strictEqual(spyLoggerDebug.args.length, 1);
+
+ done();
+ });
+ });
+ });
+
+ describe('when "sendBeacon" is NOT available', () => {
+ let server: any;
+ beforeEach(() => {
+ // @ts-ignore
+ window.navigator.sendBeacon = false;
+ server = sinon.fakeServer.create();
+ });
+ afterEach(() => {
+ server.restore();
+ });
+
+ it('should successfully send the spans using XMLHttpRequest', done => {
+ collectorExporter.export(spans, function() {});
+
+ setTimeout(() => {
+ const request = server.requests[0];
+ assert.strictEqual(request.method, 'POST');
+ assert.strictEqual(request.url, 'http://foo.bar.com');
+
+ const body = request.requestBody;
+ const json = JSON.parse(
+ body
+ ) as collectorTypes.ExportTraceServiceRequest;
+ const span1 = json.spans && json.spans[0];
+
+ assert.ok(typeof span1 !== 'undefined', "span doesn't exist");
+ if (span1) {
+ ensureSpanIsCorrect(span1);
+ }
+ assert.strictEqual(spyBeacon.callCount, 0);
+
+ ensureExportTraceServiceRequestIsSet(json, 10);
+
+ done();
+ });
+ });
+
+ it('should log the successful message', done => {
+ const spyLoggerDebug = sinon.stub(collectorExporter.logger, 'debug');
+ const spyLoggerError = sinon.stub(collectorExporter.logger, 'error');
+
+ collectorExporter.export(spans, function() {});
+
+ setTimeout(() => {
+ const request = server.requests[0];
+ request.respond(200);
+
+ const response: any = spyLoggerDebug.args[1][0];
+ assert.strictEqual(response, 'xhr success');
+ assert.strictEqual(spyLoggerError.args.length, 0);
+
+ assert.strictEqual(spyBeacon.callCount, 0);
+ done();
+ });
+ });
+
+ it('should log the error message', done => {
+ const spyLoggerError = sinon.stub(collectorExporter.logger, 'error');
+
+ collectorExporter.export(spans, function() {});
+
+ setTimeout(() => {
+ const request = server.requests[0];
+ request.respond(400);
+
+ const response: any = spyLoggerError.args[0][0];
+ assert.strictEqual(response, 'xhr error');
+
+ assert.strictEqual(spyBeacon.callCount, 0);
+ done();
+ });
+ });
+ });
+ });
+});
diff --git a/packages/opentelemetry-exporter-collector/test/browser/index-webpack.ts b/packages/opentelemetry-exporter-collector/test/browser/index-webpack.ts
new file mode 100644
index 0000000000..9fdb7117a2
--- /dev/null
+++ b/packages/opentelemetry-exporter-collector/test/browser/index-webpack.ts
@@ -0,0 +1,26 @@
+/*!
+ * Copyright 2019, 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.
+ */
+
+// This file is the webpack entry point for the browser Karma tests. It requires
+// all modules ending in "test" from the current folder and all its subfolders.
+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-collector/test/common/CollectorExporter.test.ts b/packages/opentelemetry-exporter-collector/test/common/CollectorExporter.test.ts
new file mode 100644
index 0000000000..12b7985f1d
--- /dev/null
+++ b/packages/opentelemetry-exporter-collector/test/common/CollectorExporter.test.ts
@@ -0,0 +1,174 @@
+/*!
+ * Copyright 2019, 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 { ExportResult } from '@opentelemetry/base';
+import { NoopLogger } from '@opentelemetry/core';
+import { ReadableSpan } from '@opentelemetry/tracing';
+import * as assert from 'assert';
+import * as sinon from 'sinon';
+import {
+ CollectorExporter,
+ CollectorExporterConfig,
+} from '../../src/CollectorExporter';
+import * as collectorTypes from '../../src/types';
+import * as platform from '../../src/platform/index';
+
+import { ensureSpanIsCorrect, mockedReadableSpan } from '../helper';
+
+describe('CollectorExporter - common', () => {
+ let collectorExporter: CollectorExporter;
+ let collectorExporterConfig: CollectorExporterConfig;
+
+ describe('constructor', () => {
+ let onInitSpy: any;
+ beforeEach(() => {
+ onInitSpy = sinon.stub(platform, 'onInit');
+ collectorExporterConfig = {
+ hostName: 'foo',
+ logger: new NoopLogger(),
+ serviceName: 'bar',
+ attributes: {},
+ url: 'http://foo.bar.com',
+ };
+ collectorExporter = new CollectorExporter(collectorExporterConfig);
+ });
+ afterEach(() => {
+ onInitSpy.restore();
+ });
+
+ it('should create an instance', () => {
+ assert.ok(typeof collectorExporter !== 'undefined');
+ });
+
+ it('should call onInit', () => {
+ assert.strictEqual(onInitSpy.callCount, 1);
+ assert.ok(onInitSpy.args[0][0] === collectorExporter.shutdown);
+ });
+
+ describe('when config contains certain params', () => {
+ it('should set hostName', () => {
+ assert.strictEqual(collectorExporter.hostName, 'foo');
+ });
+
+ it('should set serviceName', () => {
+ assert.strictEqual(collectorExporter.serviceName, 'bar');
+ });
+
+ it('should set url', () => {
+ assert.strictEqual(collectorExporter.url, 'http://foo.bar.com');
+ });
+
+ it('should set logger', () => {
+ assert.ok(collectorExporter.logger === collectorExporterConfig.logger);
+ });
+ });
+
+ describe('when config is missing certain params', () => {
+ beforeEach(() => {
+ collectorExporter = new CollectorExporter();
+ });
+
+ it('should set default serviceName', () => {
+ assert.strictEqual(collectorExporter.serviceName, 'collector-exporter');
+ });
+
+ it('should set default logger', () => {
+ assert.ok(collectorExporter.logger instanceof NoopLogger);
+ });
+ });
+ });
+
+ describe('export', () => {
+ let spySend: any;
+ beforeEach(() => {
+ spySend = sinon.stub(platform, 'sendSpans');
+ collectorExporter = new CollectorExporter(collectorExporterConfig);
+ });
+ afterEach(() => {
+ spySend.restore();
+ });
+
+ it('should export spans as collectorTypes.Spans', done => {
+ const spans: ReadableSpan[] = [];
+ spans.push(Object.assign({}, mockedReadableSpan));
+
+ collectorExporter.export(spans, function() {});
+ setTimeout(() => {
+ const span1 = spySend.args[0][0][0] as collectorTypes.Span;
+ ensureSpanIsCorrect(span1);
+ done();
+ });
+ assert.strictEqual(spySend.callCount, 1);
+ });
+
+ describe('when exporter is shutdown', () => {
+ it('should not export anything but return callback with code "FailedNotRetryable"', () => {
+ const spans: ReadableSpan[] = [];
+ spans.push(Object.assign({}, mockedReadableSpan));
+ collectorExporter.shutdown();
+ spySend.resetHistory();
+
+ const callbackSpy = sinon.spy();
+ collectorExporter.export(spans, callbackSpy);
+ const returnCode = callbackSpy.args[0][0];
+
+ assert.strictEqual(
+ returnCode,
+ ExportResult.FAILED_NOT_RETRYABLE,
+ 'return value is wrong'
+ );
+ assert.strictEqual(spySend.callCount, 0, 'should not call send');
+ });
+ });
+ });
+
+ describe('shutdown', () => {
+ let onShutdownSpy: any;
+ beforeEach(() => {
+ onShutdownSpy = sinon.stub(platform, 'onShutdown');
+ collectorExporterConfig = {
+ hostName: 'foo',
+ logger: new NoopLogger(),
+ serviceName: 'bar',
+ attributes: {},
+ url: 'http://foo.bar.com',
+ };
+ collectorExporter = new CollectorExporter(collectorExporterConfig);
+ });
+ afterEach(() => {
+ onShutdownSpy.restore();
+ });
+
+ it('should export spans once only', done => {
+ collectorExporter.shutdown();
+ collectorExporter.shutdown();
+ collectorExporter.shutdown();
+
+ setTimeout(() => {
+ assert.strictEqual(onShutdownSpy.callCount, 1);
+ done();
+ });
+ });
+
+ it('should call onShutdown', done => {
+ collectorExporter.shutdown();
+ setTimeout(() => {
+ assert.ok(onShutdownSpy.args[0][0] === collectorExporter.shutdown);
+ done();
+ });
+ });
+ });
+});
diff --git a/packages/opentelemetry-exporter-collector/test/common/transform.test.ts b/packages/opentelemetry-exporter-collector/test/common/transform.test.ts
new file mode 100644
index 0000000000..d1227f04a8
--- /dev/null
+++ b/packages/opentelemetry-exporter-collector/test/common/transform.test.ts
@@ -0,0 +1,152 @@
+/*!
+ * Copyright 2019, 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 { Attributes, TimedEvent } from '@opentelemetry/types';
+import * as assert from 'assert';
+import * as transform from '../../src/transform';
+import { ensureSpanIsCorrect, mockedReadableSpan } from '../helper';
+
+describe('transform', () => {
+ describe('toCollectorTruncatableString', () => {
+ it('should convert string to TruncatableString', () => {
+ assert.deepStrictEqual(transform.toCollectorTruncatableString('foo'), {
+ truncatedByteCount: 0,
+ value: 'foo',
+ });
+ });
+
+ it('should convert long string to TruncatableString', () => {
+ let foo =
+ 'foo1234567890foo1234567890foo1234567890foo1234567890foo1234567890foo1234567890foo1234567890';
+ foo += foo;
+ assert.deepStrictEqual(transform.toCollectorTruncatableString(foo), {
+ truncatedByteCount: 54,
+ value:
+ 'foo1234567890foo1234567890foo1234567890foo1234567890foo1234567890foo1234567890foo1234567890foo1234567890foo1234567890foo12345678',
+ });
+ });
+ });
+
+ describe('toCollectorAttributes', () => {
+ it('should convert attribute string', () => {
+ const attributes: Attributes = {
+ foo: 'bar',
+ };
+ assert.deepStrictEqual(transform.toCollectorAttributes(attributes), {
+ attributeMap: {
+ foo: {
+ stringValue: {
+ truncatedByteCount: 0,
+ value: 'bar',
+ },
+ },
+ },
+ droppedAttributesCount: 0,
+ });
+ });
+
+ it('should convert attribute integer', () => {
+ const attributes: Attributes = {
+ foo: 13,
+ };
+ assert.deepStrictEqual(transform.toCollectorAttributes(attributes), {
+ attributeMap: {
+ foo: {
+ doubleValue: 13,
+ },
+ },
+ droppedAttributesCount: 0,
+ });
+ });
+
+ it('should convert attribute boolean', () => {
+ const attributes: Attributes = {
+ foo: true,
+ };
+ assert.deepStrictEqual(transform.toCollectorAttributes(attributes), {
+ attributeMap: {
+ foo: {
+ boolValue: true,
+ },
+ },
+ droppedAttributesCount: 0,
+ });
+ });
+
+ it('should convert attribute double', () => {
+ const attributes: Attributes = {
+ foo: 1.34,
+ };
+ assert.deepStrictEqual(transform.toCollectorAttributes(attributes), {
+ attributeMap: {
+ foo: {
+ doubleValue: 1.34,
+ },
+ },
+ droppedAttributesCount: 0,
+ });
+ });
+ });
+
+ describe('toCollectorEvents', () => {
+ it('should convert events to otc events', () => {
+ const events: TimedEvent[] = [
+ { name: 'foo', time: [123, 123], attributes: { a: 'b' } },
+ {
+ name: 'foo2',
+ time: [321, 321],
+ attributes: { c: 'd' },
+ },
+ ];
+ assert.deepStrictEqual(transform.toCollectorEvents(events), {
+ timeEvent: [
+ {
+ time: '1970-01-01T00:02:03.000000123Z',
+ annotation: {
+ description: { value: 'foo', truncatedByteCount: 0 },
+ attributes: {
+ droppedAttributesCount: 0,
+ attributeMap: {
+ a: { stringValue: { value: 'b', truncatedByteCount: 0 } },
+ },
+ },
+ },
+ },
+ {
+ time: '1970-01-01T00:05:21.000000321Z',
+ annotation: {
+ description: { value: 'foo2', truncatedByteCount: 0 },
+ attributes: {
+ droppedAttributesCount: 0,
+ attributeMap: {
+ c: { stringValue: { value: 'd', truncatedByteCount: 0 } },
+ },
+ },
+ },
+ },
+ ],
+ droppedAnnotationsCount: 0,
+ droppedMessageEventsCount: 0,
+ });
+ });
+ });
+
+ describe('toCollectorSpan', () => {
+ it('should convert span', () => {
+ ensureSpanIsCorrect(transform.toCollectorSpan(mockedReadableSpan));
+ });
+ });
+});
diff --git a/packages/opentelemetry-exporter-collector/test/helper.ts b/packages/opentelemetry-exporter-collector/test/helper.ts
new file mode 100644
index 0000000000..46071b7e7f
--- /dev/null
+++ b/packages/opentelemetry-exporter-collector/test/helper.ts
@@ -0,0 +1,195 @@
+/*!
+ * Copyright 2019, 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 core from '@opentelemetry/core';
+import { ReadableSpan } from '@opentelemetry/tracing';
+import * as assert from 'assert';
+import * as transform from '../src/transform';
+import * as collectorTypes from '../src/types';
+import { VERSION } from '../src/version';
+
+export const mockedReadableSpan: ReadableSpan = {
+ name: 'documentFetch',
+ kind: 0,
+ spanContext: {
+ traceId: '1f1008dc8e270e85c40a0d7c3939b278',
+ spanId: '5e107261f64fa53e',
+ traceFlags: 1,
+ },
+ parentSpanId: '78a8915098864388',
+ startTime: [1574120165, 429803070],
+ endTime: [1574120165, 438688070],
+ status: { code: 0 },
+ attributes: { component: 'document-load' },
+ links: [
+ {
+ spanContext: {
+ traceId: '1f1008dc8e270e85c40a0d7c3939b278',
+ spanId: '78a8915098864388',
+ traceFlags: 1,
+ },
+ attributes: { component: 'document-load' },
+ },
+ ],
+ events: [
+ { name: 'fetchStart', time: [1574120165, 429803070] },
+ {
+ name: 'domainLookupStart',
+ time: [1574120165, 429803070],
+ },
+ { name: 'domainLookupEnd', time: [1574120165, 429803070] },
+ {
+ name: 'connectStart',
+ time: [1574120165, 429803070],
+ },
+ { name: 'connectEnd', time: [1574120165, 429803070] },
+ {
+ name: 'requestStart',
+ time: [1574120165, 435513070],
+ },
+ { name: 'responseStart', time: [1574120165, 436923070] },
+ {
+ name: 'responseEnd',
+ time: [1574120165, 438688070],
+ },
+ ],
+ duration: [0, 8885000],
+};
+
+export function ensureSpanIsCorrect(span: collectorTypes.Span) {
+ assert.deepStrictEqual(transform.toCollectorSpan(mockedReadableSpan), {
+ traceId: 'HxAI3I4nDoXECg18OTmyeA==',
+ spanId: 'XhByYfZPpT4=',
+ parentSpanId: 'eKiRUJiGQ4g=',
+ tracestate: {},
+ name: { value: 'documentFetch', truncatedByteCount: 0 },
+ kind: 0,
+ startTime: '2019-11-18T23:36:05.429803070Z',
+ endTime: '2019-11-18T23:36:05.438688070Z',
+ attributes: {
+ droppedAttributesCount: 0,
+ attributeMap: {
+ component: {
+ stringValue: { value: 'document-load', truncatedByteCount: 0 },
+ },
+ },
+ },
+ timeEvents: {
+ timeEvent: [
+ {
+ time: '2019-11-18T23:36:05.429803070Z',
+ annotation: {
+ description: { value: 'fetchStart', truncatedByteCount: 0 },
+ },
+ },
+ {
+ time: '2019-11-18T23:36:05.429803070Z',
+ annotation: {
+ description: {
+ value: 'domainLookupStart',
+ truncatedByteCount: 0,
+ },
+ },
+ },
+ {
+ time: '2019-11-18T23:36:05.429803070Z',
+ annotation: {
+ description: {
+ value: 'domainLookupEnd',
+ truncatedByteCount: 0,
+ },
+ },
+ },
+ {
+ time: '2019-11-18T23:36:05.429803070Z',
+ annotation: {
+ description: { value: 'connectStart', truncatedByteCount: 0 },
+ },
+ },
+ {
+ time: '2019-11-18T23:36:05.429803070Z',
+ annotation: {
+ description: { value: 'connectEnd', truncatedByteCount: 0 },
+ },
+ },
+ {
+ time: '2019-11-18T23:36:05.435513070Z',
+ annotation: {
+ description: { value: 'requestStart', truncatedByteCount: 0 },
+ },
+ },
+ {
+ time: '2019-11-18T23:36:05.436923070Z',
+ annotation: {
+ description: { value: 'responseStart', truncatedByteCount: 0 },
+ },
+ },
+ {
+ time: '2019-11-18T23:36:05.438688070Z',
+ annotation: {
+ description: { value: 'responseEnd', truncatedByteCount: 0 },
+ },
+ },
+ ],
+ droppedAnnotationsCount: 0,
+ droppedMessageEventsCount: 0,
+ },
+ status: { code: 0 },
+ sameProcessAsParentSpan: true,
+ links: {
+ droppedLinksCount: 0,
+ link: [
+ {
+ traceId: 'HxAI3I4nDoXECg18OTmyeA==',
+ spanId: 'eKiRUJiGQ4g=',
+ type: 2,
+ attributes: {
+ droppedAttributesCount: 0,
+ attributeMap: {
+ component: {
+ stringValue: { value: 'document-load', truncatedByteCount: 0 },
+ },
+ },
+ },
+ },
+ ],
+ },
+ });
+}
+
+export function ensureExportTraceServiceRequestIsSet(
+ json: collectorTypes.ExportTraceServiceRequest,
+ languageInfo: collectorTypes.LibraryInfoLanguage
+) {
+ const libraryInfo = json.node && json.node.libraryInfo;
+ const serviceInfo = json.node && json.node.serviceInfo;
+ const identifier = json.node && json.node.identifier;
+
+ const language = libraryInfo && libraryInfo.language;
+ assert.strictEqual(language, languageInfo, 'language is missing');
+
+ const exporterVersion = libraryInfo && libraryInfo.exporterVersion;
+ assert.strictEqual(exporterVersion, VERSION, 'version is missing');
+
+ const coreVersion = libraryInfo && libraryInfo.coreLibraryVersion;
+ assert.strictEqual(coreVersion, core.VERSION, 'core version is missing');
+
+ const name = serviceInfo && serviceInfo.name;
+ assert.strictEqual(name, 'bar', 'name is missing');
+
+ const hostName = identifier && identifier.hostName;
+ assert.strictEqual(hostName, 'foo', 'hostName is missing');
+}
diff --git a/packages/opentelemetry-exporter-collector/test/node/CollectorExporter.test.ts b/packages/opentelemetry-exporter-collector/test/node/CollectorExporter.test.ts
new file mode 100644
index 0000000000..f057ce471d
--- /dev/null
+++ b/packages/opentelemetry-exporter-collector/test/node/CollectorExporter.test.ts
@@ -0,0 +1,149 @@
+/*!
+ * Copyright 2019, 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 core from '@opentelemetry/core';
+import { ReadableSpan } from '@opentelemetry/tracing';
+import * as http from 'http';
+import * as assert from 'assert';
+import * as sinon from 'sinon';
+import {
+ CollectorExporter,
+ CollectorExporterConfig,
+} from '../../src/CollectorExporter';
+import * as collectorTypes from '../../src/types';
+
+import {
+ ensureExportTraceServiceRequestIsSet,
+ ensureSpanIsCorrect,
+ mockedReadableSpan,
+} from '../helper';
+
+const fakeRequest = {
+ end: function() {},
+ on: function() {},
+ write: function() {},
+};
+
+const mockRes = {
+ statusCode: 200,
+};
+
+const mockResError = {
+ statusCode: 400,
+};
+
+describe('CollectorExporter - node', () => {
+ let collectorExporter: CollectorExporter;
+ let collectorExporterConfig: CollectorExporterConfig;
+ let spyRequest: any;
+ let spyWrite: any;
+ let spans: ReadableSpan[];
+ describe('export', () => {
+ beforeEach(() => {
+ spyRequest = sinon.stub(http, 'request').returns(fakeRequest as any);
+ spyWrite = sinon.stub(fakeRequest, 'write');
+ collectorExporterConfig = {
+ hostName: 'foo',
+ logger: new core.NoopLogger(),
+ serviceName: 'bar',
+ attributes: {},
+ url: 'http://foo.bar.com',
+ };
+ collectorExporter = new CollectorExporter(collectorExporterConfig);
+ spans = [];
+ spans.push(Object.assign({}, mockedReadableSpan));
+ });
+ afterEach(() => {
+ spyRequest.restore();
+ spyWrite.restore();
+ });
+
+ it('should open the connection', done => {
+ collectorExporter.export(spans, function() {});
+
+ setTimeout(() => {
+ const args = spyRequest.args[0];
+ const options = args[0];
+
+ assert.strictEqual(options.hostname, 'foo.bar.com');
+ assert.strictEqual(options.method, 'POST');
+ assert.strictEqual(options.path, '/');
+ done();
+ });
+ });
+
+ it('should successfully send the spans', done => {
+ collectorExporter.export(spans, function() {});
+
+ setTimeout(() => {
+ const writeArgs = spyWrite.args[0];
+ const json = JSON.parse(
+ writeArgs[0]
+ ) as collectorTypes.ExportTraceServiceRequest;
+ const span1 = json.spans && json.spans[0];
+ assert.ok(typeof span1 !== 'undefined', "span doesn't exist");
+ if (span1) {
+ ensureSpanIsCorrect(span1);
+ }
+
+ ensureExportTraceServiceRequestIsSet(json, 6);
+
+ done();
+ });
+ });
+
+ it('should log the successful message', done => {
+ const spyLoggerDebug = sinon.stub(collectorExporter.logger, 'debug');
+ const spyLoggerError = sinon.stub(collectorExporter.logger, 'error');
+
+ const responseSpy = sinon.spy();
+ collectorExporter.export(spans, responseSpy);
+
+ setTimeout(() => {
+ const args = spyRequest.args[0];
+ const callback = args[1];
+ callback(mockRes);
+ setTimeout(() => {
+ const response: any = spyLoggerDebug.args[1][0];
+ assert.strictEqual(response, 'statusCode: 200');
+ assert.strictEqual(spyLoggerError.args.length, 0);
+ assert.strictEqual(responseSpy.args[0][0], 0);
+ done();
+ });
+ });
+ });
+
+ it('should log the error message', done => {
+ const spyLoggerError = sinon.stub(collectorExporter.logger, 'error');
+
+ const responseSpy = sinon.spy();
+ collectorExporter.export(spans, responseSpy);
+
+ setTimeout(() => {
+ const args = spyRequest.args[0];
+ const callback = args[1];
+ callback(mockResError);
+ setTimeout(() => {
+ const response: any = spyLoggerError.args[0][0];
+ assert.strictEqual(response, 'statusCode: 400');
+
+ assert.strictEqual(responseSpy.args[0][0], 1);
+ done();
+ });
+ });
+ });
+ });
+});
diff --git a/packages/opentelemetry-exporter-collector/tsconfig-release.json b/packages/opentelemetry-exporter-collector/tsconfig-release.json
new file mode 100644
index 0000000000..ffc0f77968
--- /dev/null
+++ b/packages/opentelemetry-exporter-collector/tsconfig-release.json
@@ -0,0 +1,7 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "types": []
+ },
+ "include": ["src/**/*.ts"]
+}
diff --git a/packages/opentelemetry-exporter-collector/tsconfig.json b/packages/opentelemetry-exporter-collector/tsconfig.json
new file mode 100644
index 0000000000..a2042cd68b
--- /dev/null
+++ b/packages/opentelemetry-exporter-collector/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "extends": "../tsconfig.base",
+ "compilerOptions": {
+ "rootDir": ".",
+ "outDir": "build"
+ },
+ "include": [
+ "src/**/*.ts",
+ "test/**/*.ts"
+ ]
+}
diff --git a/packages/opentelemetry-exporter-collector/tslint.json b/packages/opentelemetry-exporter-collector/tslint.json
new file mode 100644
index 0000000000..0710b135d0
--- /dev/null
+++ b/packages/opentelemetry-exporter-collector/tslint.json
@@ -0,0 +1,4 @@
+{
+ "rulesDirectory": ["node_modules/tslint-microsoft-contrib"],
+ "extends": ["../../tslint.base.js", "./node_modules/tslint-consistent-codestyle"]
+}