From abaadf8c993b946d65c1f6c119ce932fbdf5488d Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Wed, 20 Nov 2019 17:13:59 +0100 Subject: [PATCH 01/25] chore: linting --- .../src/prometheus.ts | 12 ++++-------- packages/opentelemetry-plugin-grpc/src/grpc.ts | 9 +++------ .../opentelemetry-plugin-pg/src/pg.ts | 7 +++---- 3 files changed, 10 insertions(+), 18 deletions(-) diff --git a/packages/opentelemetry-exporter-prometheus/src/prometheus.ts b/packages/opentelemetry-exporter-prometheus/src/prometheus.ts index 69f4545909..4af6471295 100644 --- a/packages/opentelemetry-exporter-prometheus/src/prometheus.ts +++ b/packages/opentelemetry-exporter-prometheus/src/prometheus.ts @@ -131,19 +131,15 @@ export class PrometheusExporter implements MetricExporter { // ReadableMetric value is the current state, not the delta to be incremented by. // Currently, _registerMetric creates a new counter every time the value changes, // so the increment here behaves as a set value (increment from 0) - metric.inc( - this._getLabelValues(labelKeys, ts.labelValues), - ts.points[0].value as number - ); + metric.inc(this._getLabelValues(labelKeys, ts.labelValues), ts.points[0] + .value as number); } } if (metric instanceof Gauge) { for (const ts of readableMetric.timeseries) { - metric.set( - this._getLabelValues(labelKeys, ts.labelValues), - ts.points[0].value as number - ); + metric.set(this._getLabelValues(labelKeys, ts.labelValues), ts.points[0] + .value as number); } } diff --git a/packages/opentelemetry-plugin-grpc/src/grpc.ts b/packages/opentelemetry-plugin-grpc/src/grpc.ts index 9c44c65abd..531bd80bbf 100644 --- a/packages/opentelemetry-plugin-grpc/src/grpc.ts +++ b/packages/opentelemetry-plugin-grpc/src/grpc.ts @@ -354,12 +354,9 @@ export class GrpcPlugin extends BasePlugin { parent: currentSpan || undefined, }) .setAttribute(AttributeNames.COMPONENT, GrpcPlugin.component); - return plugin._makeGrpcClientRemoteCall( - original, - args, - this, - plugin - )(span); + return plugin._makeGrpcClientRemoteCall(original, args, this, plugin)( + span + ); }; }; } diff --git a/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg/src/pg.ts b/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg/src/pg.ts index a6841ae33a..e41ceee08a 100644 --- a/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg/src/pg.ts +++ b/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg/src/pg.ts @@ -97,10 +97,9 @@ export class PostgresPlugin extends BasePlugin { const parentSpan = plugin._tracer.getCurrentSpan(); if (typeof args[args.length - 1] === 'function') { // Patch ParameterQuery callback - args[args.length - 1] = utils.patchCallback( - span, - args[args.length - 1] as PostgresCallback - ); + args[args.length - 1] = utils.patchCallback(span, args[ + args.length - 1 + ] as PostgresCallback); // If a parent span exists, bind the callback if (parentSpan) { args[args.length - 1] = plugin._tracer.bind( From 7af153f5368aadbda3025a882d8fe2527cef57fb Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Wed, 20 Nov 2019 18:04:01 +0100 Subject: [PATCH 02/25] feat(collector-exporter): new exporter for opentelemetry collector --- examples/basic-tracer-node/README.md | 19 + .../docker/oc/collector-config.yaml | 7 + .../docker/oc/docker-compose.yaml | 19 + .../docker/ot/collector-config.yaml | 18 + .../docker/ot/docker-compose.yaml | 19 + examples/basic-tracer-node/index.js | 7 +- examples/basic-tracer-node/multi_exporter.js | 7 +- examples/basic-tracer-node/package.json | 5 + examples/tracer-web/index.html | 2 +- examples/tracer-web/index.js | 2 + examples/tracer-web/package.json | 1 + packages/opentelemetry-core/package.json | 3 +- .../opentelemetry-core/src/common/time.ts | 27 ++ .../src/platform/browser/index.ts | 1 + .../src/platform/browser/span-id-to-base64.ts | 30 ++ .../src/platform/node/index.ts | 1 + .../src/platform/node/span-id-to-base64.ts | 31 ++ .../test/common/time.test.ts | 29 ++ .../test/platform/span-id-to-base64.test.ts | 27 ++ .../.npmignore | 4 + .../opentelemetry-exporter-collector/LICENSE | 201 +++++++++ .../README.md | 35 ++ .../karma.conf.js | 26 ++ .../package.json | 86 ++++ .../src/CollectorExporter.ts | 136 ++++++ .../src/index.ts | 17 + .../src/platform/browser/index.ts | 17 + .../src/platform/browser/sendSpans.ts | 151 +++++++ .../src/platform/index.ts | 17 + .../src/platform/node/index.ts | 17 + .../src/platform/node/sendSpans.ts | 106 +++++ .../src/types.ts | 421 ++++++++++++++++++ .../src/util.ts | 205 +++++++++ .../test/browser/CollectorExporter.test.ts | 189 ++++++++ .../test/browser/index-webpack.ts | 26 ++ .../test/common/CollectorExporter.test.ts | 155 +++++++ .../test/common/util.test.ts | 261 +++++++++++ .../test/helper.ts | 86 ++++ .../test/node/CollectorExporter.test.ts | 136 ++++++ .../tsconfig-release.json | 7 + .../tsconfig.json | 11 + .../tslint.json | 4 + 42 files changed, 2564 insertions(+), 5 deletions(-) create mode 100644 examples/basic-tracer-node/docker/oc/collector-config.yaml create mode 100644 examples/basic-tracer-node/docker/oc/docker-compose.yaml create mode 100644 examples/basic-tracer-node/docker/ot/collector-config.yaml create mode 100644 examples/basic-tracer-node/docker/ot/docker-compose.yaml create mode 100644 packages/opentelemetry-core/src/platform/browser/span-id-to-base64.ts create mode 100644 packages/opentelemetry-core/src/platform/node/span-id-to-base64.ts create mode 100644 packages/opentelemetry-core/test/platform/span-id-to-base64.test.ts create mode 100644 packages/opentelemetry-exporter-collector/.npmignore create mode 100644 packages/opentelemetry-exporter-collector/LICENSE create mode 100644 packages/opentelemetry-exporter-collector/README.md create mode 100644 packages/opentelemetry-exporter-collector/karma.conf.js create mode 100644 packages/opentelemetry-exporter-collector/package.json create mode 100644 packages/opentelemetry-exporter-collector/src/CollectorExporter.ts create mode 100644 packages/opentelemetry-exporter-collector/src/index.ts create mode 100644 packages/opentelemetry-exporter-collector/src/platform/browser/index.ts create mode 100644 packages/opentelemetry-exporter-collector/src/platform/browser/sendSpans.ts create mode 100644 packages/opentelemetry-exporter-collector/src/platform/index.ts create mode 100644 packages/opentelemetry-exporter-collector/src/platform/node/index.ts create mode 100644 packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts create mode 100644 packages/opentelemetry-exporter-collector/src/types.ts create mode 100644 packages/opentelemetry-exporter-collector/src/util.ts create mode 100644 packages/opentelemetry-exporter-collector/test/browser/CollectorExporter.test.ts create mode 100644 packages/opentelemetry-exporter-collector/test/browser/index-webpack.ts create mode 100644 packages/opentelemetry-exporter-collector/test/common/CollectorExporter.test.ts create mode 100644 packages/opentelemetry-exporter-collector/test/common/util.test.ts create mode 100644 packages/opentelemetry-exporter-collector/test/helper.ts create mode 100644 packages/opentelemetry-exporter-collector/test/node/CollectorExporter.test.ts create mode 100644 packages/opentelemetry-exporter-collector/tsconfig-release.json create mode 100644 packages/opentelemetry-exporter-collector/tsconfig.json create mode 100644 packages/opentelemetry-exporter-collector/tslint.json diff --git a/examples/basic-tracer-node/README.md b/examples/basic-tracer-node/README.md index d7e0feb9ea..510f68bb3b 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,22 @@ Click on the trace to view its details.

+### Collector Exporter +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 + $ # or alternatively open census + $ npm run collector:docker:oc + $ # at any time you can stop & clean them + $ npm run collector:docker:stop + ``` + +#### Collector Exporter - Zipkin UI +Please follow the section [Zipkin UI](#zipkin-ui) only + ### Export to multiple exporters - Run the sample diff --git a/examples/basic-tracer-node/docker/oc/collector-config.yaml b/examples/basic-tracer-node/docker/oc/collector-config.yaml new file mode 100644 index 0000000000..542ed48b85 --- /dev/null +++ b/examples/basic-tracer-node/docker/oc/collector-config.yaml @@ -0,0 +1,7 @@ +receivers: + opencensus: + address: "127.0.0.1:55678" + +exporters: + zipkin: + endpoint: "http://zipkin-all-in-one:9411/api/v2/spans" diff --git a/examples/basic-tracer-node/docker/oc/docker-compose.yaml b/examples/basic-tracer-node/docker/oc/docker-compose.yaml new file mode 100644 index 0000000000..fe584fbfe7 --- /dev/null +++ b/examples/basic-tracer-node/docker/oc/docker-compose.yaml @@ -0,0 +1,19 @@ +version: "2" +services: + + # Collector + collector: + image: omnition/opencensus-collector + command: ["--config=/conf/collector-config.yaml", "--http-pprof-port=1777", "--receive-zipkin", "--receive-oc-trace", "--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/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..1443d273ac 100644 --- a/examples/basic-tracer-node/package.json +++ b/examples/basic-tracer-node/package.json @@ -7,6 +7,10 @@ "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:oc": "cd ./dockers/ot && docker-compose down && cd ../oc && docker-compose down && docker-compose up", + "collector:docker:ot": "cd ./dockers/oc && docker-compose down && cd ../ot && docker-compose down && docker-compose up", + "collector:docker:stop": "cd ./dockers/oc && docker-compose down && cd ../ot && docker-compose down", "multi_exporter": "node ./multi_exporter.js" }, "repository": { @@ -28,6 +32,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..07d89882b6 100644 --- a/packages/opentelemetry-core/package.json +++ b/packages/opentelemetry-core/package.json @@ -22,7 +22,8 @@ "precompile": "tsc --version", "compile": "tsc -p .", "fix": "gts fix", - "prepare": "npm run compile" + "prepare": "npm run compile", + "watch": "tsc -w" }, "keywords": [ "opentelemetry", diff --git a/packages/opentelemetry-core/src/common/time.ts b/packages/opentelemetry-core/src/common/time.ts index fcc564692e..283ebaa430 100644 --- a/packages/opentelemetry-core/src/common/time.ts +++ b/packages/opentelemetry-core/src/common/time.ts @@ -99,6 +99,33 @@ export function hrTimeDuration( return [seconds, nanos]; } +// Returns end time based on startTime and duration +export function hrTimeEndTime( + startTime: types.HrTime, + duration: types.HrTime +): types.HrTime { + let seconds = startTime[0] + duration[0]; + let nanos = startTime[1] + duration[1]; + + // overflow + if (nanos < 0) { + seconds -= 1; + // negate + nanos += SECOND_TO_NANOSECONDS; + } + + return [seconds, nanos]; +} + +// Convert hrTime to timestamp. +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. export function hrTimeToNanoseconds(hrTime: types.HrTime): number { return hrTime[0] * SECOND_TO_NANOSECONDS + hrTime[1]; diff --git a/packages/opentelemetry-core/src/platform/browser/index.ts b/packages/opentelemetry-core/src/platform/browser/index.ts index 8b1b9afe53..e95d5013fe 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 './span-id-to-base64'; diff --git a/packages/opentelemetry-core/src/platform/browser/span-id-to-base64.ts b/packages/opentelemetry-core/src/platform/browser/span-id-to-base64.ts new file mode 100644 index 0000000000..8fd65040d0 --- /dev/null +++ b/packages/opentelemetry-core/src/platform/browser/span-id-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 spanIdToBase64(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/node/index.ts b/packages/opentelemetry-core/src/platform/node/index.ts index 8b1b9afe53..e95d5013fe 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 './span-id-to-base64'; diff --git a/packages/opentelemetry-core/src/platform/node/span-id-to-base64.ts b/packages/opentelemetry-core/src/platform/node/span-id-to-base64.ts new file mode 100644 index 0000000000..4158708530 --- /dev/null +++ b/packages/opentelemetry-core/src/platform/node/span-id-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 spanIdToBase64(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/test/common/time.test.ts b/packages/opentelemetry-core/test/common/time.test.ts index 4eb82a9b78..e4ce78857b 100644 --- a/packages/opentelemetry-core/test/common/time.test.ts +++ b/packages/opentelemetry-core/test/common/time.test.ts @@ -25,7 +25,9 @@ import { hrTimeToNanoseconds, hrTimeToMilliseconds, hrTimeToMicroseconds, + hrTimeToTimeStamp, isTimeInput, + hrTimeEndTime, } from '../../src/common/time'; describe('time', () => { @@ -156,6 +158,33 @@ describe('time', () => { }); }); + describe('#hrTimeEndTime', () => { + it('should return endTime', () => { + const startTime: types.HrTime = [22, 400000000]; + const durationTime: types.HrTime = [32, 800000000]; + + const output = hrTimeEndTime(startTime, durationTime); + assert.deepStrictEqual(output, [54, 1200000000]); + }); + + it('should handle nanosecond overflow', () => { + const startTime: types.HrTime = [22, 400000000]; + const durationTime: types.HrTime = [32, 200000000]; + + const output = hrTimeEndTime(startTime, durationTime); + assert.deepStrictEqual(output, [54, 600000000]); + }); + }); + + 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/span-id-to-base64.test.ts b/packages/opentelemetry-core/test/platform/span-id-to-base64.test.ts new file mode 100644 index 0000000000..3ec10db223 --- /dev/null +++ b/packages/opentelemetry-core/test/platform/span-id-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 { spanIdToBase64 } from '../../src/platform'; + +describe('idToBase64', () => { + it('returns convert id to base64', () => { + const id1 = '7deb739e02e44ef2'; + const id2 = '46cef837b919a16ff26e608c8cf42c80'; + assert.strictEqual(spanIdToBase64(id1), 'fetzngLkTvI='); + assert.strictEqual(spanIdToBase64(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..09fc2c1657 --- /dev/null +++ b/packages/opentelemetry-exporter-collector/README.md @@ -0,0 +1,35 @@ +# OpenTelemetry Exporter Web Collector +[![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 to be used with [opentelemetry-collector][opentelemetry-collector-url]. + +## Installation + +```bash +npm install --save @opentelemetry/exporter-collector +``` + +## 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-web-collector +[dependencies-url]: https://david-dm.org/open-telemetry/opentelemetry-js?path=packages%2Fopentelemetry-exporter-web-collector +[devDependencies-image]: https://david-dm.org/open-telemetry/opentelemetry-js/dev-status.svg?path=packages/opentelemetry-exporter-web-collector +[devDependencies-url]: https://david-dm.org/open-telemetry/opentelemetry-js?path=packages%2Fopentelemetry-exporter-web-collector&type=dev +[npm-url]: https://www.npmjs.com/package/@opentelemetry/exporter-web-collector +[npm-img]: https://badge.fury.io/js/%40opentelemetry%exporter-web-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..1f6e3b1654 --- /dev/null +++ b/packages/opentelemetry-exporter-collector/package.json @@ -0,0 +1,86 @@ +{ + "name": "@opentelemetry/exporter-collector", + "version": "0.2.0", + "description": "OpenTelemetry Exporter Collector for Web and Node", + "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": "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", + "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/src/CollectorExporter.ts b/packages/opentelemetry-exporter-collector/src/CollectorExporter.ts new file mode 100644 index 0000000000..6d884eb107 --- /dev/null +++ b/packages/opentelemetry-exporter-collector/src/CollectorExporter.ts @@ -0,0 +1,136 @@ +/*! + * 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 { OTCSpan } from './types'; +import { convertSpan } from './util'; + +import { onInit, onShutdown, sendSpans } from './platform/index'; + +/** + * Collector Exporter Config + */ +export interface CollectorExporterConfig { + hostName?: string; + logger?: Logger; + serviceName?: string; + spanAttributes?: Attributes; + url?: string; +} + +const defaultServiceName = 'collector-exporter'; +const defaultCollectorUrl = '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; + private _isShutdown: boolean = false; + + /** + * @param config + */ + constructor(config: CollectorExporterConfig = {}) { + this.serviceName = config.serviceName || defaultServiceName; + this.url = config.url || defaultCollectorUrl; + if (typeof config.hostName === 'string') { + this.hostName = config.hostName; + } + + 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 + ) { + 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: OTCSpan[] = spans.map(span => convertSpan(span)); + this.logger.debug('spans to be sent', spansToBeSent); + this.sendSpan(spansToBeSent, resolve, reject); + } catch (e) { + reject(e); + } + }); + } + + /** + * Send spans to [opentelemetry collector]{@link https://github.com/open-telemetry/opentelemetry-collector} + * it will use the appropriate transport layer automatically depends on platform + * @param spans + * @param onSuccess + * @param onError + */ + sendSpan( + spans: OTCSpan[], + onSuccess: () => void, + onError: (status?: number) => void + ) { + // platform dependent + sendSpans(spans, onSuccess, onError, this); + } + + /** + * 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); + + this._exportSpans([]).then(() => { + this.logger.debug('shutdown completed'); + }); + } +} 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..c4405ebbd2 --- /dev/null +++ b/packages/opentelemetry-exporter-collector/src/platform/browser/sendSpans.ts @@ -0,0 +1,151 @@ +/*! + * 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 { hrTime, hrTimeToTimeStamp } from '@opentelemetry/core'; +import { Logger } from '@opentelemetry/types'; +import { CollectorExporter } from '../../CollectorExporter'; +import { + LibraryInfoLanguage, + OTCExportTraceServiceRequest, + OTCSpan, +} from '../../types'; + +/** + * 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); +} + +/** + * + * @param spans + * @param onSuccess + * @param onError + * @param collectorExporter + */ +export function sendSpans( + spans: OTCSpan[], + onSuccess: () => void, + onError: (status?: number) => void, + collectorExporter: CollectorExporter +) { + const ocExportTraceServiceRequest: OTCExportTraceServiceRequest = { + node: { + identifier: { + hostName: collectorExporter.hostName || window.location.host, + startTimestamp: hrTimeToTimeStamp(hrTime()), + }, + libraryInfo: { + language: LibraryInfoLanguage.WEB_JS, + // coreLibraryVersion: , not implemented + // exporterVersion: , not implemented + // coreLibraryVersion: , not implemented + }, + serviceInfo: { + name: collectorExporter.serviceName, + }, + // attributes: {} + }, + // resource: '', not implemented + spans, + }; + + const body = JSON.stringify(ocExportTraceServiceRequest); + + if (typeof navigator.sendBeacon === 'function') { + sendSpansWithBeacon( + body, + onSuccess, + onError, + collectorExporter.logger, + collectorExporter.url + ); + } else { + sendSpansWithXhr( + body, + onSuccess, + onError, + collectorExporter.logger, + collectorExporter.url + ); + } +} + +/** + * + * @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(); + } +} + +/** + * + * @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.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..26b074a3c7 --- /dev/null +++ b/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts @@ -0,0 +1,106 @@ +/*! + * 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 http = require('http'); +const https = require('https'); + +import { IncomingMessage } from 'http'; +import { hrTime, hrTimeToTimeStamp } from '@opentelemetry/core'; +import { CollectorExporter } from '../../CollectorExporter'; +import { + LibraryInfoLanguage, + OTCExportTraceServiceRequest, + OTCSpan, +} from '../../types'; + +const url = require('url'); + +/** + * function that is called once when {@link ExporterCollector} is initialised + * in node version thsi 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 thsi is not used + * @param shutdownF - shutdown method of {@link ExporterCollector} + */ +export function onShutdown(shutdownF: Function) {} + +/** + * + * @param spans + * @param onSuccess + * @param onError + * @param collectorExporter + */ +export function sendSpans( + spans: OTCSpan[], + onSuccess: () => void, + onError: (status?: number) => void, + collectorExporter: CollectorExporter +) { + const ocExportTraceServiceRequest: OTCExportTraceServiceRequest = { + node: { + identifier: { + hostName: collectorExporter.hostName, + startTimestamp: hrTimeToTimeStamp(hrTime()), + }, + libraryInfo: { + language: LibraryInfoLanguage.NODE_JS, + // coreLibraryVersion: , not implemented + // exporterVersion: , not implemented + // coreLibraryVersion: , not implemented + }, + serviceInfo: { + name: collectorExporter.serviceName, + }, + // attributes: {} + }, + // resource: '', not implemented + spans, + }; + const body = JSON.stringify(ocExportTraceServiceRequest); + const parsedUrl = url.parse(collectorExporter.url); + + const options = { + hostname: parsedUrl.hostname, + port: parsedUrl.port, + path: parsedUrl.path, + method: 'POST', + timeout: 5000, + headers: { + 'Content-Length': Buffer.byteLength(body), + }, + }; + + 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}`); + } else { + collectorExporter.logger.error(`statusCode: ${res.statusCode}`); + } + }); + + req.on('error', (error: Error) => { + collectorExporter.logger.error('error', error.message); + }); + req.write(body); + req.end(); +} diff --git a/packages/opentelemetry-exporter-collector/src/types.ts b/packages/opentelemetry-exporter-collector/src/types.ts new file mode 100644 index 0000000000..7f67303bad --- /dev/null +++ b/packages/opentelemetry-exporter-collector/src/types.ts @@ -0,0 +1,421 @@ +/*! + * 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'; + +/** + * {@link https://github.com/open-telemetry/opentelemetry-proto/blob/master/opentelemetry/proto/agent/common/v1/common.proto#L66} + */ +export enum LibraryInfoLanguage { + LANGUAGE_UNSPECIFIED = 0, + NODE_JS = 6, + WEB_JS = 10, +} + +export interface OTCAttributeMap { + [key: string]: OTCAttributeValue; +} + +/** + * A text annotation with a set of attributes. + */ +export interface OTCAnnotation { + /** + * A user-supplied message describing the event. + */ + description?: OTCTruncatableString; + /** + * A set of attributes on the annotation. + */ + attributes?: OTCAttributes; +} + +/** + * A set of attributes, each with a key and a value. + */ +export interface OTCAttributes { + /** + * \"/instance_id\": \"my-instance\" \"/http/user_agent\": \"\" + * \"/http/server_latency\": 300 \"abc.com/myattribute\": true + */ + attributeMap?: OTCAttributeMap; + /** + * 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 OTCAttributeValue { + /** + * A string up to 256 bytes long. + */ + stringValue?: OTCTruncatableString; + /** + * 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 OTCExportTraceServiceRequest { + node?: OTCNode; + + /** A list of Spans that belong to the last received Node. */ + spans?: OTCSpan[]; + + /** + * 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?: OTCResource; +} + +/** Information on OpenTelemetry library that produced the spans/metrics. */ +export interface OTCLibraryInfo { + /** Language of OpenTelemetry Library. */ + language?: LibraryInfoLanguage; + + /** Version of collector exporter of Library. */ + exporterVersion?: string; + + /** Version of OpenTelemetry Library. */ + coreLibraryVersion?: string; +} + +/** + * A description of a binary module. + */ +export interface OTCModule { + /** + * TODO: document the meaning of this field. For example: main binary, kernel + * modules, and dynamic libraries such as libc.so, sharedlib.so. + */ + module?: OTCTruncatableString; + /** + * A unique identifier for the module, usually a hash of its contents. + */ + buildId?: OTCTruncatableString; +} + +/** + * 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 OTCNode { + /** Identifier that uniquely identifies a process within a VM/container. */ + identifier?: OTCProcessIdentifier; + + /** Information on the OpenTelemetry Library that initiates the stream. */ + libraryInfo?: OTCLibraryInfo; + + /** Additional information on service. */ + serviceInfo?: OTCServiceInfo; + + /** Additional attributes. */ + attributes?: { [key: string]: string }; +} + +/** + * Identifier that uniquely identifies a process within a VM/container. + * For OpenTelemetry Web, this identifies the domain name of the site. + */ +export interface OTCProcessIdentifier { + /** + * 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 OTCResource { + /** Type identifier for the resource. */ + type?: string; + + /** Set of labels that describe the resource. */ + labels?: { [key: string]: string }; +} + +/** Additional service information. */ +export interface OTCServiceInfo { + /** 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 OTCSpan { + /** + * 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?: OTCTraceState; + /** + * 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?: OTCTruncatableString; + /** + * 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?: OTCAttributes; + /** + * A stack trace captured at the start of the span. + * Currently not used + */ + stackTrace?: OTCStackTrace; + /** + * The included time events. + */ + timeEvents?: OTCTimeEvents; + /** + * 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 OTCStackFrame { + /** + * The fully-qualified name that uniquely identifies the function or method + * that is active in this frame. + */ + functionName?: OTCTruncatableString; + /** + * An un-mangled function name, if `function_name` is + * [mangled](http://www.avabodh.com/cxxin/namemangling.html). The name can be + * fully qualified. + */ + originalFunctionName?: OTCTruncatableString; + /** + * The name of the source file where the function call appears. + */ + fileName?: OTCTruncatableString; + /** + * 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?: OTCModule; + /** + * The version of the deployed source code. + */ + sourceVersion?: OTCTruncatableString; +} + +/** + * A collection of stack frames, which can be truncated. + */ +export interface OTCStackFrames { + /** + * Stack frames in this call stack. + */ + frame?: OTCStackFrame[]; + /** + * 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 OTCStackTrace { + /** + * Stack frames in this stack trace. + */ + stackFrames?: OTCStackFrames; + /** + * 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 OCSpan. + */ +export interface OTCTimeEvent { + /** + * The time the event occurred. + */ + time?: string; + /** + * A text annotation with a set of attributes. + */ + annotation?: OTCAnnotation; + /** + * 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 OTCTimeEvents { + /** + * A collection of `TimeEvent`s. + */ + timeEvent?: OTCTimeEvent[]; + /** + * 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 OTCTruncatableString { + /** + * 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 OTCTraceState { + [key: string]: string; +} diff --git a/packages/opentelemetry-exporter-collector/src/util.ts b/packages/opentelemetry-exporter-collector/src/util.ts new file mode 100644 index 0000000000..becf884636 --- /dev/null +++ b/packages/opentelemetry-exporter-collector/src/util.ts @@ -0,0 +1,205 @@ +/*! + * 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 { + spanIdToBase64, + hrTimeEndTime, + hrTimeToTimeStamp, +} from '@opentelemetry/core'; +import { ReadableSpan } from '@opentelemetry/tracing'; +import { Attributes, TimedEvent, TraceState } from '@opentelemetry/types'; +import { + OTCAttributeMap, + OTCAnnotation, + OTCAttributes, + OTCAttributeValue, + OTCSpan, + OTCTimeEvent, + OTCTimeEvents, + OTCTruncatableString, + OTCTraceState, +} from './types'; + +const OC_MAX_STRING_LENGTH = 128; +const OC_MAX_ATTRIBUTES = 30; + +/** + * convert string to maximum length of 128, providing information of truncated bytes + * @param name - string to be converted + */ +export function stringToTruncatableString(name: string): OTCTruncatableString { + const value = name.substr(0, OC_MAX_STRING_LENGTH); + const truncatedByteCount = + name.length > OC_MAX_STRING_LENGTH ? name.length - OC_MAX_STRING_LENGTH : 0; + + return { value, truncatedByteCount }; +} + +/** + * convert attributes + * @param attributes + * @param maxAttributes - default value is 30 + */ +export function convertAttributesToOTCAttributes( + attributes: Attributes, + maxAttributes: number = OC_MAX_ATTRIBUTES +): OTCAttributes { + const attributeMap: OTCAttributeMap = {}; + let droppedAttributesCount = 0; + + const keys = Object.keys(attributes || {}); + + const countKeys = Math.min(keys.length, maxAttributes); + if (keys.length > maxAttributes) { + droppedAttributesCount = keys.length - maxAttributes; + } + + for (let i = 0; i <= countKeys - 1; i++) { + const key = keys[i]; + const eventAttributeValue = convertEventValueToOTCValue( + attributes && attributes[key] + ); + if (eventAttributeValue) { + attributeMap[key] = eventAttributeValue; + } + } + + return { + droppedAttributesCount, + attributeMap, + }; +} + +/** + * convert event value + * @param value event value + */ +export function convertEventValueToOTCValue( + value: unknown +): OTCAttributeValue | undefined { + const ocAttributeValue: OTCAttributeValue = {}; + + if (typeof value === 'string') { + ocAttributeValue.stringValue = stringToTruncatableString(value); + } else if (typeof value === 'boolean') { + ocAttributeValue.boolValue = value; + } else if (typeof value === 'number') { + if (Math.floor(value) === value) { + ocAttributeValue.intValue = value; + } else { + ocAttributeValue.doubleValue = value; + } + } + + return ocAttributeValue; +} + +/** + * convert events + * @param events array of events + * @param maxAttributes - maximum number of event attributes to be converted + */ +export function convertEventsToOTCEvents( + events: TimedEvent[], + maxAttributes: number = OC_MAX_ATTRIBUTES +): OTCTimeEvents { + let droppedAnnotationsCount = 0; + let droppedMessageEventsCount = 0; // not counting yet as messageEvent is not implemented + + const timeEvent: OTCTimeEvent[] = events.map((event: TimedEvent) => { + let attributes: OTCAttributes | undefined; + + if (event && event.attributes) { + attributes = convertAttributesToOTCAttributes( + event.attributes, + maxAttributes + ); + droppedAnnotationsCount += attributes.droppedAttributesCount || 0; + } + + let annotation: OTCAnnotation = {}; + if (event.name || attributes) { + annotation = {}; + } + + if (event.name) { + annotation.description = stringToTruncatableString(event.name); + } + + if (typeof attributes !== 'undefined') { + annotation.attributes = attributes; + } + + // const messageEvent: MessageEvent; + + const ocTimeEvent: OTCTimeEvent = { + time: hrTimeToTimeStamp(event.time), + // messageEvent, + }; + + if (annotation) { + ocTimeEvent.annotation = annotation; + } + + return ocTimeEvent; + }); + + return { + timeEvent, + droppedAnnotationsCount, + droppedMessageEventsCount, + }; +} + +/** + * @param span + */ +export function convertSpan(span: ReadableSpan): OTCSpan { + return { + traceId: spanIdToBase64(span.spanContext.traceId), + spanId: spanIdToBase64(span.spanContext.spanId), + parentSpanId: span.parentSpanId + ? spanIdToBase64(span.parentSpanId) + : undefined, + tracestate: convertTraceStateToOTCTraceState(span.spanContext.traceState), + name: stringToTruncatableString(span.name), + kind: span.kind, + startTime: hrTimeToTimeStamp(span.startTime), + endTime: hrTimeToTimeStamp(hrTimeEndTime(span.startTime, span.duration)), + attributes: convertAttributesToOTCAttributes(span.attributes), + // stackTrace: // not implemented + timeEvents: convertEventsToOTCEvents(span.events), + status: span.status, + sameProcessAsParentSpan: !!span.parentSpanId, + // childSpanCount: // not implemented + }; +} + +/** + * @param traceState + */ +function convertTraceStateToOTCTraceState( + traceState?: TraceState +): OTCTraceState { + if (!traceState) return {}; + const entries = traceState.serialize().split(','); + const apiTraceState: OTCTraceState = {}; + for (const entry of entries) { + const [key, value] = entry.split('='); + apiTraceState[key] = value; + } + return apiTraceState; +} 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..7c7a881f96 --- /dev/null +++ b/packages/opentelemetry-exporter-collector/test/browser/CollectorExporter.test.ts @@ -0,0 +1,189 @@ +/*! + * 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 { OTCExportTraceServiceRequest } from '../../src/types'; + +import { ensureSpanIsCorrect, mockedReadableSpan } from '../helper'; +const sendBeacon = navigator.sendBeacon; + +describe('CollectorExporter', () => { + 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', + spanAttributes: {}, + 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 OTCExportTraceServiceRequest; + 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); + 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 OTCExportTraceServiceRequest; + 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); + 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..e1fa48153b --- /dev/null +++ b/packages/opentelemetry-exporter-collector/test/common/CollectorExporter.test.ts @@ -0,0 +1,155 @@ +/*! + * 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 { OTCSpan } from '../../src/types'; +import * as platform from '../../src/platform/index'; + +import { ensureSpanIsCorrect, mockedReadableSpan } from '../helper'; + +describe('CollectorExporter', () => { + 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', + spanAttributes: {}, + 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(collectorExporter, 'sendSpan'); + }); + afterEach(() => { + spySend.restore(); + }); + + it('should export spans as OTCSpans', done => { + const spans: ReadableSpan[] = []; + spans.push(Object.assign({}, mockedReadableSpan)); + + collectorExporter.export(spans, function() {}); + setTimeout(() => { + const span1 = spySend.args[0][0][0] as OTCSpan; + ensureSpanIsCorrect(span1); + done(); + }); + assert.strictEqual(spySend.callCount, 1); + }); + }); + + describe('shutdown', () => { + let spySend: any; + let onShutdownSpy: any; + beforeEach(() => { + spySend = sinon.stub(collectorExporter, 'sendSpan'); + onShutdownSpy = sinon.stub(platform, 'onShutdown'); + collectorExporterConfig = { + hostName: 'foo', + logger: new NoopLogger(), + serviceName: 'bar', + spanAttributes: {}, + url: 'http://foo.bar.com', + }; + collectorExporter = new CollectorExporter(collectorExporterConfig); + }); + afterEach(() => { + spySend.restore(); + 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/util.test.ts b/packages/opentelemetry-exporter-collector/test/common/util.test.ts new file mode 100644 index 0000000000..6b35cd3b1f --- /dev/null +++ b/packages/opentelemetry-exporter-collector/test/common/util.test.ts @@ -0,0 +1,261 @@ +/*! + * 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 util from '../../src/util'; +import { mockedReadableSpan } from '../helper'; + +describe('util', () => { + describe('stringToTruncatableString', () => { + it('should convert string to TruncatableString', () => { + assert.deepStrictEqual(util.stringToTruncatableString('foo'), { + truncatedByteCount: 0, + value: 'foo', + }); + }); + + it('should convert long string to TruncatableString', () => { + let foo = + 'foo1234567890foo1234567890foo1234567890foo1234567890foo1234567890foo1234567890foo1234567890'; + foo += foo; + assert.deepStrictEqual(util.stringToTruncatableString(foo), { + truncatedByteCount: 54, + value: + 'foo1234567890foo1234567890foo1234567890foo1234567890foo1234567890foo1234567890foo1234567890foo1234567890foo1234567890foo12345678', + }); + }); + }); + + describe('convertAttributesToOTCAttributes', () => { + it('should convert attribute string', () => { + const attributes: Attributes = { + foo: 'bar', + }; + assert.deepStrictEqual( + util.convertAttributesToOTCAttributes(attributes), + { + attributeMap: { + foo: { + stringValue: { + truncatedByteCount: 0, + value: 'bar', + }, + }, + }, + droppedAttributesCount: 0, + } + ); + }); + + it('should convert attribute integer', () => { + const attributes: Attributes = { + foo: 13, + }; + assert.deepStrictEqual( + util.convertAttributesToOTCAttributes(attributes), + { + attributeMap: { + foo: { + intValue: 13, + }, + }, + droppedAttributesCount: 0, + } + ); + }); + + it('should convert attribute boolean', () => { + const attributes: Attributes = { + foo: true, + }; + assert.deepStrictEqual( + util.convertAttributesToOTCAttributes(attributes), + { + attributeMap: { + foo: { + boolValue: true, + }, + }, + droppedAttributesCount: 0, + } + ); + }); + + it('should convert attribute double', () => { + const attributes: Attributes = { + foo: 1.34, + }; + assert.deepStrictEqual( + util.convertAttributesToOTCAttributes(attributes), + { + attributeMap: { + foo: { + doubleValue: 1.34, + }, + }, + droppedAttributesCount: 0, + } + ); + }); + + it('should convert only first attribute', () => { + const attributes: Attributes = { + foo: 1, + bar: 1, + }; + assert.deepStrictEqual( + util.convertAttributesToOTCAttributes(attributes, 1), + { + attributeMap: { + foo: { + intValue: 1, + }, + }, + droppedAttributesCount: 1, + } + ); + }); + }); + + describe('convertEventsToOTCEvents', () => { + 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(util.convertEventsToOTCEvents(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('convertSpan', () => { + it('should convert span', () => { + assert.deepStrictEqual(util.convertSpan(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, + }); + }); + }); +}); diff --git a/packages/opentelemetry-exporter-collector/test/helper.ts b/packages/opentelemetry-exporter-collector/test/helper.ts new file mode 100644 index 0000000000..baf21df24e --- /dev/null +++ b/packages/opentelemetry-exporter-collector/test/helper.ts @@ -0,0 +1,86 @@ +/*! + * 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 { ReadableSpan } from '@opentelemetry/tracing'; +import * as assert from 'assert'; +import { OTCSpan, OTCTimeEvent, OTCTimeEvents } from '../src/types'; + +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: [], + 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: OTCSpan) { + const timeEvents: OTCTimeEvents = (span.timeEvents && span.timeEvents) || {}; + const timeEvent: OTCTimeEvent[] = timeEvents.timeEvent || []; + + assert.strictEqual(span.traceId, 'HxAI3I4nDoXECg18OTmyeA=='); + assert.strictEqual(span.spanId, 'XhByYfZPpT4='); + assert.strictEqual(span.parentSpanId, 'eKiRUJiGQ4g='); + assert.deepStrictEqual(span.tracestate, {}); + assert.strictEqual(span.name && span.name.value, 'documentFetch'); + assert.strictEqual(span.name && span.name.truncatedByteCount, 0); + assert.strictEqual(span.kind, 0); + assert.strictEqual(span.startTime, '2019-11-18T23:36:05.429803070Z'); + assert.strictEqual(span.endTime, '2019-11-18T23:36:05.438688070Z'); + assert.strictEqual(timeEvents.droppedAnnotationsCount, 0); + assert.strictEqual(timeEvents.droppedMessageEventsCount, 0); + assert.deepStrictEqual(span.status, { code: 0 }); + assert.strictEqual(span.sameProcessAsParentSpan, true); + + assert.strictEqual(timeEvent.length, 8); + const timeEvent1 = timeEvent[0]; + assert.deepStrictEqual(timeEvent1, { + time: '2019-11-18T23:36:05.429803070Z', + annotation: { + description: { value: 'fetchStart', truncatedByteCount: 0 }, + }, + }); +} 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..a5fc04b24d --- /dev/null +++ b/packages/opentelemetry-exporter-collector/test/node/CollectorExporter.test.ts @@ -0,0 +1,136 @@ +/*! + * 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 http from 'http'; +import * as assert from 'assert'; +import * as sinon from 'sinon'; +import { + CollectorExporter, + CollectorExporterConfig, +} from '../../src/CollectorExporter'; +import { OTCExportTraceServiceRequest } from '../../src/types'; + +import { ensureSpanIsCorrect, mockedReadableSpan } from '../helper'; + +const fakeRequest = { + end: function() {}, + on: function() {}, + write: function() {}, +}; + +const mockRes = { + statusCode: 200, +}; + +const mockResError = { + statusCode: 400, +}; + +describe('CollectorExporter', () => { + 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 NoopLogger(), + serviceName: 'bar', + spanAttributes: {}, + 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, '/'); + assert.deepStrictEqual(options.headers, { 'Content-Length': 1652 }); + 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 OTCExportTraceServiceRequest; + const span1 = json.spans && json.spans[0]; + assert.ok(typeof span1 !== 'undefined', "span doesn't exist"); + if (span1) { + ensureSpanIsCorrect(span1); + } + + 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 args = spyRequest.args[0]; + const callback = args[1]; + callback(mockRes); + + const response: any = spyLoggerDebug.args[1][0]; + assert.strictEqual(response, 'statusCode: 200'); + assert.strictEqual(spyLoggerError.args.length, 0); + done(); + }); + }); + + it('should log the error message', done => { + const spyLoggerError = sinon.stub(collectorExporter.logger, 'error'); + + collectorExporter.export(spans, function() {}); + + setTimeout(() => { + // const response: any = spyLoggerDebug.args[0][0]; + const args = spyRequest.args[0]; + const callback = args[1]; + callback(mockResError); + + const response: any = spyLoggerError.args[0][0]; + assert.strictEqual(response, 'statusCode: 400'); + 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"] +} From 48818fbf1f03889b5e130907fc0dd114e938f211 Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Wed, 20 Nov 2019 19:27:15 +0100 Subject: [PATCH 03/25] chore: updating readme --- .../README.md | 60 ++++++++++++++++--- 1 file changed, 51 insertions(+), 9 deletions(-) diff --git a/packages/opentelemetry-exporter-collector/README.md b/packages/opentelemetry-exporter-collector/README.md index 09fc2c1657..50f0e51767 100644 --- a/packages/opentelemetry-exporter-collector/README.md +++ b/packages/opentelemetry-exporter-collector/README.md @@ -1,11 +1,11 @@ -# OpenTelemetry Exporter Web Collector +# 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 to be used with [opentelemetry-collector][opentelemetry-collector-url]. +This module provides exporter for web and node to be used with [opentelemetry-collector][opentelemetry-collector-url]. ## Installation @@ -13,6 +13,48 @@ This module provides exporter for web to be used with [opentelemetry-collector][ 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: @@ -26,10 +68,10 @@ Apache 2.0 - See [LICENSE][license-url] for more information. [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-web-collector -[dependencies-url]: https://david-dm.org/open-telemetry/opentelemetry-js?path=packages%2Fopentelemetry-exporter-web-collector -[devDependencies-image]: https://david-dm.org/open-telemetry/opentelemetry-js/dev-status.svg?path=packages/opentelemetry-exporter-web-collector -[devDependencies-url]: https://david-dm.org/open-telemetry/opentelemetry-js?path=packages%2Fopentelemetry-exporter-web-collector&type=dev -[npm-url]: https://www.npmjs.com/package/@opentelemetry/exporter-web-collector -[npm-img]: https://badge.fury.io/js/%40opentelemetry%exporter-web-collector.svg -[opentelemetry-collector-url]: https://github.com/open-telemetry/opentelemetry-collector +[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-exporter-collector From 1cbf4c6fa00703ec09ca8d71ad38d9331385a6f7 Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Wed, 20 Nov 2019 19:37:13 +0100 Subject: [PATCH 04/25] chore: undo auto lint fix - which is wrong --- .../src/prometheus.ts | 12 ++++++++---- packages/opentelemetry-plugin-grpc/src/grpc.ts | 9 ++++++--- .../opentelemetry-plugin-pg/src/pg.ts | 7 ++++--- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/packages/opentelemetry-exporter-prometheus/src/prometheus.ts b/packages/opentelemetry-exporter-prometheus/src/prometheus.ts index 4af6471295..69f4545909 100644 --- a/packages/opentelemetry-exporter-prometheus/src/prometheus.ts +++ b/packages/opentelemetry-exporter-prometheus/src/prometheus.ts @@ -131,15 +131,19 @@ export class PrometheusExporter implements MetricExporter { // ReadableMetric value is the current state, not the delta to be incremented by. // Currently, _registerMetric creates a new counter every time the value changes, // so the increment here behaves as a set value (increment from 0) - metric.inc(this._getLabelValues(labelKeys, ts.labelValues), ts.points[0] - .value as number); + metric.inc( + this._getLabelValues(labelKeys, ts.labelValues), + ts.points[0].value as number + ); } } if (metric instanceof Gauge) { for (const ts of readableMetric.timeseries) { - metric.set(this._getLabelValues(labelKeys, ts.labelValues), ts.points[0] - .value as number); + metric.set( + this._getLabelValues(labelKeys, ts.labelValues), + ts.points[0].value as number + ); } } diff --git a/packages/opentelemetry-plugin-grpc/src/grpc.ts b/packages/opentelemetry-plugin-grpc/src/grpc.ts index 531bd80bbf..9c44c65abd 100644 --- a/packages/opentelemetry-plugin-grpc/src/grpc.ts +++ b/packages/opentelemetry-plugin-grpc/src/grpc.ts @@ -354,9 +354,12 @@ export class GrpcPlugin extends BasePlugin { parent: currentSpan || undefined, }) .setAttribute(AttributeNames.COMPONENT, GrpcPlugin.component); - return plugin._makeGrpcClientRemoteCall(original, args, this, plugin)( - span - ); + return plugin._makeGrpcClientRemoteCall( + original, + args, + this, + plugin + )(span); }; }; } diff --git a/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg/src/pg.ts b/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg/src/pg.ts index e41ceee08a..a6841ae33a 100644 --- a/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg/src/pg.ts +++ b/packages/opentelemetry-plugin-postgres/opentelemetry-plugin-pg/src/pg.ts @@ -97,9 +97,10 @@ export class PostgresPlugin extends BasePlugin { const parentSpan = plugin._tracer.getCurrentSpan(); if (typeof args[args.length - 1] === 'function') { // Patch ParameterQuery callback - args[args.length - 1] = utils.patchCallback(span, args[ - args.length - 1 - ] as PostgresCallback); + args[args.length - 1] = utils.patchCallback( + span, + args[args.length - 1] as PostgresCallback + ); // If a parent span exists, bind the callback if (parentSpan) { args[args.length - 1] = plugin._tracer.bind( From 952f99d2902ce68412fda172f6ddc9c2d8d1848e Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Thu, 21 Nov 2019 01:07:17 +0100 Subject: [PATCH 05/25] chore: updates after comments --- examples/basic-tracer-node/README.md | 15 ++++++++++----- .../docker/oc/collector-config.yaml | 7 ------- .../docker/oc/docker-compose.yaml | 19 ------------------- examples/basic-tracer-node/package.json | 5 ++--- .../opentelemetry-core/src/common/time.ts | 18 ------------------ ...{span-id-to-base64.ts => hex-to-base64.ts} | 2 +- .../src/platform/browser/index.ts | 2 +- ...{span-id-to-base64.ts => hex-to-base64.ts} | 2 +- .../src/platform/node/index.ts | 2 +- .../test/common/time.test.ts | 19 ------------------- ...o-base64.test.ts => hex-to-base64.test.ts} | 10 +++++----- .../README.md | 2 +- .../src/platform/browser/sendSpans.ts | 3 ++- .../src/platform/node/sendSpans.ts | 7 ++++--- .../src/util.ts | 15 +++++++-------- 15 files changed, 35 insertions(+), 93 deletions(-) delete mode 100644 examples/basic-tracer-node/docker/oc/collector-config.yaml delete mode 100644 examples/basic-tracer-node/docker/oc/docker-compose.yaml rename packages/opentelemetry-core/src/platform/browser/{span-id-to-base64.ts => hex-to-base64.ts} (93%) rename packages/opentelemetry-core/src/platform/node/{span-id-to-base64.ts => hex-to-base64.ts} (94%) rename packages/opentelemetry-core/test/platform/{span-id-to-base64.test.ts => hex-to-base64.test.ts} (73%) diff --git a/examples/basic-tracer-node/README.md b/examples/basic-tracer-node/README.md index 510f68bb3b..ce75a56078 100644 --- a/examples/basic-tracer-node/README.md +++ b/examples/basic-tracer-node/README.md @@ -61,20 +61,22 @@ Click on the trace to view its details.

### Collector Exporter -Make sure you have [docker](https://docs.docker.com/) installed +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 - $ # or alternatively open census - $ npm run collector:docker:oc - $ # at any time you can stop & clean them + $ # at any time you can stop it $ npm run collector:docker:stop ``` #### Collector Exporter - Zipkin UI -Please follow the section [Zipkin UI](#zipkin-ui) only +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 @@ -94,3 +96,6 @@ Please follow the section [Zipkin UI](#zipkin-ui) only ## LICENSE Apache License 2.0 + + +[opentelemetry-collector-url]: https://github.com/open-telemetry/opentelemetry-exporter-collector diff --git a/examples/basic-tracer-node/docker/oc/collector-config.yaml b/examples/basic-tracer-node/docker/oc/collector-config.yaml deleted file mode 100644 index 542ed48b85..0000000000 --- a/examples/basic-tracer-node/docker/oc/collector-config.yaml +++ /dev/null @@ -1,7 +0,0 @@ -receivers: - opencensus: - address: "127.0.0.1:55678" - -exporters: - zipkin: - endpoint: "http://zipkin-all-in-one:9411/api/v2/spans" diff --git a/examples/basic-tracer-node/docker/oc/docker-compose.yaml b/examples/basic-tracer-node/docker/oc/docker-compose.yaml deleted file mode 100644 index fe584fbfe7..0000000000 --- a/examples/basic-tracer-node/docker/oc/docker-compose.yaml +++ /dev/null @@ -1,19 +0,0 @@ -version: "2" -services: - - # Collector - collector: - image: omnition/opencensus-collector - command: ["--config=/conf/collector-config.yaml", "--http-pprof-port=1777", "--receive-zipkin", "--receive-oc-trace", "--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/package.json b/examples/basic-tracer-node/package.json index 1443d273ac..562fd44ff2 100644 --- a/examples/basic-tracer-node/package.json +++ b/examples/basic-tracer-node/package.json @@ -8,9 +8,8 @@ "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:oc": "cd ./dockers/ot && docker-compose down && cd ../oc && docker-compose down && docker-compose up", - "collector:docker:ot": "cd ./dockers/oc && docker-compose down && cd ../ot && docker-compose down && docker-compose up", - "collector:docker:stop": "cd ./dockers/oc && docker-compose down && cd ../ot && docker-compose down", + "collector:docker:ot": "cd ./dockers/ot && docker-compose down && docker-compose up", + "collector:docker:stop": "cd ./dockers/ot && docker-compose down", "multi_exporter": "node ./multi_exporter.js" }, "repository": { diff --git a/packages/opentelemetry-core/src/common/time.ts b/packages/opentelemetry-core/src/common/time.ts index 283ebaa430..41dfa40b13 100644 --- a/packages/opentelemetry-core/src/common/time.ts +++ b/packages/opentelemetry-core/src/common/time.ts @@ -99,24 +99,6 @@ export function hrTimeDuration( return [seconds, nanos]; } -// Returns end time based on startTime and duration -export function hrTimeEndTime( - startTime: types.HrTime, - duration: types.HrTime -): types.HrTime { - let seconds = startTime[0] + duration[0]; - let nanos = startTime[1] + duration[1]; - - // overflow - if (nanos < 0) { - seconds -= 1; - // negate - nanos += SECOND_TO_NANOSECONDS; - } - - return [seconds, nanos]; -} - // Convert hrTime to timestamp. export function hrTimeToTimeStamp(hrTime: types.HrTime): string { const precision = NANOSECOND_DIGITS; diff --git a/packages/opentelemetry-core/src/platform/browser/span-id-to-base64.ts b/packages/opentelemetry-core/src/platform/browser/hex-to-base64.ts similarity index 93% rename from packages/opentelemetry-core/src/platform/browser/span-id-to-base64.ts rename to packages/opentelemetry-core/src/platform/browser/hex-to-base64.ts index 8fd65040d0..733d746826 100644 --- a/packages/opentelemetry-core/src/platform/browser/span-id-to-base64.ts +++ b/packages/opentelemetry-core/src/platform/browser/hex-to-base64.ts @@ -18,7 +18,7 @@ * converts id string into base64 * @param hexStr - id of span */ -export function spanIdToBase64(hexStr: string = ''): string { +export function hexToBase64(hexStr: string = ''): string { const hexStrLen = hexStr.length; let hexAsciiCharsStr = ''; for (let i = 0; i < hexStrLen; i += 2) { diff --git a/packages/opentelemetry-core/src/platform/browser/index.ts b/packages/opentelemetry-core/src/platform/browser/index.ts index e95d5013fe..2e954c6d39 100644 --- a/packages/opentelemetry-core/src/platform/browser/index.ts +++ b/packages/opentelemetry-core/src/platform/browser/index.ts @@ -17,4 +17,4 @@ export * from './id'; export * from './performance'; export * from './timer-util'; -export * from './span-id-to-base64'; +export * from './hex-to-ascii-base64'; diff --git a/packages/opentelemetry-core/src/platform/node/span-id-to-base64.ts b/packages/opentelemetry-core/src/platform/node/hex-to-base64.ts similarity index 94% rename from packages/opentelemetry-core/src/platform/node/span-id-to-base64.ts rename to packages/opentelemetry-core/src/platform/node/hex-to-base64.ts index 4158708530..50c3f4e748 100644 --- a/packages/opentelemetry-core/src/platform/node/span-id-to-base64.ts +++ b/packages/opentelemetry-core/src/platform/node/hex-to-base64.ts @@ -18,7 +18,7 @@ * converts id string into base64 * @param hexStr - id of span */ -export function spanIdToBase64(hexStr: string = ''): string { +export function hexToBase64(hexStr: string = ''): string { const hexStrLen = hexStr.length; let hexAsciiCharsStr = ''; for (let i = 0; i < hexStrLen; i += 2) { diff --git a/packages/opentelemetry-core/src/platform/node/index.ts b/packages/opentelemetry-core/src/platform/node/index.ts index e95d5013fe..4668bb35aa 100644 --- a/packages/opentelemetry-core/src/platform/node/index.ts +++ b/packages/opentelemetry-core/src/platform/node/index.ts @@ -17,4 +17,4 @@ export * from './id'; export * from './performance'; export * from './timer-util'; -export * from './span-id-to-base64'; +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 e4ce78857b..a65af498fc 100644 --- a/packages/opentelemetry-core/test/common/time.test.ts +++ b/packages/opentelemetry-core/test/common/time.test.ts @@ -27,7 +27,6 @@ import { hrTimeToMicroseconds, hrTimeToTimeStamp, isTimeInput, - hrTimeEndTime, } from '../../src/common/time'; describe('time', () => { @@ -158,24 +157,6 @@ describe('time', () => { }); }); - describe('#hrTimeEndTime', () => { - it('should return endTime', () => { - const startTime: types.HrTime = [22, 400000000]; - const durationTime: types.HrTime = [32, 800000000]; - - const output = hrTimeEndTime(startTime, durationTime); - assert.deepStrictEqual(output, [54, 1200000000]); - }); - - it('should handle nanosecond overflow', () => { - const startTime: types.HrTime = [22, 400000000]; - const durationTime: types.HrTime = [32, 200000000]; - - const output = hrTimeEndTime(startTime, durationTime); - assert.deepStrictEqual(output, [54, 600000000]); - }); - }); - describe('#hrTimeToTimeStamp', () => { it('should return timestamp', () => { const time: types.HrTime = [1573513121, 123456]; diff --git a/packages/opentelemetry-core/test/platform/span-id-to-base64.test.ts b/packages/opentelemetry-core/test/platform/hex-to-base64.test.ts similarity index 73% rename from packages/opentelemetry-core/test/platform/span-id-to-base64.test.ts rename to packages/opentelemetry-core/test/platform/hex-to-base64.test.ts index 3ec10db223..1165ef7b95 100644 --- a/packages/opentelemetry-core/test/platform/span-id-to-base64.test.ts +++ b/packages/opentelemetry-core/test/platform/hex-to-base64.test.ts @@ -15,13 +15,13 @@ */ import * as assert from 'assert'; -import { spanIdToBase64 } from '../../src/platform'; +import { hexToBase64 } from '../../src/platform'; -describe('idToBase64', () => { - it('returns convert id to base64', () => { +describe('hexToBase64', () => { + it('convert hex to base64', () => { const id1 = '7deb739e02e44ef2'; const id2 = '46cef837b919a16ff26e608c8cf42c80'; - assert.strictEqual(spanIdToBase64(id1), 'fetzngLkTvI='); - assert.strictEqual(spanIdToBase64(id2), 'Rs74N7kZoW/ybmCMjPQsgA=='); + assert.strictEqual(hexToBase64(id1), 'fetzngLkTvI='); + assert.strictEqual(hexToBase64(id2), 'Rs74N7kZoW/ybmCMjPQsgA=='); }); }); diff --git a/packages/opentelemetry-exporter-collector/README.md b/packages/opentelemetry-exporter-collector/README.md index 50f0e51767..25574ea689 100644 --- a/packages/opentelemetry-exporter-collector/README.md +++ b/packages/opentelemetry-exporter-collector/README.md @@ -74,4 +74,4 @@ Apache 2.0 - See [LICENSE][license-url] for more information. [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-exporter-collector +[opentelemetry-collector-url]: https://github.com/open-telemetry/opentelemetry-collector diff --git a/packages/opentelemetry-exporter-collector/src/platform/browser/sendSpans.ts b/packages/opentelemetry-exporter-collector/src/platform/browser/sendSpans.ts index c4405ebbd2..9af8a36f9f 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/browser/sendSpans.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/browser/sendSpans.ts @@ -40,7 +40,8 @@ export function onShutdown(shutdownF: EventListener) { } /** - * + * 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 diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts b/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts index 26b074a3c7..af0e38315e 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts @@ -30,20 +30,21 @@ const url = require('url'); /** * function that is called once when {@link ExporterCollector} is initialised - * in node version thsi is not used + * 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 thsi is not used + * 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 diff --git a/packages/opentelemetry-exporter-collector/src/util.ts b/packages/opentelemetry-exporter-collector/src/util.ts index becf884636..f4947b3356 100644 --- a/packages/opentelemetry-exporter-collector/src/util.ts +++ b/packages/opentelemetry-exporter-collector/src/util.ts @@ -16,7 +16,6 @@ import { spanIdToBase64, - hrTimeEndTime, hrTimeToTimeStamp, } from '@opentelemetry/core'; import { ReadableSpan } from '@opentelemetry/tracing'; @@ -33,17 +32,17 @@ import { OTCTraceState, } from './types'; -const OC_MAX_STRING_LENGTH = 128; -const OC_MAX_ATTRIBUTES = 30; +const OT_MAX_STRING_LENGTH = 128; +const OT_MAX_ATTRIBUTES = 30; /** * convert string to maximum length of 128, providing information of truncated bytes * @param name - string to be converted */ export function stringToTruncatableString(name: string): OTCTruncatableString { - const value = name.substr(0, OC_MAX_STRING_LENGTH); + const value = name.substr(0, OT_MAX_STRING_LENGTH); const truncatedByteCount = - name.length > OC_MAX_STRING_LENGTH ? name.length - OC_MAX_STRING_LENGTH : 0; + name.length > OT_MAX_STRING_LENGTH ? name.length - OT_MAX_STRING_LENGTH : 0; return { value, truncatedByteCount }; } @@ -55,7 +54,7 @@ export function stringToTruncatableString(name: string): OTCTruncatableString { */ export function convertAttributesToOTCAttributes( attributes: Attributes, - maxAttributes: number = OC_MAX_ATTRIBUTES + maxAttributes: number = OT_MAX_ATTRIBUTES ): OTCAttributes { const attributeMap: OTCAttributeMap = {}; let droppedAttributesCount = 0; @@ -114,7 +113,7 @@ export function convertEventValueToOTCValue( */ export function convertEventsToOTCEvents( events: TimedEvent[], - maxAttributes: number = OC_MAX_ATTRIBUTES + maxAttributes: number = OT_MAX_ATTRIBUTES ): OTCTimeEvents { let droppedAnnotationsCount = 0; let droppedMessageEventsCount = 0; // not counting yet as messageEvent is not implemented @@ -178,7 +177,7 @@ export function convertSpan(span: ReadableSpan): OTCSpan { name: stringToTruncatableString(span.name), kind: span.kind, startTime: hrTimeToTimeStamp(span.startTime), - endTime: hrTimeToTimeStamp(hrTimeEndTime(span.startTime, span.duration)), + endTime: hrTimeToTimeStamp(span.endTime), attributes: convertAttributesToOTCAttributes(span.attributes), // stackTrace: // not implemented timeEvents: convertEventsToOTCEvents(span.events), From 1fe92b8ddf9dd077ac02b8abe418b674bd080c5b Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Thu, 21 Nov 2019 01:43:12 +0100 Subject: [PATCH 06/25] chore: renaming util to transform --- .../src/platform/browser/index.ts | 2 +- .../src/CollectorExporter.ts | 6 +- .../src/{util.ts => transform.ts} | 40 +++---- .../{util.test.ts => transform.test.ts} | 107 ++++++++---------- 4 files changed, 69 insertions(+), 86 deletions(-) rename packages/opentelemetry-exporter-collector/src/{util.ts => transform.ts} (82%) rename packages/opentelemetry-exporter-collector/test/common/{util.test.ts => transform.test.ts} (76%) diff --git a/packages/opentelemetry-core/src/platform/browser/index.ts b/packages/opentelemetry-core/src/platform/browser/index.ts index 2e954c6d39..4668bb35aa 100644 --- a/packages/opentelemetry-core/src/platform/browser/index.ts +++ b/packages/opentelemetry-core/src/platform/browser/index.ts @@ -17,4 +17,4 @@ export * from './id'; export * from './performance'; export * from './timer-util'; -export * from './hex-to-ascii-base64'; +export * from './hex-to-base64'; diff --git a/packages/opentelemetry-exporter-collector/src/CollectorExporter.ts b/packages/opentelemetry-exporter-collector/src/CollectorExporter.ts index 6d884eb107..2f36ef5ab8 100644 --- a/packages/opentelemetry-exporter-collector/src/CollectorExporter.ts +++ b/packages/opentelemetry-exporter-collector/src/CollectorExporter.ts @@ -19,7 +19,7 @@ import { NoopLogger } from '@opentelemetry/core'; import { ReadableSpan, SpanExporter } from '@opentelemetry/tracing'; import { Attributes, Logger } from '@opentelemetry/types'; import { OTCSpan } from './types'; -import { convertSpan } from './util'; +import { toCollectorSpan } from './transform'; import { onInit, onShutdown, sendSpans } from './platform/index'; @@ -90,7 +90,9 @@ export class CollectorExporter implements SpanExporter { private _exportSpans(spans: ReadableSpan[]): Promise { return new Promise((resolve, reject) => { try { - const spansToBeSent: OTCSpan[] = spans.map(span => convertSpan(span)); + const spansToBeSent: OTCSpan[] = spans.map(span => + toCollectorSpan(span) + ); this.logger.debug('spans to be sent', spansToBeSent); this.sendSpan(spansToBeSent, resolve, reject); } catch (e) { diff --git a/packages/opentelemetry-exporter-collector/src/util.ts b/packages/opentelemetry-exporter-collector/src/transform.ts similarity index 82% rename from packages/opentelemetry-exporter-collector/src/util.ts rename to packages/opentelemetry-exporter-collector/src/transform.ts index f4947b3356..8014628620 100644 --- a/packages/opentelemetry-exporter-collector/src/util.ts +++ b/packages/opentelemetry-exporter-collector/src/transform.ts @@ -14,10 +14,7 @@ * limitations under the License. */ -import { - spanIdToBase64, - hrTimeToTimeStamp, -} from '@opentelemetry/core'; +import { hexToBase64, hrTimeToTimeStamp } from '@opentelemetry/core'; import { ReadableSpan } from '@opentelemetry/tracing'; import { Attributes, TimedEvent, TraceState } from '@opentelemetry/types'; import { @@ -39,7 +36,9 @@ const OT_MAX_ATTRIBUTES = 30; * convert string to maximum length of 128, providing information of truncated bytes * @param name - string to be converted */ -export function stringToTruncatableString(name: string): OTCTruncatableString { +export function toCollectorTruncatableString( + name: string +): OTCTruncatableString { const value = name.substr(0, OT_MAX_STRING_LENGTH); const truncatedByteCount = name.length > OT_MAX_STRING_LENGTH ? name.length - OT_MAX_STRING_LENGTH : 0; @@ -52,7 +51,7 @@ export function stringToTruncatableString(name: string): OTCTruncatableString { * @param attributes * @param maxAttributes - default value is 30 */ -export function convertAttributesToOTCAttributes( +export function toCollectorAttributes( attributes: Attributes, maxAttributes: number = OT_MAX_ATTRIBUTES ): OTCAttributes { @@ -68,7 +67,7 @@ export function convertAttributesToOTCAttributes( for (let i = 0; i <= countKeys - 1; i++) { const key = keys[i]; - const eventAttributeValue = convertEventValueToOTCValue( + const eventAttributeValue = toCollectorEventValue( attributes && attributes[key] ); if (eventAttributeValue) { @@ -86,13 +85,13 @@ export function convertAttributesToOTCAttributes( * convert event value * @param value event value */ -export function convertEventValueToOTCValue( +export function toCollectorEventValue( value: unknown ): OTCAttributeValue | undefined { const ocAttributeValue: OTCAttributeValue = {}; if (typeof value === 'string') { - ocAttributeValue.stringValue = stringToTruncatableString(value); + ocAttributeValue.stringValue = toCollectorTruncatableString(value); } else if (typeof value === 'boolean') { ocAttributeValue.boolValue = value; } else if (typeof value === 'number') { @@ -111,7 +110,7 @@ export function convertEventValueToOTCValue( * @param events array of events * @param maxAttributes - maximum number of event attributes to be converted */ -export function convertEventsToOTCEvents( +export function toCollectorEvents( events: TimedEvent[], maxAttributes: number = OT_MAX_ATTRIBUTES ): OTCTimeEvents { @@ -122,10 +121,7 @@ export function convertEventsToOTCEvents( let attributes: OTCAttributes | undefined; if (event && event.attributes) { - attributes = convertAttributesToOTCAttributes( - event.attributes, - maxAttributes - ); + attributes = toCollectorAttributes(event.attributes, maxAttributes); droppedAnnotationsCount += attributes.droppedAttributesCount || 0; } @@ -135,7 +131,7 @@ export function convertEventsToOTCEvents( } if (event.name) { - annotation.description = stringToTruncatableString(event.name); + annotation.description = toCollectorTruncatableString(event.name); } if (typeof attributes !== 'undefined') { @@ -166,21 +162,21 @@ export function convertEventsToOTCEvents( /** * @param span */ -export function convertSpan(span: ReadableSpan): OTCSpan { +export function toCollectorSpan(span: ReadableSpan): OTCSpan { return { - traceId: spanIdToBase64(span.spanContext.traceId), - spanId: spanIdToBase64(span.spanContext.spanId), + traceId: hexToBase64(span.spanContext.traceId), + spanId: hexToBase64(span.spanContext.spanId), parentSpanId: span.parentSpanId - ? spanIdToBase64(span.parentSpanId) + ? hexToBase64(span.parentSpanId) : undefined, tracestate: convertTraceStateToOTCTraceState(span.spanContext.traceState), - name: stringToTruncatableString(span.name), + name: toCollectorTruncatableString(span.name), kind: span.kind, startTime: hrTimeToTimeStamp(span.startTime), endTime: hrTimeToTimeStamp(span.endTime), - attributes: convertAttributesToOTCAttributes(span.attributes), + attributes: toCollectorAttributes(span.attributes), // stackTrace: // not implemented - timeEvents: convertEventsToOTCEvents(span.events), + timeEvents: toCollectorEvents(span.events), status: span.status, sameProcessAsParentSpan: !!span.parentSpanId, // childSpanCount: // not implemented diff --git a/packages/opentelemetry-exporter-collector/test/common/util.test.ts b/packages/opentelemetry-exporter-collector/test/common/transform.test.ts similarity index 76% rename from packages/opentelemetry-exporter-collector/test/common/util.test.ts rename to packages/opentelemetry-exporter-collector/test/common/transform.test.ts index 6b35cd3b1f..76d5e5801e 100644 --- a/packages/opentelemetry-exporter-collector/test/common/util.test.ts +++ b/packages/opentelemetry-exporter-collector/test/common/transform.test.ts @@ -16,13 +16,13 @@ import { Attributes, TimedEvent } from '@opentelemetry/types'; import * as assert from 'assert'; -import * as util from '../../src/util'; +import * as transform from '../../src/transform'; import { mockedReadableSpan } from '../helper'; describe('util', () => { - describe('stringToTruncatableString', () => { + describe('toCollectorTruncatableString', () => { it('should convert string to TruncatableString', () => { - assert.deepStrictEqual(util.stringToTruncatableString('foo'), { + assert.deepStrictEqual(transform.toCollectorTruncatableString('foo'), { truncatedByteCount: 0, value: 'foo', }); @@ -32,7 +32,7 @@ describe('util', () => { let foo = 'foo1234567890foo1234567890foo1234567890foo1234567890foo1234567890foo1234567890foo1234567890'; foo += foo; - assert.deepStrictEqual(util.stringToTruncatableString(foo), { + assert.deepStrictEqual(transform.toCollectorTruncatableString(foo), { truncatedByteCount: 54, value: 'foo1234567890foo1234567890foo1234567890foo1234567890foo1234567890foo1234567890foo1234567890foo1234567890foo1234567890foo12345678', @@ -40,76 +40,64 @@ describe('util', () => { }); }); - describe('convertAttributesToOTCAttributes', () => { + describe('toCollectorAttributes', () => { it('should convert attribute string', () => { const attributes: Attributes = { foo: 'bar', }; - assert.deepStrictEqual( - util.convertAttributesToOTCAttributes(attributes), - { - attributeMap: { - foo: { - stringValue: { - truncatedByteCount: 0, - value: 'bar', - }, + assert.deepStrictEqual(transform.toCollectorAttributes(attributes), { + attributeMap: { + foo: { + stringValue: { + truncatedByteCount: 0, + value: 'bar', }, }, - droppedAttributesCount: 0, - } - ); + }, + droppedAttributesCount: 0, + }); }); it('should convert attribute integer', () => { const attributes: Attributes = { foo: 13, }; - assert.deepStrictEqual( - util.convertAttributesToOTCAttributes(attributes), - { - attributeMap: { - foo: { - intValue: 13, - }, + assert.deepStrictEqual(transform.toCollectorAttributes(attributes), { + attributeMap: { + foo: { + intValue: 13, }, - droppedAttributesCount: 0, - } - ); + }, + droppedAttributesCount: 0, + }); }); it('should convert attribute boolean', () => { const attributes: Attributes = { foo: true, }; - assert.deepStrictEqual( - util.convertAttributesToOTCAttributes(attributes), - { - attributeMap: { - foo: { - boolValue: true, - }, + assert.deepStrictEqual(transform.toCollectorAttributes(attributes), { + attributeMap: { + foo: { + boolValue: true, }, - droppedAttributesCount: 0, - } - ); + }, + droppedAttributesCount: 0, + }); }); it('should convert attribute double', () => { const attributes: Attributes = { foo: 1.34, }; - assert.deepStrictEqual( - util.convertAttributesToOTCAttributes(attributes), - { - attributeMap: { - foo: { - doubleValue: 1.34, - }, + assert.deepStrictEqual(transform.toCollectorAttributes(attributes), { + attributeMap: { + foo: { + doubleValue: 1.34, }, - droppedAttributesCount: 0, - } - ); + }, + droppedAttributesCount: 0, + }); }); it('should convert only first attribute', () => { @@ -117,21 +105,18 @@ describe('util', () => { foo: 1, bar: 1, }; - assert.deepStrictEqual( - util.convertAttributesToOTCAttributes(attributes, 1), - { - attributeMap: { - foo: { - intValue: 1, - }, + assert.deepStrictEqual(transform.toCollectorAttributes(attributes, 1), { + attributeMap: { + foo: { + intValue: 1, }, - droppedAttributesCount: 1, - } - ); + }, + droppedAttributesCount: 1, + }); }); }); - describe('convertEventsToOTCEvents', () => { + describe('toCollectorEvents', () => { it('should convert events to otc events', () => { const events: TimedEvent[] = [ { name: 'foo', time: [123, 123], attributes: { a: 'b' } }, @@ -141,7 +126,7 @@ describe('util', () => { attributes: { c: 'd' }, }, ]; - assert.deepStrictEqual(util.convertEventsToOTCEvents(events), { + assert.deepStrictEqual(transform.toCollectorEvents(events), { timeEvent: [ { time: '1970-01-01T00:02:03.000000123Z', @@ -174,9 +159,9 @@ describe('util', () => { }); }); - describe('convertSpan', () => { + describe('toCollectorSpan', () => { it('should convert span', () => { - assert.deepStrictEqual(util.convertSpan(mockedReadableSpan), { + assert.deepStrictEqual(transform.toCollectorSpan(mockedReadableSpan), { traceId: 'HxAI3I4nDoXECg18OTmyeA==', spanId: 'XhByYfZPpT4=', parentSpanId: 'eKiRUJiGQ4g=', From eb1bb4f164b3dae38e864ab4390d1025577ad921 Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Thu, 21 Nov 2019 02:11:17 +0100 Subject: [PATCH 07/25] chore: renaming types, last comments from review --- examples/basic-tracer-node/package.json | 4 +- .../src/CollectorExporter.ts | 7 +- .../src/platform/browser/sendSpans.ts | 14 +-- .../src/platform/node/sendSpans.ts | 15 ++- .../src/transform.ts | 98 +++++++++---------- .../src/types.ts | 92 ++++++++--------- .../test/browser/CollectorExporter.test.ts | 10 +- .../test/common/CollectorExporter.test.ts | 6 +- .../test/helper.ts | 9 +- .../test/node/CollectorExporter.test.ts | 6 +- 10 files changed, 126 insertions(+), 135 deletions(-) diff --git a/examples/basic-tracer-node/package.json b/examples/basic-tracer-node/package.json index 562fd44ff2..05063bd87a 100644 --- a/examples/basic-tracer-node/package.json +++ b/examples/basic-tracer-node/package.json @@ -8,8 +8,8 @@ "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 ./dockers/ot && docker-compose down && docker-compose up", - "collector:docker:stop": "cd ./dockers/ot && docker-compose down", + "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": { diff --git a/packages/opentelemetry-exporter-collector/src/CollectorExporter.ts b/packages/opentelemetry-exporter-collector/src/CollectorExporter.ts index 2f36ef5ab8..e217a29ecb 100644 --- a/packages/opentelemetry-exporter-collector/src/CollectorExporter.ts +++ b/packages/opentelemetry-exporter-collector/src/CollectorExporter.ts @@ -18,7 +18,8 @@ import { ExportResult } from '@opentelemetry/base'; import { NoopLogger } from '@opentelemetry/core'; import { ReadableSpan, SpanExporter } from '@opentelemetry/tracing'; import { Attributes, Logger } from '@opentelemetry/types'; -import { OTCSpan } from './types'; + +import * as collectorTypes from './types'; import { toCollectorSpan } from './transform'; import { onInit, onShutdown, sendSpans } from './platform/index'; @@ -90,7 +91,7 @@ export class CollectorExporter implements SpanExporter { private _exportSpans(spans: ReadableSpan[]): Promise { return new Promise((resolve, reject) => { try { - const spansToBeSent: OTCSpan[] = spans.map(span => + const spansToBeSent: collectorTypes.Span[] = spans.map(span => toCollectorSpan(span) ); this.logger.debug('spans to be sent', spansToBeSent); @@ -109,7 +110,7 @@ export class CollectorExporter implements SpanExporter { * @param onError */ sendSpan( - spans: OTCSpan[], + spans: collectorTypes.Span[], onSuccess: () => void, onError: (status?: number) => void ) { diff --git a/packages/opentelemetry-exporter-collector/src/platform/browser/sendSpans.ts b/packages/opentelemetry-exporter-collector/src/platform/browser/sendSpans.ts index 9af8a36f9f..869d569f99 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/browser/sendSpans.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/browser/sendSpans.ts @@ -17,11 +17,7 @@ import { hrTime, hrTimeToTimeStamp } from '@opentelemetry/core'; import { Logger } from '@opentelemetry/types'; import { CollectorExporter } from '../../CollectorExporter'; -import { - LibraryInfoLanguage, - OTCExportTraceServiceRequest, - OTCSpan, -} from '../../types'; +import * as collectorTypes from '../../types'; /** * function that is called once when {@link ExporterCollector} is initialised @@ -48,19 +44,19 @@ export function onShutdown(shutdownF: EventListener) { * @param collectorExporter */ export function sendSpans( - spans: OTCSpan[], + spans: collectorTypes.Span[], onSuccess: () => void, onError: (status?: number) => void, collectorExporter: CollectorExporter ) { - const ocExportTraceServiceRequest: OTCExportTraceServiceRequest = { + const exportTraceServiceRequest: collectorTypes.ExportTraceServiceRequest = { node: { identifier: { hostName: collectorExporter.hostName || window.location.host, startTimestamp: hrTimeToTimeStamp(hrTime()), }, libraryInfo: { - language: LibraryInfoLanguage.WEB_JS, + language: collectorTypes.LibraryInfoLanguage.WEB_JS, // coreLibraryVersion: , not implemented // exporterVersion: , not implemented // coreLibraryVersion: , not implemented @@ -74,7 +70,7 @@ export function sendSpans( spans, }; - const body = JSON.stringify(ocExportTraceServiceRequest); + const body = JSON.stringify(exportTraceServiceRequest); if (typeof navigator.sendBeacon === 'function') { sendSpansWithBeacon( diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts b/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts index af0e38315e..79880a21f7 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts @@ -20,11 +20,8 @@ const https = require('https'); import { IncomingMessage } from 'http'; import { hrTime, hrTimeToTimeStamp } from '@opentelemetry/core'; import { CollectorExporter } from '../../CollectorExporter'; -import { - LibraryInfoLanguage, - OTCExportTraceServiceRequest, - OTCSpan, -} from '../../types'; + +import * as collectorTypes from '../../types'; const url = require('url'); @@ -51,19 +48,19 @@ export function onShutdown(shutdownF: Function) {} * @param collectorExporter */ export function sendSpans( - spans: OTCSpan[], + spans: collectorTypes.Span[], onSuccess: () => void, onError: (status?: number) => void, collectorExporter: CollectorExporter ) { - const ocExportTraceServiceRequest: OTCExportTraceServiceRequest = { + const exportTraceServiceRequest: collectorTypes.ExportTraceServiceRequest = { node: { identifier: { hostName: collectorExporter.hostName, startTimestamp: hrTimeToTimeStamp(hrTime()), }, libraryInfo: { - language: LibraryInfoLanguage.NODE_JS, + language: collectorTypes.LibraryInfoLanguage.NODE_JS, // coreLibraryVersion: , not implemented // exporterVersion: , not implemented // coreLibraryVersion: , not implemented @@ -76,7 +73,7 @@ export function sendSpans( // resource: '', not implemented spans, }; - const body = JSON.stringify(ocExportTraceServiceRequest); + const body = JSON.stringify(exportTraceServiceRequest); const parsedUrl = url.parse(collectorExporter.url); const options = { diff --git a/packages/opentelemetry-exporter-collector/src/transform.ts b/packages/opentelemetry-exporter-collector/src/transform.ts index 8014628620..a96e5e9a02 100644 --- a/packages/opentelemetry-exporter-collector/src/transform.ts +++ b/packages/opentelemetry-exporter-collector/src/transform.ts @@ -17,17 +17,7 @@ import { hexToBase64, hrTimeToTimeStamp } from '@opentelemetry/core'; import { ReadableSpan } from '@opentelemetry/tracing'; import { Attributes, TimedEvent, TraceState } from '@opentelemetry/types'; -import { - OTCAttributeMap, - OTCAnnotation, - OTCAttributes, - OTCAttributeValue, - OTCSpan, - OTCTimeEvent, - OTCTimeEvents, - OTCTruncatableString, - OTCTraceState, -} from './types'; +import * as collectorTypes from './types'; const OT_MAX_STRING_LENGTH = 128; const OT_MAX_ATTRIBUTES = 30; @@ -38,7 +28,7 @@ const OT_MAX_ATTRIBUTES = 30; */ export function toCollectorTruncatableString( name: string -): OTCTruncatableString { +): 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; @@ -54,8 +44,8 @@ export function toCollectorTruncatableString( export function toCollectorAttributes( attributes: Attributes, maxAttributes: number = OT_MAX_ATTRIBUTES -): OTCAttributes { - const attributeMap: OTCAttributeMap = {}; +): collectorTypes.Attributes { + const attributeMap: collectorTypes.AttributeMap = {}; let droppedAttributesCount = 0; const keys = Object.keys(attributes || {}); @@ -87,22 +77,22 @@ export function toCollectorAttributes( */ export function toCollectorEventValue( value: unknown -): OTCAttributeValue | undefined { - const ocAttributeValue: OTCAttributeValue = {}; +): collectorTypes.AttributeValue | undefined { + const attributeValue: collectorTypes.AttributeValue = {}; if (typeof value === 'string') { - ocAttributeValue.stringValue = toCollectorTruncatableString(value); + attributeValue.stringValue = toCollectorTruncatableString(value); } else if (typeof value === 'boolean') { - ocAttributeValue.boolValue = value; + attributeValue.boolValue = value; } else if (typeof value === 'number') { if (Math.floor(value) === value) { - ocAttributeValue.intValue = value; + attributeValue.intValue = value; } else { - ocAttributeValue.doubleValue = value; + attributeValue.doubleValue = value; } } - return ocAttributeValue; + return attributeValue; } /** @@ -113,44 +103,46 @@ export function toCollectorEventValue( export function toCollectorEvents( events: TimedEvent[], maxAttributes: number = OT_MAX_ATTRIBUTES -): OTCTimeEvents { +): collectorTypes.TimeEvents { let droppedAnnotationsCount = 0; let droppedMessageEventsCount = 0; // not counting yet as messageEvent is not implemented - const timeEvent: OTCTimeEvent[] = events.map((event: TimedEvent) => { - let attributes: OTCAttributes | undefined; + const timeEvent: collectorTypes.TimeEvent[] = events.map( + (event: TimedEvent) => { + let attributes: collectorTypes.Attributes | undefined; - if (event && event.attributes) { - attributes = toCollectorAttributes(event.attributes, maxAttributes); - droppedAnnotationsCount += attributes.droppedAttributesCount || 0; - } + if (event && event.attributes) { + attributes = toCollectorAttributes(event.attributes, maxAttributes); + droppedAnnotationsCount += attributes.droppedAttributesCount || 0; + } - let annotation: OTCAnnotation = {}; - if (event.name || attributes) { - annotation = {}; - } + let annotation: collectorTypes.Annotation = {}; + if (event.name || attributes) { + annotation = {}; + } - if (event.name) { - annotation.description = toCollectorTruncatableString(event.name); - } + if (event.name) { + annotation.description = toCollectorTruncatableString(event.name); + } - if (typeof attributes !== 'undefined') { - annotation.attributes = attributes; - } + if (typeof attributes !== 'undefined') { + annotation.attributes = attributes; + } - // const messageEvent: MessageEvent; + // const messageEvent: MessageEvent; - const ocTimeEvent: OTCTimeEvent = { - time: hrTimeToTimeStamp(event.time), - // messageEvent, - }; + const timeEvent: collectorTypes.TimeEvent = { + time: hrTimeToTimeStamp(event.time), + // messageEvent, + }; - if (annotation) { - ocTimeEvent.annotation = annotation; - } + if (annotation) { + timeEvent.annotation = annotation; + } - return ocTimeEvent; - }); + return timeEvent; + } + ); return { timeEvent, @@ -162,14 +154,14 @@ export function toCollectorEvents( /** * @param span */ -export function toCollectorSpan(span: ReadableSpan): OTCSpan { +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: convertTraceStateToOTCTraceState(span.spanContext.traceState), + tracestate: toTraceState(span.spanContext.traceState), name: toCollectorTruncatableString(span.name), kind: span.kind, startTime: hrTimeToTimeStamp(span.startTime), @@ -186,12 +178,10 @@ export function toCollectorSpan(span: ReadableSpan): OTCSpan { /** * @param traceState */ -function convertTraceStateToOTCTraceState( - traceState?: TraceState -): OTCTraceState { +function toTraceState(traceState?: TraceState): collectorTypes.TraceState { if (!traceState) return {}; const entries = traceState.serialize().split(','); - const apiTraceState: OTCTraceState = {}; + const apiTraceState: collectorTypes.TraceState = {}; for (const entry of entries) { const [key, value] = entry.split('='); apiTraceState[key] = value; diff --git a/packages/opentelemetry-exporter-collector/src/types.ts b/packages/opentelemetry-exporter-collector/src/types.ts index 7f67303bad..cca1bb321e 100644 --- a/packages/opentelemetry-exporter-collector/src/types.ts +++ b/packages/opentelemetry-exporter-collector/src/types.ts @@ -25,33 +25,33 @@ export enum LibraryInfoLanguage { WEB_JS = 10, } -export interface OTCAttributeMap { - [key: string]: OTCAttributeValue; +export interface AttributeMap { + [key: string]: AttributeValue; } /** * A text annotation with a set of attributes. */ -export interface OTCAnnotation { +export interface Annotation { /** * A user-supplied message describing the event. */ - description?: OTCTruncatableString; + description?: TruncatableString; /** * A set of attributes on the annotation. */ - attributes?: OTCAttributes; + attributes?: Attributes; } /** * A set of attributes, each with a key and a value. */ -export interface OTCAttributes { +export interface Attributes { /** * \"/instance_id\": \"my-instance\" \"/http/user_agent\": \"\" * \"/http/server_latency\": 300 \"abc.com/myattribute\": true */ - attributeMap?: OTCAttributeMap; + 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. @@ -63,11 +63,11 @@ export interface OTCAttributes { /** * The value of an Attribute. */ -export interface OTCAttributeValue { +export interface AttributeValue { /** * A string up to 256 bytes long. */ - stringValue?: OTCTruncatableString; + 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). @@ -86,11 +86,11 @@ export interface OTCAttributeValue { /** * Format for an HTTP/JSON request to a grpc-gateway for a trace span exporter. */ -export interface OTCExportTraceServiceRequest { - node?: OTCNode; +export interface ExportTraceServiceRequest { + node?: Node; /** A list of Spans that belong to the last received Node. */ - spans?: OTCSpan[]; + spans?: Span[]; /** * The resource for the spans in this message that do not have an explicit @@ -98,11 +98,11 @@ export interface OTCExportTraceServiceRequest { * 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?: OTCResource; + resource?: Resource; } /** Information on OpenTelemetry library that produced the spans/metrics. */ -export interface OTCLibraryInfo { +export interface LibraryInfo { /** Language of OpenTelemetry Library. */ language?: LibraryInfoLanguage; @@ -116,16 +116,16 @@ export interface OTCLibraryInfo { /** * A description of a binary module. */ -export interface OTCModule { +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?: OTCTruncatableString; + module?: TruncatableString; /** * A unique identifier for the module, usually a hash of its contents. */ - buildId?: OTCTruncatableString; + buildId?: TruncatableString; } /** @@ -134,15 +134,15 @@ export interface OTCModule { * In the future we plan to extend the identifier proto definition to support * additional information (e.g cloud id, etc.) */ -export interface OTCNode { +export interface Node { /** Identifier that uniquely identifies a process within a VM/container. */ - identifier?: OTCProcessIdentifier; + identifier?: ProcessIdentifier; /** Information on the OpenTelemetry Library that initiates the stream. */ - libraryInfo?: OTCLibraryInfo; + libraryInfo?: LibraryInfo; /** Additional information on service. */ - serviceInfo?: OTCServiceInfo; + serviceInfo?: ServiceInfo; /** Additional attributes. */ attributes?: { [key: string]: string }; @@ -152,7 +152,7 @@ export interface OTCNode { * Identifier that uniquely identifies a process within a VM/container. * For OpenTelemetry Web, this identifies the domain name of the site. */ -export interface OTCProcessIdentifier { +export interface ProcessIdentifier { /** * The host name. Usually refers to the machine/container name. * For example: os.Hostname() in Go, socket.gethostname() in Python. @@ -168,7 +168,7 @@ export interface OTCProcessIdentifier { } /** Resource information. */ -export interface OTCResource { +export interface Resource { /** Type identifier for the resource. */ type?: string; @@ -177,7 +177,7 @@ export interface OTCResource { } /** Additional service information. */ -export interface OTCServiceInfo { +export interface ServiceInfo { /** Name of the service. */ name?: string; } @@ -190,7 +190,7 @@ export interface OTCServiceInfo { * contiguous - there may be gaps or overlaps between spans in a trace. The * next id is 16. */ -export interface OTCSpan { +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. @@ -214,7 +214,7 @@ export interface OTCSpan { * carriage returns, etc. See the https://github.com/w3c/distributed-tracing * for more details about this field. */ - tracestate?: OTCTraceState; + 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. @@ -227,7 +227,7 @@ export interface OTCSpan { * point in an application. This makes it easier to correlate spans in * different traces. This field is required. */ - name?: OTCTruncatableString; + name?: TruncatableString; /** * Distinguishes between spans generated in a particular context. For example, * two spans with the same name may be distinguished using `CLIENT` and @@ -251,16 +251,16 @@ export interface OTCSpan { /** * A set of attributes on the span. */ - attributes?: OTCAttributes; + attributes?: Attributes; /** * A stack trace captured at the start of the span. * Currently not used */ - stackTrace?: OTCStackTrace; + stackTrace?: StackTrace; /** * The included time events. */ - timeEvents?: OTCTimeEvents; + timeEvents?: TimeEvents; /** * An optional final status for this span. */ @@ -287,22 +287,22 @@ export interface OTCSpan { /** * A single stack frame in a stack trace. */ -export interface OTCStackFrame { +export interface StackFrame { /** * The fully-qualified name that uniquely identifies the function or method * that is active in this frame. */ - functionName?: OTCTruncatableString; + 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?: OTCTruncatableString; + originalFunctionName?: TruncatableString; /** * The name of the source file where the function call appears. */ - fileName?: OTCTruncatableString; + fileName?: TruncatableString; /** * The line number in `file_name` where the function call appears. */ @@ -315,21 +315,21 @@ export interface OTCStackFrame { /** * The binary module from where the code was loaded. */ - loadModule?: OTCModule; + loadModule?: Module; /** * The version of the deployed source code. */ - sourceVersion?: OTCTruncatableString; + sourceVersion?: TruncatableString; } /** * A collection of stack frames, which can be truncated. */ -export interface OTCStackFrames { +export interface StackFrames { /** * Stack frames in this call stack. */ - frame?: OTCStackFrame[]; + 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. @@ -340,11 +340,11 @@ export interface OTCStackFrames { /** * The call stack which originated this span. */ -export interface OTCStackTrace { +export interface StackTrace { /** * Stack frames in this stack trace. */ - stackFrames?: OTCStackFrames; + 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 @@ -359,7 +359,7 @@ export interface OTCStackTrace { /** * A time-stamped annotation or message event in the OCSpan. */ -export interface OTCTimeEvent { +export interface TimeEvent { /** * The time the event occurred. */ @@ -367,7 +367,7 @@ export interface OTCTimeEvent { /** * A text annotation with a set of attributes. */ - annotation?: OTCAnnotation; + annotation?: Annotation; /** * An event describing a message sent/received between Spans. */ @@ -379,11 +379,11 @@ export interface OTCTimeEvent { * the span, consisting of either user-supplied key-value pairs, or details of a * message sent/received between Spans. */ -export interface OTCTimeEvents { +export interface TimeEvents { /** * A collection of `TimeEvent`s. */ - timeEvent?: OTCTimeEvent[]; + timeEvent?: TimeEvent[]; /** * The number of dropped annotations in all the included time events. If the * value is 0, then no annotations were dropped. @@ -399,7 +399,7 @@ export interface OTCTimeEvents { /** * A string that might be shortened to a specified length. */ -export interface OTCTruncatableString { +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 @@ -416,6 +416,6 @@ export interface OTCTruncatableString { truncatedByteCount?: number; } -export interface OTCTraceState { +export interface TraceState { [key: string]: string; } diff --git a/packages/opentelemetry-exporter-collector/test/browser/CollectorExporter.test.ts b/packages/opentelemetry-exporter-collector/test/browser/CollectorExporter.test.ts index 7c7a881f96..fbc70cec3b 100644 --- a/packages/opentelemetry-exporter-collector/test/browser/CollectorExporter.test.ts +++ b/packages/opentelemetry-exporter-collector/test/browser/CollectorExporter.test.ts @@ -22,7 +22,7 @@ import { CollectorExporter, CollectorExporterConfig, } from '../../src/CollectorExporter'; -import { OTCExportTraceServiceRequest } from '../../src/types'; +import * as collectorTypes from '../../src/types'; import { ensureSpanIsCorrect, mockedReadableSpan } from '../helper'; const sendBeacon = navigator.sendBeacon; @@ -67,7 +67,9 @@ describe('CollectorExporter', () => { const args = spyBeacon.args[0]; const url = args[0]; const body = args[1]; - const json = JSON.parse(body) as OTCExportTraceServiceRequest; + const json = JSON.parse( + body + ) as collectorTypes.ExportTraceServiceRequest; const span1 = json.spans && json.spans[0]; assert.ok(typeof span1 !== 'undefined', "span doesn't exist"); @@ -137,7 +139,9 @@ describe('CollectorExporter', () => { assert.strictEqual(request.url, 'http://foo.bar.com'); const body = request.requestBody; - const json = JSON.parse(body) as OTCExportTraceServiceRequest; + const json = JSON.parse( + body + ) as collectorTypes.ExportTraceServiceRequest; const span1 = json.spans && json.spans[0]; assert.ok(typeof span1 !== 'undefined', "span doesn't exist"); diff --git a/packages/opentelemetry-exporter-collector/test/common/CollectorExporter.test.ts b/packages/opentelemetry-exporter-collector/test/common/CollectorExporter.test.ts index e1fa48153b..92bcde471b 100644 --- a/packages/opentelemetry-exporter-collector/test/common/CollectorExporter.test.ts +++ b/packages/opentelemetry-exporter-collector/test/common/CollectorExporter.test.ts @@ -22,7 +22,7 @@ import { CollectorExporter, CollectorExporterConfig, } from '../../src/CollectorExporter'; -import { OTCSpan } from '../../src/types'; +import * as collectorTypes from '../../src/types'; import * as platform from '../../src/platform/index'; import { ensureSpanIsCorrect, mockedReadableSpan } from '../helper'; @@ -99,13 +99,13 @@ describe('CollectorExporter', () => { spySend.restore(); }); - it('should export spans as OTCSpans', done => { + 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 OTCSpan; + const span1 = spySend.args[0][0][0] as collectorTypes.Span; ensureSpanIsCorrect(span1); done(); }); diff --git a/packages/opentelemetry-exporter-collector/test/helper.ts b/packages/opentelemetry-exporter-collector/test/helper.ts index baf21df24e..5a7ebf0231 100644 --- a/packages/opentelemetry-exporter-collector/test/helper.ts +++ b/packages/opentelemetry-exporter-collector/test/helper.ts @@ -16,7 +16,7 @@ import { ReadableSpan } from '@opentelemetry/tracing'; import * as assert from 'assert'; -import { OTCSpan, OTCTimeEvent, OTCTimeEvents } from '../src/types'; +import * as collectorTypes from '../src/types'; export const mockedReadableSpan: ReadableSpan = { name: 'documentFetch', @@ -57,9 +57,10 @@ export const mockedReadableSpan: ReadableSpan = { duration: [0, 8885000], }; -export function ensureSpanIsCorrect(span: OTCSpan) { - const timeEvents: OTCTimeEvents = (span.timeEvents && span.timeEvents) || {}; - const timeEvent: OTCTimeEvent[] = timeEvents.timeEvent || []; +export function ensureSpanIsCorrect(span: collectorTypes.Span) { + const timeEvents: collectorTypes.TimeEvents = + (span.timeEvents && span.timeEvents) || {}; + const timeEvent: collectorTypes.TimeEvent[] = timeEvents.timeEvent || []; assert.strictEqual(span.traceId, 'HxAI3I4nDoXECg18OTmyeA=='); assert.strictEqual(span.spanId, 'XhByYfZPpT4='); diff --git a/packages/opentelemetry-exporter-collector/test/node/CollectorExporter.test.ts b/packages/opentelemetry-exporter-collector/test/node/CollectorExporter.test.ts index a5fc04b24d..4685ad01de 100644 --- a/packages/opentelemetry-exporter-collector/test/node/CollectorExporter.test.ts +++ b/packages/opentelemetry-exporter-collector/test/node/CollectorExporter.test.ts @@ -23,7 +23,7 @@ import { CollectorExporter, CollectorExporterConfig, } from '../../src/CollectorExporter'; -import { OTCExportTraceServiceRequest } from '../../src/types'; +import * as collectorTypes from '../../src/types'; import { ensureSpanIsCorrect, mockedReadableSpan } from '../helper'; @@ -87,7 +87,9 @@ describe('CollectorExporter', () => { setTimeout(() => { const writeArgs = spyWrite.args[0]; - const json = JSON.parse(writeArgs[0]) as OTCExportTraceServiceRequest; + 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) { From c09bc5caf62f13ce9eb6471749512cdd25a464f1 Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Thu, 21 Nov 2019 03:36:38 +0100 Subject: [PATCH 08/25] chore: adding missing links --- .../src/transform.ts | 57 +++++++- .../src/types.ts | 71 +++++++++- .../test/common/transform.test.ts | 85 +---------- .../test/helper.ts | 132 ++++++++++++++---- .../test/node/CollectorExporter.test.ts | 2 +- 5 files changed, 231 insertions(+), 116 deletions(-) diff --git a/packages/opentelemetry-exporter-collector/src/transform.ts b/packages/opentelemetry-exporter-collector/src/transform.ts index a96e5e9a02..9a52b0ad5e 100644 --- a/packages/opentelemetry-exporter-collector/src/transform.ts +++ b/packages/opentelemetry-exporter-collector/src/transform.ts @@ -16,12 +16,16 @@ import { hexToBase64, hrTimeToTimeStamp } from '@opentelemetry/core'; import { ReadableSpan } from '@opentelemetry/tracing'; -import { Attributes, TimedEvent, TraceState } from '@opentelemetry/types'; +import { Attributes, Link, TimedEvent, TraceState } from '@opentelemetry/types'; import * as collectorTypes from './types'; const OT_MAX_STRING_LENGTH = 128; const OT_MAX_ATTRIBUTES = 30; +const LINK_TYPE_UNSPECIFIED: collectorTypes.LinkTypeUnspecified = 0; +// const LINK_TYPE_CHILD_LINKED_SPAN: collectorTypes.LinkTypeChildLinkedSpan = 1; +const LINK_TYPE_PARENT_LINKED_SPAN: collectorTypes.LinkTypeParentLinkedSpan = 2; + /** * convert string to maximum length of 128, providing information of truncated bytes * @param name - string to be converted @@ -151,6 +155,50 @@ export function toCollectorEvents( }; } +/** + * determines the type of link, only parent link type can be determined now + * @param span + * @param link + */ +export function toCollectorLinkType( + span: ReadableSpan, + link: Link +): collectorTypes.LinkType { + const linkSpanId = link.spanContext.spanId; + const spanParentId = span.parentSpanId; + + if (linkSpanId === spanParentId) { + return LINK_TYPE_PARENT_LINKED_SPAN; + } + + return LINK_TYPE_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 */ @@ -161,7 +209,7 @@ export function toCollectorSpan(span: ReadableSpan): collectorTypes.Span { parentSpanId: span.parentSpanId ? hexToBase64(span.parentSpanId) : undefined, - tracestate: toTraceState(span.spanContext.traceState), + tracestate: toCollectorTraceState(span.spanContext.traceState), name: toCollectorTruncatableString(span.name), kind: span.kind, startTime: hrTimeToTimeStamp(span.startTime), @@ -171,6 +219,7 @@ export function toCollectorSpan(span: ReadableSpan): collectorTypes.Span { timeEvents: toCollectorEvents(span.events), status: span.status, sameProcessAsParentSpan: !!span.parentSpanId, + links: toCollectorLinks(span), // childSpanCount: // not implemented }; } @@ -178,7 +227,9 @@ export function toCollectorSpan(span: ReadableSpan): collectorTypes.Span { /** * @param traceState */ -function toTraceState(traceState?: TraceState): collectorTypes.TraceState { +function toCollectorTraceState( + traceState?: TraceState +): collectorTypes.TraceState { if (!traceState) return {}; const entries = traceState.serialize().split(','); const apiTraceState: collectorTypes.TraceState = {}; diff --git a/packages/opentelemetry-exporter-collector/src/types.ts b/packages/opentelemetry-exporter-collector/src/types.ts index cca1bb321e..5e31c54f6d 100644 --- a/packages/opentelemetry-exporter-collector/src/types.ts +++ b/packages/opentelemetry-exporter-collector/src/types.ts @@ -113,6 +113,67 @@ export interface LibraryInfo { 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 two spans is unknown, or known but other than + * parent-child. + */ +export type LinkTypeUnspecified = 0; +/** The linked span is a child of the current span. */ +export type LinkTypeChildLinkedSpan = 1; +/** The linked span is a parent of the current span. */ +export type LinkTypeParentLinkedSpan = 2; +/** + * The relationship of the current span relative to the linked span: child, + * parent, or unspecified. + */ +export type LinkType = + | LinkTypeUnspecified + | LinkTypeChildLinkedSpan + | LinkTypeParentLinkedSpan; + /** * A description of a binary module. */ @@ -278,10 +339,10 @@ export interface Span { // * active. If set, allows an implementation to detect missing child spans. // */ // childSpanCount?: number; - // /** - // * The included links. - // */ - // links?: Links; + /** + * The included links. + */ + links?: Links; } /** @@ -357,7 +418,7 @@ export interface StackTrace { } /** - * A time-stamped annotation or message event in the OCSpan. + * A time-stamped annotation or message event in the Span. */ export interface TimeEvent { /** diff --git a/packages/opentelemetry-exporter-collector/test/common/transform.test.ts b/packages/opentelemetry-exporter-collector/test/common/transform.test.ts index 76d5e5801e..4b0b258a34 100644 --- a/packages/opentelemetry-exporter-collector/test/common/transform.test.ts +++ b/packages/opentelemetry-exporter-collector/test/common/transform.test.ts @@ -17,9 +17,9 @@ import { Attributes, TimedEvent } from '@opentelemetry/types'; import * as assert from 'assert'; import * as transform from '../../src/transform'; -import { mockedReadableSpan } from '../helper'; +import { ensureSpanIsCorrect, mockedReadableSpan } from '../helper'; -describe('util', () => { +describe('transform', () => { describe('toCollectorTruncatableString', () => { it('should convert string to TruncatableString', () => { assert.deepStrictEqual(transform.toCollectorTruncatableString('foo'), { @@ -161,86 +161,7 @@ describe('util', () => { describe('toCollectorSpan', () => { it('should convert 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, - }); + ensureSpanIsCorrect(transform.toCollectorSpan(mockedReadableSpan)); }); }); }); diff --git a/packages/opentelemetry-exporter-collector/test/helper.ts b/packages/opentelemetry-exporter-collector/test/helper.ts index 5a7ebf0231..febf0deaba 100644 --- a/packages/opentelemetry-exporter-collector/test/helper.ts +++ b/packages/opentelemetry-exporter-collector/test/helper.ts @@ -16,6 +16,7 @@ import { ReadableSpan } from '@opentelemetry/tracing'; import * as assert from 'assert'; +import * as transform from '../src/transform'; import * as collectorTypes from '../src/types'; export const mockedReadableSpan: ReadableSpan = { @@ -31,7 +32,16 @@ export const mockedReadableSpan: ReadableSpan = { endTime: [1574120165, 438688070], status: { code: 0 }, attributes: { component: 'document-load' }, - links: [], + links: [ + { + spanContext: { + traceId: '1f1008dc8e270e85c40a0d7c3939b278', + spanId: '78a8915098864388', + traceFlags: 1, + }, + attributes: { component: 'document-load' }, + }, + ], events: [ { name: 'fetchStart', time: [1574120165, 429803070] }, { @@ -58,30 +68,102 @@ export const mockedReadableSpan: ReadableSpan = { }; export function ensureSpanIsCorrect(span: collectorTypes.Span) { - const timeEvents: collectorTypes.TimeEvents = - (span.timeEvents && span.timeEvents) || {}; - const timeEvent: collectorTypes.TimeEvent[] = timeEvents.timeEvent || []; - - assert.strictEqual(span.traceId, 'HxAI3I4nDoXECg18OTmyeA=='); - assert.strictEqual(span.spanId, 'XhByYfZPpT4='); - assert.strictEqual(span.parentSpanId, 'eKiRUJiGQ4g='); - assert.deepStrictEqual(span.tracestate, {}); - assert.strictEqual(span.name && span.name.value, 'documentFetch'); - assert.strictEqual(span.name && span.name.truncatedByteCount, 0); - assert.strictEqual(span.kind, 0); - assert.strictEqual(span.startTime, '2019-11-18T23:36:05.429803070Z'); - assert.strictEqual(span.endTime, '2019-11-18T23:36:05.438688070Z'); - assert.strictEqual(timeEvents.droppedAnnotationsCount, 0); - assert.strictEqual(timeEvents.droppedMessageEventsCount, 0); - assert.deepStrictEqual(span.status, { code: 0 }); - assert.strictEqual(span.sameProcessAsParentSpan, true); - - assert.strictEqual(timeEvent.length, 8); - const timeEvent1 = timeEvent[0]; - assert.deepStrictEqual(timeEvent1, { - time: '2019-11-18T23:36:05.429803070Z', - annotation: { - description: { value: 'fetchStart', truncatedByteCount: 0 }, + 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 }, + }, + }, + }, + }, + ], }, }); } diff --git a/packages/opentelemetry-exporter-collector/test/node/CollectorExporter.test.ts b/packages/opentelemetry-exporter-collector/test/node/CollectorExporter.test.ts index 4685ad01de..d2344c3baa 100644 --- a/packages/opentelemetry-exporter-collector/test/node/CollectorExporter.test.ts +++ b/packages/opentelemetry-exporter-collector/test/node/CollectorExporter.test.ts @@ -77,7 +77,7 @@ describe('CollectorExporter', () => { assert.strictEqual(options.hostname, 'foo.bar.com'); assert.strictEqual(options.method, 'POST'); assert.strictEqual(options.path, '/'); - assert.deepStrictEqual(options.headers, { 'Content-Length': 1652 }); + assert.deepStrictEqual(options.headers, { 'Content-Length': 1901 }); done(); }); }); From 49206330060a3f07a499d61dc869d31c0f80d31e Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Fri, 22 Nov 2019 17:25:39 +0100 Subject: [PATCH 09/25] chore: fixes after comments --- .../src/platform/browser/hex-to-base64.ts | 2 +- .../src/platform/node/hex-to-base64.ts | 2 +- .../src/CollectorExporter.ts | 31 ++++++------------- .../src/platform/browser/sendSpans.ts | 11 ++++--- .../src/platform/node/sendSpans.ts | 14 ++++++--- .../src/transform.ts | 9 +++++- .../test/common/CollectorExporter.test.ts | 5 +-- .../test/node/CollectorExporter.test.ts | 30 +++++++++++------- 8 files changed, 54 insertions(+), 50 deletions(-) diff --git a/packages/opentelemetry-core/src/platform/browser/hex-to-base64.ts b/packages/opentelemetry-core/src/platform/browser/hex-to-base64.ts index 733d746826..d0441b7772 100644 --- a/packages/opentelemetry-core/src/platform/browser/hex-to-base64.ts +++ b/packages/opentelemetry-core/src/platform/browser/hex-to-base64.ts @@ -18,7 +18,7 @@ * converts id string into base64 * @param hexStr - id of span */ -export function hexToBase64(hexStr: string = ''): string { +export function hexToBase64(hexStr: string): string { const hexStrLen = hexStr.length; let hexAsciiCharsStr = ''; for (let i = 0; i < hexStrLen; i += 2) { diff --git a/packages/opentelemetry-core/src/platform/node/hex-to-base64.ts b/packages/opentelemetry-core/src/platform/node/hex-to-base64.ts index 50c3f4e748..1ed62f9cf9 100644 --- a/packages/opentelemetry-core/src/platform/node/hex-to-base64.ts +++ b/packages/opentelemetry-core/src/platform/node/hex-to-base64.ts @@ -18,7 +18,7 @@ * converts id string into base64 * @param hexStr - id of span */ -export function hexToBase64(hexStr: string = ''): string { +export function hexToBase64(hexStr: string): string { const hexStrLen = hexStr.length; let hexAsciiCharsStr = ''; for (let i = 0; i < hexStrLen; i += 2) { diff --git a/packages/opentelemetry-exporter-collector/src/CollectorExporter.ts b/packages/opentelemetry-exporter-collector/src/CollectorExporter.ts index e217a29ecb..5066d7da96 100644 --- a/packages/opentelemetry-exporter-collector/src/CollectorExporter.ts +++ b/packages/opentelemetry-exporter-collector/src/CollectorExporter.ts @@ -35,8 +35,8 @@ export interface CollectorExporterConfig { url?: string; } -const defaultServiceName = 'collector-exporter'; -const defaultCollectorUrl = 'http://localhost:55678/v1/trace'; +const DEFAULT_SERVICE_NAME = 'collector-exporter'; +const DEFAULT_COLLECTOR_URL = 'http://localhost:55678/v1/trace'; /** * Collector Exporter @@ -52,8 +52,8 @@ export class CollectorExporter implements SpanExporter { * @param config */ constructor(config: CollectorExporterConfig = {}) { - this.serviceName = config.serviceName || defaultServiceName; - this.url = config.url || defaultCollectorUrl; + this.serviceName = config.serviceName || DEFAULT_SERVICE_NAME; + this.url = config.url || DEFAULT_COLLECTOR_URL; if (typeof config.hostName === 'string') { this.hostName = config.hostName; } @@ -95,7 +95,10 @@ export class CollectorExporter implements SpanExporter { toCollectorSpan(span) ); this.logger.debug('spans to be sent', spansToBeSent); - this.sendSpan(spansToBeSent, resolve, reject); + + // 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); } @@ -103,21 +106,6 @@ export class CollectorExporter implements SpanExporter { } /** - * Send spans to [opentelemetry collector]{@link https://github.com/open-telemetry/opentelemetry-collector} - * it will use the appropriate transport layer automatically depends on platform - * @param spans - * @param onSuccess - * @param onError - */ - sendSpan( - spans: collectorTypes.Span[], - onSuccess: () => void, - onError: (status?: number) => void - ) { - // platform dependent - sendSpans(spans, onSuccess, onError, this); - } - /** * Shutdown the exporter. */ @@ -132,8 +120,9 @@ export class CollectorExporter implements SpanExporter { // 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/platform/browser/sendSpans.ts b/packages/opentelemetry-exporter-collector/src/platform/browser/sendSpans.ts index 869d569f99..a1700847c2 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/browser/sendSpans.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/browser/sendSpans.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { hrTime, hrTimeToTimeStamp } from '@opentelemetry/core'; +import * as core from '@opentelemetry/core'; import { Logger } from '@opentelemetry/types'; import { CollectorExporter } from '../../CollectorExporter'; import * as collectorTypes from '../../types'; @@ -53,13 +53,14 @@ export function sendSpans( node: { identifier: { hostName: collectorExporter.hostName || window.location.host, - startTimestamp: hrTimeToTimeStamp(hrTime()), + startTimestamp: core.hrTimeToTimeStamp(core.hrTime()), }, libraryInfo: { language: collectorTypes.LibraryInfoLanguage.WEB_JS, - // coreLibraryVersion: , not implemented - // exporterVersion: , not implemented - // coreLibraryVersion: , not implemented + // @TODO add version - cannot use require('package.json') + // as it is failing in browser need to figure out better way + // coreLibraryVersion: core.version, + // exporterVersion: version, }, serviceInfo: { name: collectorExporter.serviceName, diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts b/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts index 79880a21f7..db7c5135b7 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts @@ -18,7 +18,7 @@ const http = require('http'); const https = require('https'); import { IncomingMessage } from 'http'; -import { hrTime, hrTimeToTimeStamp } from '@opentelemetry/core'; +import * as core from '@opentelemetry/core'; import { CollectorExporter } from '../../CollectorExporter'; import * as collectorTypes from '../../types'; @@ -57,13 +57,14 @@ export function sendSpans( node: { identifier: { hostName: collectorExporter.hostName, - startTimestamp: hrTimeToTimeStamp(hrTime()), + startTimestamp: core.hrTimeToTimeStamp(core.hrTime()), }, libraryInfo: { language: collectorTypes.LibraryInfoLanguage.NODE_JS, - // coreLibraryVersion: , not implemented - // exporterVersion: , not implemented - // coreLibraryVersion: , not implemented + // @TODO add version - cannot use require('package.json') + // as it is failing in browser need to figure out better way + // coreLibraryVersion: core.version, + // exporterVersion: version, }, serviceInfo: { name: collectorExporter.serviceName, @@ -91,13 +92,16 @@ export function sendSpans( 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 index 9a52b0ad5e..31568e3987 100644 --- a/packages/opentelemetry-exporter-collector/src/transform.ts +++ b/packages/opentelemetry-exporter-collector/src/transform.ts @@ -89,7 +89,7 @@ export function toCollectorEventValue( } else if (typeof value === 'boolean') { attributeValue.boolValue = value; } else if (typeof value === 'number') { - if (Math.floor(value) === value) { + if (valueCanBeInteger(value) && Math.floor(value) === value) { attributeValue.intValue = value; } else { attributeValue.doubleValue = value; @@ -99,6 +99,13 @@ export function toCollectorEventValue( return attributeValue; } +function valueCanBeInteger(value: unknown) { + if (typeof value === 'number') { + return value >= Number.MIN_SAFE_INTEGER && value <= Number.MAX_SAFE_INTEGER; + } + return false; +} + /** * convert events * @param events array of events diff --git a/packages/opentelemetry-exporter-collector/test/common/CollectorExporter.test.ts b/packages/opentelemetry-exporter-collector/test/common/CollectorExporter.test.ts index 92bcde471b..c21d47ce00 100644 --- a/packages/opentelemetry-exporter-collector/test/common/CollectorExporter.test.ts +++ b/packages/opentelemetry-exporter-collector/test/common/CollectorExporter.test.ts @@ -93,7 +93,7 @@ describe('CollectorExporter', () => { describe('export', () => { let spySend: any; beforeEach(() => { - spySend = sinon.stub(collectorExporter, 'sendSpan'); + spySend = sinon.stub(platform, 'sendSpans'); }); afterEach(() => { spySend.restore(); @@ -114,10 +114,8 @@ describe('CollectorExporter', () => { }); describe('shutdown', () => { - let spySend: any; let onShutdownSpy: any; beforeEach(() => { - spySend = sinon.stub(collectorExporter, 'sendSpan'); onShutdownSpy = sinon.stub(platform, 'onShutdown'); collectorExporterConfig = { hostName: 'foo', @@ -129,7 +127,6 @@ describe('CollectorExporter', () => { collectorExporter = new CollectorExporter(collectorExporterConfig); }); afterEach(() => { - spySend.restore(); onShutdownSpy.restore(); }); diff --git a/packages/opentelemetry-exporter-collector/test/node/CollectorExporter.test.ts b/packages/opentelemetry-exporter-collector/test/node/CollectorExporter.test.ts index d2344c3baa..7e7b164825 100644 --- a/packages/opentelemetry-exporter-collector/test/node/CollectorExporter.test.ts +++ b/packages/opentelemetry-exporter-collector/test/node/CollectorExporter.test.ts @@ -77,7 +77,7 @@ describe('CollectorExporter', () => { assert.strictEqual(options.hostname, 'foo.bar.com'); assert.strictEqual(options.method, 'POST'); assert.strictEqual(options.path, '/'); - assert.deepStrictEqual(options.headers, { 'Content-Length': 1901 }); + assert.deepStrictEqual(options.headers, { 'Content-Length': 1956 }); done(); }); }); @@ -104,34 +104,40 @@ describe('CollectorExporter', () => { const spyLoggerDebug = sinon.stub(collectorExporter.logger, 'debug'); const spyLoggerError = sinon.stub(collectorExporter.logger, 'error'); - collectorExporter.export(spans, function() {}); + const responseSpy = sinon.spy(); + collectorExporter.export(spans, responseSpy); setTimeout(() => { const args = spyRequest.args[0]; const callback = args[1]; callback(mockRes); - - const response: any = spyLoggerDebug.args[1][0]; - assert.strictEqual(response, 'statusCode: 200'); - assert.strictEqual(spyLoggerError.args.length, 0); - done(); + 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'); - collectorExporter.export(spans, function() {}); + const responseSpy = sinon.spy(); + collectorExporter.export(spans, responseSpy); setTimeout(() => { - // const response: any = spyLoggerDebug.args[0][0]; const args = spyRequest.args[0]; const callback = args[1]; callback(mockResError); + setTimeout(()=> { + const response: any = spyLoggerError.args[0][0]; + assert.strictEqual(response, 'statusCode: 400'); - const response: any = spyLoggerError.args[0][0]; - assert.strictEqual(response, 'statusCode: 400'); - done(); + assert.strictEqual(responseSpy.args[0][0], 1); + done(); + }); }); }); }); From 7c2923b152dc1d7146efd192775b1fc08c76e91d Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Fri, 22 Nov 2019 17:50:51 +0100 Subject: [PATCH 10/25] chore: fixes after comments --- .../src/CollectorExporter.ts | 8 +-- .../src/platform/node/sendSpans.ts | 4 +- .../src/transform.ts | 49 ++++--------------- .../test/browser/CollectorExporter.test.ts | 2 +- .../test/common/CollectorExporter.test.ts | 2 +- .../test/common/transform.test.ts | 17 +------ .../test/node/CollectorExporter.test.ts | 10 ++-- 7 files changed, 25 insertions(+), 67 deletions(-) diff --git a/packages/opentelemetry-exporter-collector/src/CollectorExporter.ts b/packages/opentelemetry-exporter-collector/src/CollectorExporter.ts index 5066d7da96..ac088d49a0 100644 --- a/packages/opentelemetry-exporter-collector/src/CollectorExporter.ts +++ b/packages/opentelemetry-exporter-collector/src/CollectorExporter.ts @@ -121,8 +121,10 @@ export class CollectorExporter implements SpanExporter { onShutdown(this.shutdown); // @TODO get spans from span processor (batch) - this._exportSpans([]).then(() => { - this.logger.debug('shutdown completed'); - }).catch(()=> {}); + this._exportSpans([]) + .then(() => { + this.logger.debug('shutdown completed'); + }) + .catch(() => {}); } } diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts b/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts index db7c5135b7..14cc63a18e 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -const http = require('http'); -const https = require('https'); +import * as http from 'http'; +import * as https from 'https'; import { IncomingMessage } from 'http'; import * as core from '@opentelemetry/core'; diff --git a/packages/opentelemetry-exporter-collector/src/transform.ts b/packages/opentelemetry-exporter-collector/src/transform.ts index 31568e3987..adaa7a83ef 100644 --- a/packages/opentelemetry-exporter-collector/src/transform.ts +++ b/packages/opentelemetry-exporter-collector/src/transform.ts @@ -20,7 +20,6 @@ import { Attributes, Link, TimedEvent, TraceState } from '@opentelemetry/types'; import * as collectorTypes from './types'; const OT_MAX_STRING_LENGTH = 128; -const OT_MAX_ATTRIBUTES = 30; const LINK_TYPE_UNSPECIFIED: collectorTypes.LinkTypeUnspecified = 0; // const LINK_TYPE_CHILD_LINKED_SPAN: collectorTypes.LinkTypeChildLinkedSpan = 1; @@ -43,34 +42,17 @@ export function toCollectorTruncatableString( /** * convert attributes * @param attributes - * @param maxAttributes - default value is 30 */ export function toCollectorAttributes( - attributes: Attributes, - maxAttributes: number = OT_MAX_ATTRIBUTES + attributes: Attributes ): collectorTypes.Attributes { const attributeMap: collectorTypes.AttributeMap = {}; - let droppedAttributesCount = 0; - - const keys = Object.keys(attributes || {}); - - const countKeys = Math.min(keys.length, maxAttributes); - if (keys.length > maxAttributes) { - droppedAttributesCount = keys.length - maxAttributes; - } - - for (let i = 0; i <= countKeys - 1; i++) { - const key = keys[i]; - const eventAttributeValue = toCollectorEventValue( - attributes && attributes[key] - ); - if (eventAttributeValue) { - attributeMap[key] = eventAttributeValue; - } - } + Object.keys(attributes || {}).forEach(key => { + attributeMap[key] = toCollectorEventValue(attributes[key]); + }); return { - droppedAttributesCount, + droppedAttributesCount: 0, attributeMap, }; } @@ -81,7 +63,7 @@ export function toCollectorAttributes( */ export function toCollectorEventValue( value: unknown -): collectorTypes.AttributeValue | undefined { +): collectorTypes.AttributeValue { const attributeValue: collectorTypes.AttributeValue = {}; if (typeof value === 'string') { @@ -89,31 +71,20 @@ export function toCollectorEventValue( } else if (typeof value === 'boolean') { attributeValue.boolValue = value; } else if (typeof value === 'number') { - if (valueCanBeInteger(value) && Math.floor(value) === value) { - attributeValue.intValue = value; - } else { - attributeValue.doubleValue = value; - } + // all numbers will be treated as double + attributeValue.doubleValue = value; } return attributeValue; } -function valueCanBeInteger(value: unknown) { - if (typeof value === 'number') { - return value >= Number.MIN_SAFE_INTEGER && value <= Number.MAX_SAFE_INTEGER; - } - return false; -} - /** * convert events * @param events array of events * @param maxAttributes - maximum number of event attributes to be converted */ export function toCollectorEvents( - events: TimedEvent[], - maxAttributes: number = OT_MAX_ATTRIBUTES + events: TimedEvent[] ): collectorTypes.TimeEvents { let droppedAnnotationsCount = 0; let droppedMessageEventsCount = 0; // not counting yet as messageEvent is not implemented @@ -123,7 +94,7 @@ export function toCollectorEvents( let attributes: collectorTypes.Attributes | undefined; if (event && event.attributes) { - attributes = toCollectorAttributes(event.attributes, maxAttributes); + attributes = toCollectorAttributes(event.attributes); droppedAnnotationsCount += attributes.droppedAttributesCount || 0; } diff --git a/packages/opentelemetry-exporter-collector/test/browser/CollectorExporter.test.ts b/packages/opentelemetry-exporter-collector/test/browser/CollectorExporter.test.ts index fbc70cec3b..4fed6d8713 100644 --- a/packages/opentelemetry-exporter-collector/test/browser/CollectorExporter.test.ts +++ b/packages/opentelemetry-exporter-collector/test/browser/CollectorExporter.test.ts @@ -27,7 +27,7 @@ import * as collectorTypes from '../../src/types'; import { ensureSpanIsCorrect, mockedReadableSpan } from '../helper'; const sendBeacon = navigator.sendBeacon; -describe('CollectorExporter', () => { +describe('CollectorExporter - web', () => { let collectorExporter: CollectorExporter; let collectorExporterConfig: CollectorExporterConfig; let spyOpen: any; diff --git a/packages/opentelemetry-exporter-collector/test/common/CollectorExporter.test.ts b/packages/opentelemetry-exporter-collector/test/common/CollectorExporter.test.ts index c21d47ce00..d95e5ac1eb 100644 --- a/packages/opentelemetry-exporter-collector/test/common/CollectorExporter.test.ts +++ b/packages/opentelemetry-exporter-collector/test/common/CollectorExporter.test.ts @@ -27,7 +27,7 @@ import * as platform from '../../src/platform/index'; import { ensureSpanIsCorrect, mockedReadableSpan } from '../helper'; -describe('CollectorExporter', () => { +describe('CollectorExporter - common', () => { let collectorExporter: CollectorExporter; let collectorExporterConfig: CollectorExporterConfig; diff --git a/packages/opentelemetry-exporter-collector/test/common/transform.test.ts b/packages/opentelemetry-exporter-collector/test/common/transform.test.ts index 4b0b258a34..d1227f04a8 100644 --- a/packages/opentelemetry-exporter-collector/test/common/transform.test.ts +++ b/packages/opentelemetry-exporter-collector/test/common/transform.test.ts @@ -65,7 +65,7 @@ describe('transform', () => { assert.deepStrictEqual(transform.toCollectorAttributes(attributes), { attributeMap: { foo: { - intValue: 13, + doubleValue: 13, }, }, droppedAttributesCount: 0, @@ -99,21 +99,6 @@ describe('transform', () => { droppedAttributesCount: 0, }); }); - - it('should convert only first attribute', () => { - const attributes: Attributes = { - foo: 1, - bar: 1, - }; - assert.deepStrictEqual(transform.toCollectorAttributes(attributes, 1), { - attributeMap: { - foo: { - intValue: 1, - }, - }, - droppedAttributesCount: 1, - }); - }); }); describe('toCollectorEvents', () => { diff --git a/packages/opentelemetry-exporter-collector/test/node/CollectorExporter.test.ts b/packages/opentelemetry-exporter-collector/test/node/CollectorExporter.test.ts index 7e7b164825..7e5705a9c8 100644 --- a/packages/opentelemetry-exporter-collector/test/node/CollectorExporter.test.ts +++ b/packages/opentelemetry-exporter-collector/test/node/CollectorExporter.test.ts @@ -41,7 +41,7 @@ const mockResError = { statusCode: 400, }; -describe('CollectorExporter', () => { +describe('CollectorExporter - node', () => { let collectorExporter: CollectorExporter; let collectorExporterConfig: CollectorExporterConfig; let spyRequest: any; @@ -77,7 +77,7 @@ describe('CollectorExporter', () => { assert.strictEqual(options.hostname, 'foo.bar.com'); assert.strictEqual(options.method, 'POST'); assert.strictEqual(options.path, '/'); - assert.deepStrictEqual(options.headers, { 'Content-Length': 1956 }); + assert.deepStrictEqual(options.headers, { 'Content-Length': 1901 }); done(); }); }); @@ -111,13 +111,13 @@ describe('CollectorExporter', () => { const args = spyRequest.args[0]; const callback = args[1]; callback(mockRes); - setTimeout(()=> { + 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(); - }) + }); }); }); @@ -131,7 +131,7 @@ describe('CollectorExporter', () => { const args = spyRequest.args[0]; const callback = args[1]; callback(mockResError); - setTimeout(()=> { + setTimeout(() => { const response: any = spyLoggerError.args[0][0]; assert.strictEqual(response, 'statusCode: 400'); From a42d9923c48c7fad6650009eee04177bc51d4e3b Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Fri, 22 Nov 2019 17:52:46 +0100 Subject: [PATCH 11/25] chore: fixes after comments --- .../src/platform/node/sendSpans.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts b/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts index 14cc63a18e..5af1b7d69f 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts @@ -23,7 +23,7 @@ import { CollectorExporter } from '../../CollectorExporter'; import * as collectorTypes from '../../types'; -const url = require('url'); +import * as url from 'url'; /** * function that is called once when {@link ExporterCollector} is initialised From e3203f006a4b94a334171aac649bfbf36f746f02 Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Mon, 25 Nov 2019 11:58:13 +0100 Subject: [PATCH 12/25] chore: updating jsdoc --- .../opentelemetry-core/src/common/time.ts | 51 +++++++++++++++---- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/packages/opentelemetry-core/src/common/time.ts b/packages/opentelemetry-core/src/common/time.ts index 41dfa40b13..45e76b88af 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,7 +113,10 @@ export function hrTimeDuration( return [seconds, nanos]; } -// Convert hrTime to timestamp. +/** + * Convert hrTime to timestamp. + * @param hrTime + */ export function hrTimeToTimeStamp(hrTime: types.HrTime): string { const precision = NANOSECOND_DIGITS; const tmp = `${'0'.repeat(precision)}${hrTime[1]}Z`; @@ -108,21 +125,34 @@ export function hrTimeToTimeStamp(hrTime: types.HrTime): string { return date.replace('000Z', nanoString); } -// Convert hrTime to nanoseconds. +/** + * 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) && @@ -132,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) || From dfbbc9427a7ba13b078a2f53a2126369fb7bfc97 Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Mon, 25 Nov 2019 13:37:46 +0100 Subject: [PATCH 13/25] chore: enabling attributes --- .../src/CollectorExporter.ts | 5 ++++- .../src/platform/browser/sendSpans.ts | 2 +- .../src/platform/node/sendSpans.ts | 2 +- packages/opentelemetry-exporter-collector/src/types.ts | 2 +- .../test/browser/CollectorExporter.test.ts | 2 +- .../test/common/CollectorExporter.test.ts | 4 ++-- .../test/node/CollectorExporter.test.ts | 4 ++-- 7 files changed, 12 insertions(+), 9 deletions(-) diff --git a/packages/opentelemetry-exporter-collector/src/CollectorExporter.ts b/packages/opentelemetry-exporter-collector/src/CollectorExporter.ts index ac088d49a0..72a3d24264 100644 --- a/packages/opentelemetry-exporter-collector/src/CollectorExporter.ts +++ b/packages/opentelemetry-exporter-collector/src/CollectorExporter.ts @@ -31,7 +31,7 @@ export interface CollectorExporterConfig { hostName?: string; logger?: Logger; serviceName?: string; - spanAttributes?: Attributes; + attributes?: Attributes; url?: string; } @@ -46,6 +46,7 @@ export class CollectorExporter implements SpanExporter { readonly url: string; readonly logger: Logger; readonly hostName: string | undefined; + readonly attributes?: Attributes; private _isShutdown: boolean = false; /** @@ -58,6 +59,8 @@ export class CollectorExporter implements SpanExporter { this.hostName = config.hostName; } + this.attributes = config.attributes; + this.logger = config.logger || new NoopLogger(); this.shutdown = this.shutdown.bind(this); diff --git a/packages/opentelemetry-exporter-collector/src/platform/browser/sendSpans.ts b/packages/opentelemetry-exporter-collector/src/platform/browser/sendSpans.ts index a1700847c2..c62592c04d 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/browser/sendSpans.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/browser/sendSpans.ts @@ -65,7 +65,7 @@ export function sendSpans( serviceInfo: { name: collectorExporter.serviceName, }, - // attributes: {} + attributes: collectorExporter.attributes, }, // resource: '', not implemented spans, diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts b/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts index 5af1b7d69f..5c33fd130e 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts @@ -69,7 +69,7 @@ export function sendSpans( serviceInfo: { name: collectorExporter.serviceName, }, - // attributes: {} + attributes: collectorExporter.attributes, }, // resource: '', not implemented spans, diff --git a/packages/opentelemetry-exporter-collector/src/types.ts b/packages/opentelemetry-exporter-collector/src/types.ts index 5e31c54f6d..b18ccb947f 100644 --- a/packages/opentelemetry-exporter-collector/src/types.ts +++ b/packages/opentelemetry-exporter-collector/src/types.ts @@ -206,7 +206,7 @@ export interface Node { serviceInfo?: ServiceInfo; /** Additional attributes. */ - attributes?: { [key: string]: string }; + attributes?: { [key: string]: unknown }; } /** diff --git a/packages/opentelemetry-exporter-collector/test/browser/CollectorExporter.test.ts b/packages/opentelemetry-exporter-collector/test/browser/CollectorExporter.test.ts index 4fed6d8713..62765f2804 100644 --- a/packages/opentelemetry-exporter-collector/test/browser/CollectorExporter.test.ts +++ b/packages/opentelemetry-exporter-collector/test/browser/CollectorExporter.test.ts @@ -43,7 +43,7 @@ describe('CollectorExporter - web', () => { hostName: 'foo', logger: new NoopLogger(), serviceName: 'bar', - spanAttributes: {}, + attributes: {}, url: 'http://foo.bar.com', }; collectorExporter = new CollectorExporter(collectorExporterConfig); diff --git a/packages/opentelemetry-exporter-collector/test/common/CollectorExporter.test.ts b/packages/opentelemetry-exporter-collector/test/common/CollectorExporter.test.ts index d95e5ac1eb..b2025a5ea5 100644 --- a/packages/opentelemetry-exporter-collector/test/common/CollectorExporter.test.ts +++ b/packages/opentelemetry-exporter-collector/test/common/CollectorExporter.test.ts @@ -39,7 +39,7 @@ describe('CollectorExporter - common', () => { hostName: 'foo', logger: new NoopLogger(), serviceName: 'bar', - spanAttributes: {}, + attributes: {}, url: 'http://foo.bar.com', }; collectorExporter = new CollectorExporter(collectorExporterConfig); @@ -121,7 +121,7 @@ describe('CollectorExporter - common', () => { hostName: 'foo', logger: new NoopLogger(), serviceName: 'bar', - spanAttributes: {}, + attributes: {}, url: 'http://foo.bar.com', }; collectorExporter = new CollectorExporter(collectorExporterConfig); diff --git a/packages/opentelemetry-exporter-collector/test/node/CollectorExporter.test.ts b/packages/opentelemetry-exporter-collector/test/node/CollectorExporter.test.ts index 7e5705a9c8..0d6995aad3 100644 --- a/packages/opentelemetry-exporter-collector/test/node/CollectorExporter.test.ts +++ b/packages/opentelemetry-exporter-collector/test/node/CollectorExporter.test.ts @@ -55,7 +55,7 @@ describe('CollectorExporter - node', () => { hostName: 'foo', logger: new NoopLogger(), serviceName: 'bar', - spanAttributes: {}, + attributes: {}, url: 'http://foo.bar.com', }; collectorExporter = new CollectorExporter(collectorExporterConfig); @@ -77,7 +77,7 @@ describe('CollectorExporter - node', () => { assert.strictEqual(options.hostname, 'foo.bar.com'); assert.strictEqual(options.method, 'POST'); assert.strictEqual(options.path, '/'); - assert.deepStrictEqual(options.headers, { 'Content-Length': 1901 }); + assert.deepStrictEqual(options.headers, { 'Content-Length': 1917 }); done(); }); }); From 08d8f8c0e478921ce97c408a897c19efed7201b8 Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Mon, 25 Nov 2019 14:15:39 +0100 Subject: [PATCH 14/25] chore: adding script to generate package version file --- packages/opentelemetry-core/package.json | 3 +- .../opentelemetry-core/scripts/version.js | 47 +++++++++++++++++++ .../opentelemetry-core/src/common/version.ts | 18 +++++++ packages/opentelemetry-core/src/index.ts | 1 + .../package.json | 3 +- .../scripts/version.js | 47 +++++++++++++++++++ .../src/platform/browser/sendSpans.ts | 7 ++- .../src/platform/node/sendSpans.ts | 7 ++- .../src/version.ts | 18 +++++++ .../test/node/CollectorExporter.test.ts | 1 - 10 files changed, 141 insertions(+), 11 deletions(-) create mode 100644 packages/opentelemetry-core/scripts/version.js create mode 100644 packages/opentelemetry-core/src/common/version.ts create mode 100644 packages/opentelemetry-exporter-collector/scripts/version.js create mode 100644 packages/opentelemetry-exporter-collector/src/version.ts diff --git a/packages/opentelemetry-core/package.json b/packages/opentelemetry-core/package.json index 07d89882b6..a1c4693519 100644 --- a/packages/opentelemetry-core/package.json +++ b/packages/opentelemetry-core/package.json @@ -20,9 +20,10 @@ "clean": "rimraf build/*", "check": "gts check", "precompile": "tsc --version", - "compile": "tsc -p .", + "compile": "npm run version && tsc -p .", "fix": "gts fix", "prepare": "npm run compile", + "version": "node scripts/version.js", "watch": "tsc -w" }, "keywords": [ diff --git a/packages/opentelemetry-core/scripts/version.js b/packages/opentelemetry-core/scripts/version.js new file mode 100644 index 0000000000..84465336d7 --- /dev/null +++ b/packages/opentelemetry-core/scripts/version.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.js +export const version: string = '${pjson.version};'; +`; + +fs.writeFileSync(fileUrl, `${license}${content}`); diff --git a/packages/opentelemetry-core/src/common/version.ts b/packages/opentelemetry-core/src/common/version.ts new file mode 100644 index 0000000000..de436912df --- /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.js +export const version: string = '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-exporter-collector/package.json b/packages/opentelemetry-exporter-collector/package.json index 1f6e3b1654..2d12df745a 100644 --- a/packages/opentelemetry-exporter-collector/package.json +++ b/packages/opentelemetry-exporter-collector/package.json @@ -14,13 +14,14 @@ "clean": "rimraf build/*", "codecov:browser": "nyc report --reporter=json && codecov -f coverage/*.json -p ../../", "precompile": "tsc --version", - "compile": "tsc -p .", + "compile": "npm run version && 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": "node scripts/version.js", "watch": "tsc -w" }, "keywords": [ diff --git a/packages/opentelemetry-exporter-collector/scripts/version.js b/packages/opentelemetry-exporter-collector/scripts/version.js new file mode 100644 index 0000000000..30b6d22fa6 --- /dev/null +++ b/packages/opentelemetry-exporter-collector/scripts/version.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.js +export const version: string = '${pjson.version};'; +`; + +fs.writeFileSync(fileUrl, `${license}${content}`); diff --git a/packages/opentelemetry-exporter-collector/src/platform/browser/sendSpans.ts b/packages/opentelemetry-exporter-collector/src/platform/browser/sendSpans.ts index c62592c04d..6a755e4f5b 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/browser/sendSpans.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/browser/sendSpans.ts @@ -18,6 +18,7 @@ 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 @@ -57,10 +58,8 @@ export function sendSpans( }, libraryInfo: { language: collectorTypes.LibraryInfoLanguage.WEB_JS, - // @TODO add version - cannot use require('package.json') - // as it is failing in browser need to figure out better way - // coreLibraryVersion: core.version, - // exporterVersion: version, + coreLibraryVersion: core.version, + exporterVersion: version, }, serviceInfo: { name: collectorExporter.serviceName, diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts b/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts index 5c33fd130e..7dc19bd1b8 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts @@ -24,6 +24,7 @@ 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 @@ -61,10 +62,8 @@ export function sendSpans( }, libraryInfo: { language: collectorTypes.LibraryInfoLanguage.NODE_JS, - // @TODO add version - cannot use require('package.json') - // as it is failing in browser need to figure out better way - // coreLibraryVersion: core.version, - // exporterVersion: version, + coreLibraryVersion: core.version, + exporterVersion: version, }, serviceInfo: { name: collectorExporter.serviceName, diff --git a/packages/opentelemetry-exporter-collector/src/version.ts b/packages/opentelemetry-exporter-collector/src/version.ts new file mode 100644 index 0000000000..de436912df --- /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.js +export const version: string = '0.2.0;'; diff --git a/packages/opentelemetry-exporter-collector/test/node/CollectorExporter.test.ts b/packages/opentelemetry-exporter-collector/test/node/CollectorExporter.test.ts index 0d6995aad3..d7e0d2c876 100644 --- a/packages/opentelemetry-exporter-collector/test/node/CollectorExporter.test.ts +++ b/packages/opentelemetry-exporter-collector/test/node/CollectorExporter.test.ts @@ -77,7 +77,6 @@ describe('CollectorExporter - node', () => { assert.strictEqual(options.hostname, 'foo.bar.com'); assert.strictEqual(options.method, 'POST'); assert.strictEqual(options.path, '/'); - assert.deepStrictEqual(options.headers, { 'Content-Length': 1917 }); done(); }); }); From 0fed57a4d508b1beaae9c083c72b91fd54a225e2 Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Mon, 25 Nov 2019 18:35:37 +0100 Subject: [PATCH 15/25] chore: naming --- packages/opentelemetry-core/package.json | 4 ++-- .../scripts/{version.js => version-update.js} | 4 ++-- packages/opentelemetry-core/src/common/version.ts | 2 +- packages/opentelemetry-exporter-collector/package.json | 4 ++-- .../scripts/{version.js => version-update.js} | 4 ++-- .../src/platform/browser/sendSpans.ts | 6 +++--- .../src/platform/node/sendSpans.ts | 6 +++--- packages/opentelemetry-exporter-collector/src/version.ts | 2 +- 8 files changed, 16 insertions(+), 16 deletions(-) rename packages/opentelemetry-core/scripts/{version.js => version-update.js} (93%) rename packages/opentelemetry-exporter-collector/scripts/{version.js => version-update.js} (93%) diff --git a/packages/opentelemetry-core/package.json b/packages/opentelemetry-core/package.json index a1c4693519..915aa32cb1 100644 --- a/packages/opentelemetry-core/package.json +++ b/packages/opentelemetry-core/package.json @@ -20,10 +20,10 @@ "clean": "rimraf build/*", "check": "gts check", "precompile": "tsc --version", - "compile": "npm run version && tsc -p .", + "compile": "npm run version:update && tsc -p .", "fix": "gts fix", "prepare": "npm run compile", - "version": "node scripts/version.js", + "version:update": "node scripts/version-update.js", "watch": "tsc -w" }, "keywords": [ diff --git a/packages/opentelemetry-core/scripts/version.js b/packages/opentelemetry-core/scripts/version-update.js similarity index 93% rename from packages/opentelemetry-core/scripts/version.js rename to packages/opentelemetry-core/scripts/version-update.js index 84465336d7..eb5ea51124 100644 --- a/packages/opentelemetry-core/scripts/version.js +++ b/packages/opentelemetry-core/scripts/version-update.js @@ -40,8 +40,8 @@ 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.js -export const version: string = '${pjson.version};'; +// this is autogenerated file, see scripts/version-update.js +export const VERSION: string = '${pjson.version};'; `; fs.writeFileSync(fileUrl, `${license}${content}`); diff --git a/packages/opentelemetry-core/src/common/version.ts b/packages/opentelemetry-core/src/common/version.ts index de436912df..ab635f0943 100644 --- a/packages/opentelemetry-core/src/common/version.ts +++ b/packages/opentelemetry-core/src/common/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version.js -export const version: string = '0.2.0;'; +export const VERSION: string = '0.2.0;'; diff --git a/packages/opentelemetry-exporter-collector/package.json b/packages/opentelemetry-exporter-collector/package.json index 2d12df745a..53ce305465 100644 --- a/packages/opentelemetry-exporter-collector/package.json +++ b/packages/opentelemetry-exporter-collector/package.json @@ -14,14 +14,14 @@ "clean": "rimraf build/*", "codecov:browser": "nyc report --reporter=json && codecov -f coverage/*.json -p ../../", "precompile": "tsc --version", - "compile": "npm run version && tsc -p .", + "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": "node scripts/version.js", + "version:update": "node scripts/version-update.js", "watch": "tsc -w" }, "keywords": [ diff --git a/packages/opentelemetry-exporter-collector/scripts/version.js b/packages/opentelemetry-exporter-collector/scripts/version-update.js similarity index 93% rename from packages/opentelemetry-exporter-collector/scripts/version.js rename to packages/opentelemetry-exporter-collector/scripts/version-update.js index 30b6d22fa6..b2ce40c8d8 100644 --- a/packages/opentelemetry-exporter-collector/scripts/version.js +++ b/packages/opentelemetry-exporter-collector/scripts/version-update.js @@ -40,8 +40,8 @@ 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.js -export const version: string = '${pjson.version};'; +// this is autogenerated file, see scripts/version-update.js +export const VERSION: string = '${pjson.version};'; `; fs.writeFileSync(fileUrl, `${license}${content}`); diff --git a/packages/opentelemetry-exporter-collector/src/platform/browser/sendSpans.ts b/packages/opentelemetry-exporter-collector/src/platform/browser/sendSpans.ts index 6a755e4f5b..f5fc8bb71a 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/browser/sendSpans.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/browser/sendSpans.ts @@ -18,7 +18,7 @@ import * as core from '@opentelemetry/core'; import { Logger } from '@opentelemetry/types'; import { CollectorExporter } from '../../CollectorExporter'; import * as collectorTypes from '../../types'; -import { version } from '../../version'; +import { VERSION } from '../../version'; /** * function that is called once when {@link ExporterCollector} is initialised @@ -58,8 +58,8 @@ export function sendSpans( }, libraryInfo: { language: collectorTypes.LibraryInfoLanguage.WEB_JS, - coreLibraryVersion: core.version, - exporterVersion: version, + coreLibraryVersion: core.VERSION, + exporterVersion: VERSION, }, serviceInfo: { name: collectorExporter.serviceName, diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts b/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts index 7dc19bd1b8..fe33e59874 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts @@ -24,7 +24,7 @@ import { CollectorExporter } from '../../CollectorExporter'; import * as collectorTypes from '../../types'; import * as url from 'url'; -import { version } from '../../version'; +import { VERSION } from '../../version'; /** * function that is called once when {@link ExporterCollector} is initialised @@ -62,8 +62,8 @@ export function sendSpans( }, libraryInfo: { language: collectorTypes.LibraryInfoLanguage.NODE_JS, - coreLibraryVersion: core.version, - exporterVersion: version, + coreLibraryVersion: core.VERSION, + exporterVersion: VERSION, }, serviceInfo: { name: collectorExporter.serviceName, diff --git a/packages/opentelemetry-exporter-collector/src/version.ts b/packages/opentelemetry-exporter-collector/src/version.ts index de436912df..ab635f0943 100644 --- a/packages/opentelemetry-exporter-collector/src/version.ts +++ b/packages/opentelemetry-exporter-collector/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version.js -export const version: string = '0.2.0;'; +export const VERSION: string = '0.2.0;'; From 1868afd5d1eb41cf88a323b3656c25b26d488982 Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Mon, 25 Nov 2019 18:45:19 +0100 Subject: [PATCH 16/25] chore: adding todo --- packages/opentelemetry-exporter-collector/src/transform.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/opentelemetry-exporter-collector/src/transform.ts b/packages/opentelemetry-exporter-collector/src/transform.ts index adaa7a83ef..ecfe992dc7 100644 --- a/packages/opentelemetry-exporter-collector/src/transform.ts +++ b/packages/opentelemetry-exporter-collector/src/transform.ts @@ -135,6 +135,7 @@ export function toCollectorEvents( /** * 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 */ @@ -143,9 +144,11 @@ export function toCollectorLinkType( 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) { + if (linkSpanId === spanParentId && linkTraceId === spanTraceId) { return LINK_TYPE_PARENT_LINKED_SPAN; } From bfea4ca85dffc786481a938207f8e947135187fb Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Tue, 26 Nov 2019 02:54:29 +0100 Subject: [PATCH 17/25] chore: updating types for link --- .../opentelemetry-core/src/common/version.ts | 2 +- .../src/transform.ts | 9 ++----- .../src/types.ts | 24 +++++++++---------- .../src/version.ts | 2 +- 4 files changed, 15 insertions(+), 22 deletions(-) diff --git a/packages/opentelemetry-core/src/common/version.ts b/packages/opentelemetry-core/src/common/version.ts index ab635f0943..43a6775984 100644 --- a/packages/opentelemetry-core/src/common/version.ts +++ b/packages/opentelemetry-core/src/common/version.ts @@ -14,5 +14,5 @@ * limitations under the License. */ -// this is autogenerated file, see scripts/version.js +// this is autogenerated file, see scripts/version-update.js export const VERSION: string = '0.2.0;'; diff --git a/packages/opentelemetry-exporter-collector/src/transform.ts b/packages/opentelemetry-exporter-collector/src/transform.ts index ecfe992dc7..14f7163470 100644 --- a/packages/opentelemetry-exporter-collector/src/transform.ts +++ b/packages/opentelemetry-exporter-collector/src/transform.ts @@ -21,10 +21,6 @@ import * as collectorTypes from './types'; const OT_MAX_STRING_LENGTH = 128; -const LINK_TYPE_UNSPECIFIED: collectorTypes.LinkTypeUnspecified = 0; -// const LINK_TYPE_CHILD_LINKED_SPAN: collectorTypes.LinkTypeChildLinkedSpan = 1; -const LINK_TYPE_PARENT_LINKED_SPAN: collectorTypes.LinkTypeParentLinkedSpan = 2; - /** * convert string to maximum length of 128, providing information of truncated bytes * @param name - string to be converted @@ -149,10 +145,9 @@ export function toCollectorLinkType( const spanTraceId = span.spanContext.traceId; if (linkSpanId === spanParentId && linkTraceId === spanTraceId) { - return LINK_TYPE_PARENT_LINKED_SPAN; + return collectorTypes.LinkType.PARENT_LINKED_SPAN; } - - return LINK_TYPE_UNSPECIFIED; + return collectorTypes.LinkType.UNSPECIFIED; } /** diff --git a/packages/opentelemetry-exporter-collector/src/types.ts b/packages/opentelemetry-exporter-collector/src/types.ts index b18ccb947f..676fa3fce5 100644 --- a/packages/opentelemetry-exporter-collector/src/types.ts +++ b/packages/opentelemetry-exporter-collector/src/types.ts @@ -156,23 +156,21 @@ export interface Links { droppedLinksCount?: number; } -/** - * The relationship of the two spans is unknown, or known but other than - * parent-child. - */ -export type LinkTypeUnspecified = 0; -/** The linked span is a child of the current span. */ -export type LinkTypeChildLinkedSpan = 1; -/** The linked span is a parent of the current span. */ -export type LinkTypeParentLinkedSpan = 2; /** * The relationship of the current span relative to the linked span: child, * parent, or unspecified. */ -export type LinkType = - | LinkTypeUnspecified - | LinkTypeChildLinkedSpan - | LinkTypeParentLinkedSpan; +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, +} /** * A description of a binary module. diff --git a/packages/opentelemetry-exporter-collector/src/version.ts b/packages/opentelemetry-exporter-collector/src/version.ts index ab635f0943..43a6775984 100644 --- a/packages/opentelemetry-exporter-collector/src/version.ts +++ b/packages/opentelemetry-exporter-collector/src/version.ts @@ -14,5 +14,5 @@ * limitations under the License. */ -// this is autogenerated file, see scripts/version.js +// this is autogenerated file, see scripts/version-update.js export const VERSION: string = '0.2.0;'; From bab3be38d824aea853ccdc0dd75b2cd290a38723 Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Tue, 26 Nov 2019 02:59:19 +0100 Subject: [PATCH 18/25] chore: fixing typo --- packages/opentelemetry-core/scripts/version-update.js | 2 +- packages/opentelemetry-core/src/common/version.ts | 2 +- .../opentelemetry-exporter-collector/scripts/version-update.js | 2 +- packages/opentelemetry-exporter-collector/src/version.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/opentelemetry-core/scripts/version-update.js b/packages/opentelemetry-core/scripts/version-update.js index eb5ea51124..ba2c2128b2 100644 --- a/packages/opentelemetry-core/scripts/version-update.js +++ b/packages/opentelemetry-core/scripts/version-update.js @@ -41,7 +41,7 @@ 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: string = '${pjson.version};'; +export const VERSION: string = '${pjson.version}'; `; fs.writeFileSync(fileUrl, `${license}${content}`); diff --git a/packages/opentelemetry-core/src/common/version.ts b/packages/opentelemetry-core/src/common/version.ts index 43a6775984..933dfd2ee1 100644 --- a/packages/opentelemetry-core/src/common/version.ts +++ b/packages/opentelemetry-core/src/common/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION: string = '0.2.0;'; +export const VERSION: string = '0.2.0'; diff --git a/packages/opentelemetry-exporter-collector/scripts/version-update.js b/packages/opentelemetry-exporter-collector/scripts/version-update.js index b2ce40c8d8..e9bd29801a 100644 --- a/packages/opentelemetry-exporter-collector/scripts/version-update.js +++ b/packages/opentelemetry-exporter-collector/scripts/version-update.js @@ -41,7 +41,7 @@ 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: string = '${pjson.version};'; +export const VERSION: string = '${pjson.version}'; `; fs.writeFileSync(fileUrl, `${license}${content}`); diff --git a/packages/opentelemetry-exporter-collector/src/version.ts b/packages/opentelemetry-exporter-collector/src/version.ts index 43a6775984..933dfd2ee1 100644 --- a/packages/opentelemetry-exporter-collector/src/version.ts +++ b/packages/opentelemetry-exporter-collector/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION: string = '0.2.0;'; +export const VERSION: string = '0.2.0'; From a00072451875ce032bcd2797659640aadf3c4987 Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Tue, 26 Nov 2019 03:02:04 +0100 Subject: [PATCH 19/25] chore: removing unnecessary typing --- packages/opentelemetry-core/scripts/version-update.js | 2 +- packages/opentelemetry-core/src/common/version.ts | 2 +- .../opentelemetry-exporter-collector/scripts/version-update.js | 2 +- packages/opentelemetry-exporter-collector/src/version.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/opentelemetry-core/scripts/version-update.js b/packages/opentelemetry-core/scripts/version-update.js index ba2c2128b2..3240a96b4b 100644 --- a/packages/opentelemetry-core/scripts/version-update.js +++ b/packages/opentelemetry-core/scripts/version-update.js @@ -41,7 +41,7 @@ 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: string = '${pjson.version}'; +export const VERSION = '${pjson.version}'; `; fs.writeFileSync(fileUrl, `${license}${content}`); diff --git a/packages/opentelemetry-core/src/common/version.ts b/packages/opentelemetry-core/src/common/version.ts index 933dfd2ee1..5e6eb85dbe 100644 --- a/packages/opentelemetry-core/src/common/version.ts +++ b/packages/opentelemetry-core/src/common/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION: string = '0.2.0'; +export const VERSION = '0.2.0'; diff --git a/packages/opentelemetry-exporter-collector/scripts/version-update.js b/packages/opentelemetry-exporter-collector/scripts/version-update.js index e9bd29801a..2bb382ce6d 100644 --- a/packages/opentelemetry-exporter-collector/scripts/version-update.js +++ b/packages/opentelemetry-exporter-collector/scripts/version-update.js @@ -41,7 +41,7 @@ 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: string = '${pjson.version}'; +export const VERSION = '${pjson.version}'; `; fs.writeFileSync(fileUrl, `${license}${content}`); diff --git a/packages/opentelemetry-exporter-collector/src/version.ts b/packages/opentelemetry-exporter-collector/src/version.ts index 933dfd2ee1..5e6eb85dbe 100644 --- a/packages/opentelemetry-exporter-collector/src/version.ts +++ b/packages/opentelemetry-exporter-collector/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION: string = '0.2.0'; +export const VERSION = '0.2.0'; From 03d8c8b8f34d53028d280500104d8261d2110452 Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Tue, 26 Nov 2019 03:04:37 +0100 Subject: [PATCH 20/25] chore: const for enum --- packages/opentelemetry-exporter-collector/src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opentelemetry-exporter-collector/src/types.ts b/packages/opentelemetry-exporter-collector/src/types.ts index 676fa3fce5..a432a8cf35 100644 --- a/packages/opentelemetry-exporter-collector/src/types.ts +++ b/packages/opentelemetry-exporter-collector/src/types.ts @@ -19,7 +19,7 @@ import { SpanKind, Status } from '@opentelemetry/types'; /** * {@link https://github.com/open-telemetry/opentelemetry-proto/blob/master/opentelemetry/proto/agent/common/v1/common.proto#L66} */ -export enum LibraryInfoLanguage { +export const enum LibraryInfoLanguage { LANGUAGE_UNSPECIFIED = 0, NODE_JS = 6, WEB_JS = 10, From 2b67bed3075b81ae9dc271a1901b901dfac40787 Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Tue, 26 Nov 2019 03:21:06 +0100 Subject: [PATCH 21/25] chore: adding missing interface for message event --- .../src/transform.ts | 3 +- .../src/types.ts | 37 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/packages/opentelemetry-exporter-collector/src/transform.ts b/packages/opentelemetry-exporter-collector/src/transform.ts index 14f7163470..aaf024b025 100644 --- a/packages/opentelemetry-exporter-collector/src/transform.ts +++ b/packages/opentelemetry-exporter-collector/src/transform.ts @@ -107,7 +107,8 @@ export function toCollectorEvents( annotation.attributes = attributes; } - // const messageEvent: MessageEvent; + // @TODO convert from event.attributes into appropriate MessageEvent + // const messageEvent: collectorTypes.MessageEvent; const timeEvent: collectorTypes.TimeEvent = { time: hrTimeToTimeStamp(event.time), diff --git a/packages/opentelemetry-exporter-collector/src/types.ts b/packages/opentelemetry-exporter-collector/src/types.ts index a432a8cf35..e5c7323cf4 100644 --- a/packages/opentelemetry-exporter-collector/src/types.ts +++ b/packages/opentelemetry-exporter-collector/src/types.ts @@ -172,6 +172,43 @@ export const enum LinkType { 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. */ From 89ad47c2d08f040f52c3c6e4787c99a64446690c Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Tue, 26 Nov 2019 18:16:28 +0100 Subject: [PATCH 22/25] chore: adding timestamp example --- packages/opentelemetry-core/src/common/time.ts | 2 +- .../opentelemetry-exporter-collector/src/CollectorExporter.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/opentelemetry-core/src/common/time.ts b/packages/opentelemetry-core/src/common/time.ts index 45e76b88af..861c9efc44 100644 --- a/packages/opentelemetry-core/src/common/time.ts +++ b/packages/opentelemetry-core/src/common/time.ts @@ -114,7 +114,7 @@ export function hrTimeDuration( } /** - * Convert hrTime to timestamp. + * Convert hrTime to timestamp, for example "2019-05-14T17:00:00.000123456Z" * @param hrTime */ export function hrTimeToTimeStamp(hrTime: types.HrTime): string { diff --git a/packages/opentelemetry-exporter-collector/src/CollectorExporter.ts b/packages/opentelemetry-exporter-collector/src/CollectorExporter.ts index 72a3d24264..90c06f706e 100644 --- a/packages/opentelemetry-exporter-collector/src/CollectorExporter.ts +++ b/packages/opentelemetry-exporter-collector/src/CollectorExporter.ts @@ -108,7 +108,6 @@ export class CollectorExporter implements SpanExporter { }); } - /** /** * Shutdown the exporter. */ From c1ee7f672f1e82e350411087a42f5f4d8fd4890d Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Tue, 26 Nov 2019 21:29:45 +0100 Subject: [PATCH 23/25] chore: changes after review --- .../package.json | 2 +- .../src/CollectorExporter.ts | 2 -- .../src/platform/browser/sendSpans.ts | 5 ++-- .../test/browser/CollectorExporter.test.ts | 12 ++++++++- .../test/helper.ts | 26 +++++++++++++++++++ .../test/node/CollectorExporter.test.ts | 12 ++++++--- 6 files changed, 50 insertions(+), 9 deletions(-) diff --git a/packages/opentelemetry-exporter-collector/package.json b/packages/opentelemetry-exporter-collector/package.json index 53ce305465..06e4eb8a51 100644 --- a/packages/opentelemetry-exporter-collector/package.json +++ b/packages/opentelemetry-exporter-collector/package.json @@ -1,7 +1,7 @@ { "name": "@opentelemetry/exporter-collector", "version": "0.2.0", - "description": "OpenTelemetry Exporter Collector for Web and Node", + "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", diff --git a/packages/opentelemetry-exporter-collector/src/CollectorExporter.ts b/packages/opentelemetry-exporter-collector/src/CollectorExporter.ts index 90c06f706e..4ed2363e9f 100644 --- a/packages/opentelemetry-exporter-collector/src/CollectorExporter.ts +++ b/packages/opentelemetry-exporter-collector/src/CollectorExporter.ts @@ -18,10 +18,8 @@ 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'; /** diff --git a/packages/opentelemetry-exporter-collector/src/platform/browser/sendSpans.ts b/packages/opentelemetry-exporter-collector/src/platform/browser/sendSpans.ts index f5fc8bb71a..1c8a5f9422 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/browser/sendSpans.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/browser/sendSpans.ts @@ -92,7 +92,7 @@ export function sendSpans( } /** - * + * function to send spans using browser navigator.sendBeacon * @param body * @param onSuccess * @param onError @@ -116,7 +116,8 @@ function sendSpansWithBeacon( } /** - * + * function to send spans using browser XMLHttpRequest + * used when navigator.sendBeacon is not available * @param body * @param onSuccess * @param onError diff --git a/packages/opentelemetry-exporter-collector/test/browser/CollectorExporter.test.ts b/packages/opentelemetry-exporter-collector/test/browser/CollectorExporter.test.ts index 62765f2804..0a11981256 100644 --- a/packages/opentelemetry-exporter-collector/test/browser/CollectorExporter.test.ts +++ b/packages/opentelemetry-exporter-collector/test/browser/CollectorExporter.test.ts @@ -24,7 +24,11 @@ import { } from '../../src/CollectorExporter'; import * as collectorTypes from '../../src/types'; -import { ensureSpanIsCorrect, mockedReadableSpan } from '../helper'; +import { + ensureExportTraceServiceRequestIsSet, + ensureSpanIsCorrect, + mockedReadableSpan, +} from '../helper'; const sendBeacon = navigator.sendBeacon; describe('CollectorExporter - web', () => { @@ -80,6 +84,9 @@ describe('CollectorExporter - web', () => { assert.strictEqual(spyBeacon.callCount, 1); assert.strictEqual(spyOpen.callCount, 0); + + ensureExportTraceServiceRequestIsSet(json, 10); + done(); }); }); @@ -149,6 +156,9 @@ describe('CollectorExporter - web', () => { ensureSpanIsCorrect(span1); } assert.strictEqual(spyBeacon.callCount, 0); + + ensureExportTraceServiceRequestIsSet(json, 10); + done(); }); }); diff --git a/packages/opentelemetry-exporter-collector/test/helper.ts b/packages/opentelemetry-exporter-collector/test/helper.ts index febf0deaba..46071b7e7f 100644 --- a/packages/opentelemetry-exporter-collector/test/helper.ts +++ b/packages/opentelemetry-exporter-collector/test/helper.ts @@ -14,10 +14,12 @@ * 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', @@ -167,3 +169,27 @@ export function ensureSpanIsCorrect(span: collectorTypes.Span) { }, }); } + +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 index d7e0d2c876..f057ce471d 100644 --- a/packages/opentelemetry-exporter-collector/test/node/CollectorExporter.test.ts +++ b/packages/opentelemetry-exporter-collector/test/node/CollectorExporter.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { NoopLogger } from '@opentelemetry/core'; +import * as core from '@opentelemetry/core'; import { ReadableSpan } from '@opentelemetry/tracing'; import * as http from 'http'; import * as assert from 'assert'; @@ -25,7 +25,11 @@ import { } from '../../src/CollectorExporter'; import * as collectorTypes from '../../src/types'; -import { ensureSpanIsCorrect, mockedReadableSpan } from '../helper'; +import { + ensureExportTraceServiceRequestIsSet, + ensureSpanIsCorrect, + mockedReadableSpan, +} from '../helper'; const fakeRequest = { end: function() {}, @@ -53,7 +57,7 @@ describe('CollectorExporter - node', () => { spyWrite = sinon.stub(fakeRequest, 'write'); collectorExporterConfig = { hostName: 'foo', - logger: new NoopLogger(), + logger: new core.NoopLogger(), serviceName: 'bar', attributes: {}, url: 'http://foo.bar.com', @@ -95,6 +99,8 @@ describe('CollectorExporter - node', () => { ensureSpanIsCorrect(span1); } + ensureExportTraceServiceRequestIsSet(json, 6); + done(); }); }); From 535d536ff1eda80ae8db3f98ae665d20a9f2f142 Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Wed, 27 Nov 2019 13:42:52 +0100 Subject: [PATCH 24/25] chore: adding case when the exporter is shutdown but export is called --- .../src/CollectorExporter.ts | 4 ++++ .../test/common/CollectorExporter.test.ts | 22 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/packages/opentelemetry-exporter-collector/src/CollectorExporter.ts b/packages/opentelemetry-exporter-collector/src/CollectorExporter.ts index 4ed2363e9f..c7fff44901 100644 --- a/packages/opentelemetry-exporter-collector/src/CollectorExporter.ts +++ b/packages/opentelemetry-exporter-collector/src/CollectorExporter.ts @@ -76,6 +76,10 @@ export class CollectorExporter implements SpanExporter { spans: ReadableSpan[], resultCallback: (result: ExportResult) => void ) { + if (this._isShutdown) { + resultCallback(ExportResult.FAILED_NOT_RETRYABLE); + return; + } this._exportSpans(spans) .then(() => { resultCallback(ExportResult.SUCCESS); diff --git a/packages/opentelemetry-exporter-collector/test/common/CollectorExporter.test.ts b/packages/opentelemetry-exporter-collector/test/common/CollectorExporter.test.ts index b2025a5ea5..12b7985f1d 100644 --- a/packages/opentelemetry-exporter-collector/test/common/CollectorExporter.test.ts +++ b/packages/opentelemetry-exporter-collector/test/common/CollectorExporter.test.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { ExportResult } from '@opentelemetry/base'; import { NoopLogger } from '@opentelemetry/core'; import { ReadableSpan } from '@opentelemetry/tracing'; import * as assert from 'assert'; @@ -94,6 +95,7 @@ describe('CollectorExporter - common', () => { let spySend: any; beforeEach(() => { spySend = sinon.stub(platform, 'sendSpans'); + collectorExporter = new CollectorExporter(collectorExporterConfig); }); afterEach(() => { spySend.restore(); @@ -111,6 +113,26 @@ describe('CollectorExporter - common', () => { }); 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', () => { From 78498230c981cde4730a7ad6bc63b6a8fe642b33 Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Thu, 28 Nov 2019 01:51:10 +0100 Subject: [PATCH 25/25] chore: adding missing header for request to prevent instrumentation --- .../src/platform/browser/sendSpans.ts | 1 + .../src/platform/node/sendSpans.ts | 2 +- packages/opentelemetry-exporter-collector/src/types.ts | 3 +++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/opentelemetry-exporter-collector/src/platform/browser/sendSpans.ts b/packages/opentelemetry-exporter-collector/src/platform/browser/sendSpans.ts index 1c8a5f9422..f7932a2afb 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/browser/sendSpans.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/browser/sendSpans.ts @@ -133,6 +133,7 @@ function sendSpansWithXhr( ) { const xhr = new XMLHttpRequest(); xhr.open('POST', collectorUrl); + xhr.setRequestHeader(collectorTypes.OT_REQUEST_HEADER, '1'); xhr.send(body); xhr.onreadystatechange = () => { diff --git a/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts b/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts index fe33e59874..aaf23d2820 100644 --- a/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts +++ b/packages/opentelemetry-exporter-collector/src/platform/node/sendSpans.ts @@ -81,9 +81,9 @@ export function sendSpans( port: parsedUrl.port, path: parsedUrl.path, method: 'POST', - timeout: 5000, headers: { 'Content-Length': Buffer.byteLength(body), + [collectorTypes.OT_REQUEST_HEADER]: 1, }, }; diff --git a/packages/opentelemetry-exporter-collector/src/types.ts b/packages/opentelemetry-exporter-collector/src/types.ts index e5c7323cf4..1e5cb13b3d 100644 --- a/packages/opentelemetry-exporter-collector/src/types.ts +++ b/packages/opentelemetry-exporter-collector/src/types.ts @@ -16,6 +16,9 @@ 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} */