diff --git a/.circleci/config.yml b/.circleci/config.yml
index 3b04413452..2f7a3a4598 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -73,6 +73,7 @@ cache_2: &cache_2
- packages/opentelemetry-plugin-mysql/node_modules
- packages/opentelemetry-exporter-collector/node_modules
- packages/opentelemetry-plugin-xml-http-request/node_modules
+ - packages/opentelemetry-exporter-stackdriver-trace/node_modules
node_unit_tests: &node_unit_tests
steps:
diff --git a/README.md b/README.md
index aba1d45d39..1cc87cc19b 100644
--- a/README.md
+++ b/README.md
@@ -108,6 +108,7 @@ OpenTelemetry is vendor-agnostic and can upload data to any backend with various
#### Trace Exporters
- [@opentelemetry/exporter-jaeger](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-exporter-jaeger)
- [@opentelemetry/exporter-zipkin](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-exporter-zipkin)
+- [@opentelemetry/exporter-stackdriver-trace](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-exporter-stackdriver-trace)
- [@opentelemetry/exporter-collector](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-exporter-collector)
#### Metric Exporters
diff --git a/examples/basic-tracer-node/multi_exporter.js b/examples/basic-tracer-node/multi_exporter.js
index 2d7723397e..309daf4311 100644
--- a/examples/basic-tracer-node/multi_exporter.js
+++ b/examples/basic-tracer-node/multi_exporter.js
@@ -17,10 +17,9 @@ const collectorExporter = new CollectorExporter({serviceName: 'basic-service'});
registry.addSpanProcessor(new BatchSpanProcessor(zipkinExporter, {
bufferSize: 10 // This is added for example, default size is 100.
}));
-
-// It is recommended to use SimpleSpanProcessor in case of Jaeger exporter as
-// it's internal client already handles the spans with batching logic.
-registry.addSpanProcessor(new SimpleSpanProcessor(jaegerExporter));
+tracer.addSpanProcessor(new BatchSpanProcessor(jaegerExporter), {
+ bufferSize: 10
+});
registry.addSpanProcessor(new SimpleSpanProcessor(collectorExporter));
diff --git a/examples/stackdriver-trace/.gitignore b/examples/stackdriver-trace/.gitignore
new file mode 100644
index 0000000000..a5d657b71a
--- /dev/null
+++ b/examples/stackdriver-trace/.gitignore
@@ -0,0 +1 @@
+service_account_key.json
\ No newline at end of file
diff --git a/examples/stackdriver-trace/README.md b/examples/stackdriver-trace/README.md
new file mode 100644
index 0000000000..4a6e09dc7e
--- /dev/null
+++ b/examples/stackdriver-trace/README.md
@@ -0,0 +1,37 @@
+# Overview
+
+This example shows how to use [@opentelemetry/tracing](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-tracing) to instrument a simple Node.js application - e.g. a batch job - and export spans either to [Stackdriver Trace](https://cloud.google.com/trace/).
+
+## Installation
+
+```sh
+$ # from this directory
+$ npm install
+```
+
+## Authenticate
+
+If you are running in a GCP environment, the exporter will automatically authenticate as the service account of your environment. Please make sure that it has permission to access stackdriver trace.
+
+If you are not running in a GCP environment you will need to create a service account and save the service account key json in the root of this example named `service_account_key.json`. For more information, visit .
+
+## Run the Application
+
+```sh
+$ # from this directory
+$ npm start
+```
+
+## View traces
+
+https://console.cloud.google.com/traces/list?project=your-project-id
+
+
+
+## Useful links
+- For more information on OpenTelemetry, visit:
+- For more information on tracing, visit:
+
+## LICENSE
+
+Apache License 2.0
diff --git a/examples/stackdriver-trace/images/trace.png b/examples/stackdriver-trace/images/trace.png
new file mode 100644
index 0000000000..66af4af916
Binary files /dev/null and b/examples/stackdriver-trace/images/trace.png differ
diff --git a/examples/stackdriver-trace/index.js b/examples/stackdriver-trace/index.js
new file mode 100644
index 0000000000..809fc4e8f0
--- /dev/null
+++ b/examples/stackdriver-trace/index.js
@@ -0,0 +1,64 @@
+'use strict';
+
+const opentelemetry = require('@opentelemetry/core');
+const { BasicTracer, SimpleSpanProcessor } = require('@opentelemetry/tracing');
+const { CanonicalCode } = require('@opentelemetry/types');
+const { StackdriverTraceExporter } = require('@opentelemetry/exporter-stackdriver-trace');
+
+// Initialize an exporter
+const exporter = new StackdriverTraceExporter({
+ serviceName: 'basic-service',
+ logger: new opentelemetry.ConsoleLogger()
+});
+
+const tracer = new BasicTracer();
+
+// Configure span processor to send spans to the provided exporter
+tracer.addSpanProcessor(new SimpleSpanProcessor(exporter));
+
+// Initialize the OpenTelemetry APIs to use the BasicTracer bindings
+opentelemetry.initGlobalTracer(tracer);
+
+// Create a span. A span must be closed.
+const root = opentelemetry.getTracer().startSpan('main');
+const related = opentelemetry.getTracer().startSpan('related', {
+ links: [{ spanContext: root.context() }]
+});
+
+for (let i = 0; i < 10; i++) {
+ doWork(root);
+ doWork(related);
+}
+// Be sure to end the span.
+root.setStatus({
+ code: CanonicalCode.UNKNOWN
+})
+root.end();
+related.end();
+
+// flush and close the connection.
+exporter.shutdown();
+
+function doWork(parent) {
+ // Start another span. In this example, the main method already started a
+ // span, so that'll be the parent span, and this will be a child span.
+ const span = opentelemetry.getTracer().startSpan('doWork', {
+ parent: parent
+ });
+
+ // simulate some random work.
+ const work = Math.floor(Math.random() * 40000000);
+ for (let i = 0; i <= work; i++) { }
+
+ if (work % 2 === 1) {
+ span.setStatus({
+ code: CanonicalCode.UNKNOWN
+ })
+ }
+
+ // Set attributes to the span.
+ span.setAttribute('key', 'value');
+
+ // Annotate our span to capture metadata about our operation
+ span.addEvent('invoking doWork').end();
+}
diff --git a/examples/stackdriver-trace/package.json b/examples/stackdriver-trace/package.json
new file mode 100644
index 0000000000..d464707d56
--- /dev/null
+++ b/examples/stackdriver-trace/package.json
@@ -0,0 +1,37 @@
+{
+ "name": "example-stackdriver-trace",
+ "private": true,
+ "version": "0.3.1",
+ "description": "Example of using @opentelemetry/exporter-stackdriver-trace in Node.js",
+ "main": "index.js",
+ "scripts": {
+ "start": "cross-env GOOGLE_APPLICATION_CREDENTIALS=service_account_key.json node ./index.js"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+ssh://git@github.com/open-telemetry/opentelemetry-js.git"
+ },
+ "keywords": [
+ "opentelemetry",
+ "http",
+ "tracing"
+ ],
+ "engines": {
+ "node": ">=8"
+ },
+ "author": "OpenTelemetry Authors",
+ "license": "Apache-2.0",
+ "bugs": {
+ "url": "https://github.com/open-telemetry/opentelemetry-js/issues"
+ },
+ "dependencies": {
+ "@opentelemetry/core": "^0.3.1",
+ "@opentelemetry/exporter-stackdriver-trace": "^0.3.1",
+ "@opentelemetry/tracing": "^0.3.1",
+ "@opentelemetry/types": "^0.3.1"
+ },
+ "homepage": "https://github.com/open-telemetry/opentelemetry-js#readme",
+ "devDependencies": {
+ "cross-env": "^6.0.0"
+ }
+}
diff --git a/packages/opentelemetry-exporter-stackdriver-trace/.npmignore b/packages/opentelemetry-exporter-stackdriver-trace/.npmignore
new file mode 100644
index 0000000000..9505ba9450
--- /dev/null
+++ b/packages/opentelemetry-exporter-stackdriver-trace/.npmignore
@@ -0,0 +1,4 @@
+/bin
+/coverage
+/doc
+/test
diff --git a/packages/opentelemetry-exporter-stackdriver-trace/LICENSE b/packages/opentelemetry-exporter-stackdriver-trace/LICENSE
new file mode 100644
index 0000000000..261eeb9e9f
--- /dev/null
+++ b/packages/opentelemetry-exporter-stackdriver-trace/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-stackdriver-trace/README.md b/packages/opentelemetry-exporter-stackdriver-trace/README.md
new file mode 100644
index 0000000000..f25d5d9329
--- /dev/null
+++ b/packages/opentelemetry-exporter-stackdriver-trace/README.md
@@ -0,0 +1,92 @@
+# OpenTelemetry Stackdriver Trace Exporter
+[![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]
+
+OpenTelemetry Stackdriver Trace Exporter allows the user to send collected traces to Stackdriver.
+
+[Stackdriver Trace](https://cloud.google.com/trace) is a distributed tracing system. It helps gather timing data needed to troubleshoot latency problems in microservice architectures. It manages both the collection and lookup of this data.
+
+## Setup
+
+Stackdriver Trace is a managed service provided by Google Cloud Platform.
+
+### Installation
+
+Install the npm package `@opentelemetry/exporter-stackdriver-trace`
+
+```shell
+$ npm install @opentelemetry/exporter-stackdriver-trace
+```
+
+## Usage
+
+Install the exporter on your application, register the exporter, and start tracing. If you are running in a GCP environment, the exporter will automatically authenticate using the environment's service account. If not, you will need to follow the instructions in [Authentication](#Authentication).
+
+```js
+const { StackdriverTraceExporter } = require('@opentelemetry/exporter-stackdriver-trace');
+
+const exporter = new StackdriverTraceExporter({
+ // If you are not in a GCP environment, you will need to provide your
+ // service account key here. See the Authentication section below.
+});
+
+tracer.addSpanProcessor(new BatchSpanProcessor(exporter));
+```
+
+You can use the built-in `SimpleSpanProcessor` or `BatchSpanProcessor`, or write your own.
+
+- [SimpleSpanProcessor](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/sdk-tracing.md#simple-processor): The implementation of `SpanProcessor` that passes ended span directly to the configured `SpanExporter`.
+- [BatchSpanProcessor](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/sdk-tracing.md#batching-processor): The implementation of the `SpanProcessor` that batches ended spans and pushes them to the configured `SpanExporter`. It is recommended to use this `SpanProcessor` for better performance and optimization.
+
+## Viewing your traces
+
+Visit the google cloud trace UI at https://console.cloud.google.com/traces/list?project=your-gcp-project-id
+
+
+## Authentication
+
+The Stackdriver Trace exporter supports authentication using service accounts. These can either be defined in a keyfile (usually called `service_account_key.json` or similar), or by the environment. If your application runs in a GCP environment, such as Compute Engine, you don't need to provide any application credentials. The client library will find the credentials by itself. For more information, go to .
+
+### Service account key
+
+If you are not running in a GCP environment, you will need to give the service account credentials to the exporter.
+
+```js
+const { StackdriverTraceExporter } = require('@opentelemetry/exporter-stackdriver-trace');
+
+const exporter = new StackdriverTraceExporter({
+ /** option 1. provide a service account key json */
+ keyFile: './service_account_key.json',
+ keyFileName: './service_account_key.json',
+
+ /** option 2. provide credentials directly */
+ credentials: {
+ client_email: string,
+ private_key: string,
+ },
+});
+```
+
+## Useful links
+- For more information on OpenTelemetry, visit:
+- For more about OpenTelemetry JavaScript:
+- Learn more about Stackdriver Trace at https://cloud.google.com/trace
+- 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-stackdriver-trace
+[dependencies-url]: https://david-dm.org/open-telemetry/opentelemetry-js?path=packages%2Fopentelemetry-exporter-stackdriver-trace
+[devDependencies-image]: https://david-dm.org/open-telemetry/opentelemetry-js/dev-status.svg?path=packages/opentelemetry-exporter-stackdriver-trace
+[devDependencies-url]: https://david-dm.org/open-telemetry/opentelemetry-js?path=packages%2Fopentelemetry-exporter-stackdriver-trace&type=dev
+[npm-url]: https://www.npmjs.com/package/@opentelemetry/exporter-stackdriver-trace
+[npm-img]: https://badge.fury.io/js/%40opentelemetry%2Fexporter-stackdriver-trace.svg
diff --git a/packages/opentelemetry-exporter-stackdriver-trace/package.json b/packages/opentelemetry-exporter-stackdriver-trace/package.json
new file mode 100644
index 0000000000..a6d0e2b5d9
--- /dev/null
+++ b/packages/opentelemetry-exporter-stackdriver-trace/package.json
@@ -0,0 +1,69 @@
+{
+ "name": "@opentelemetry/exporter-stackdriver-trace",
+ "version": "0.3.1",
+ "description": "OpenTelemetry StackDriver Trace Exporter allows the user to send collected traces to StackDriver Trace.",
+ "main": "build/src/index.js",
+ "types": "build/src/index.d.ts",
+ "repository": "open-telemetry/opentelemetry-js",
+ "scripts": {
+ "check": "gts check",
+ "clean": "rimraf build/*",
+ "codecov": "nyc report --reporter=json && codecov -f coverage/*.json -p ../../",
+ "version:update": "node ../../scripts/version-update.js",
+ "compile": "npm run version:update && tsc -p .",
+ "fix": "gts fix",
+ "precompile": "tsc --version",
+ "prepare": "npm run compile",
+ "tdd": "yarn test -- --watch-extensions ts --watch",
+ "test": "nyc ts-mocha -p tsconfig.json 'test/**/*.ts'"
+ },
+ "keywords": [
+ "opentelemetry",
+ "nodejs",
+ "tracing",
+ "profiling",
+ "stackdriver",
+ "stackdriver-trace"
+ ],
+ "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": {
+ "@types/mocha": "^5.2.7",
+ "@types/nock": "^11.1.0",
+ "@types/node": "^12.6.9",
+ "@types/sinon": "^7.5.1",
+ "codecov": "^3.6.1",
+ "gts": "^1.1.0",
+ "mocha": "^6.2.0",
+ "nock": "^11.7.0",
+ "nyc": "^14.1.1",
+ "rimraf": "^3.0.0",
+ "sinon": "^8.0.1",
+ "ts-mocha": "^6.0.0",
+ "ts-node": "^8.3.0",
+ "tslint-consistent-codestyle": "^1.15.1",
+ "tslint-microsoft-contrib": "^6.2.0",
+ "typescript": "3.7.2"
+ },
+ "dependencies": {
+ "@opentelemetry/base": "^0.3.1",
+ "@opentelemetry/core": "^0.3.1",
+ "@opentelemetry/tracing": "^0.3.1",
+ "@opentelemetry/types": "^0.3.1",
+ "google-auth-library": "^5.7.0",
+ "googleapis": "^46.0.0"
+ }
+}
diff --git a/packages/opentelemetry-exporter-stackdriver-trace/src/external-types.ts b/packages/opentelemetry-exporter-stackdriver-trace/src/external-types.ts
new file mode 100644
index 0000000000..7e92251f80
--- /dev/null
+++ b/packages/opentelemetry-exporter-stackdriver-trace/src/external-types.ts
@@ -0,0 +1,51 @@
+/*!
+ * 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 { Logger } from '@opentelemetry/types';
+
+export interface StackdriverExporterOptions {
+ /**
+ * Google Cloud Platform project ID where your traces will be stored.
+ * This is optional and will be inferred from your authentication
+ * credentials or from the GCP environment when not specified.
+ */
+ projectId?: string;
+ /**
+ * Object implementing the logger interface
+ */
+ logger?: Logger;
+ /**
+ * Path to a .json, .pem, or .p12 key file. This is optional and
+ * authentication keys will be inferred from the environment if you
+ * are running on GCP.
+ */
+ keyFilename?: string;
+ /**
+ * Path to a .json, .pem, or .p12 key file. This is optional and
+ * authentication keys will be inferred from the environment if you
+ * are running on GCP.
+ */
+ keyFile?: string;
+ /**
+ * Object containing client_email and private_key properties
+ */
+ credentials?: Credentials;
+}
+
+export interface Credentials {
+ client_email?: string;
+ private_key?: string;
+}
diff --git a/packages/opentelemetry-exporter-stackdriver-trace/src/index.ts b/packages/opentelemetry-exporter-stackdriver-trace/src/index.ts
new file mode 100644
index 0000000000..2563b8b901
--- /dev/null
+++ b/packages/opentelemetry-exporter-stackdriver-trace/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 './external-types';
+export * from './trace';
diff --git a/packages/opentelemetry-exporter-stackdriver-trace/src/trace.ts b/packages/opentelemetry-exporter-stackdriver-trace/src/trace.ts
new file mode 100644
index 0000000000..a49c688041
--- /dev/null
+++ b/packages/opentelemetry-exporter-stackdriver-trace/src/trace.ts
@@ -0,0 +1,145 @@
+/*!
+ * 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 { Logger } from '@opentelemetry/types';
+import { GoogleAuth } from 'google-auth-library';
+import { google } from 'googleapis';
+import { StackdriverExporterOptions } from './external-types';
+import { getReadableSpanTransformer } from './transform';
+import { Span, SpansWithCredentials } from './types';
+
+const OT_REQUEST_HEADER = 'x-opentelemetry-outgoing-request';
+google.options({ headers: { [OT_REQUEST_HEADER]: 1 } });
+
+/**
+ * Format and sends span information to StackDriver Trace.
+ */
+export class StackdriverTraceExporter implements SpanExporter {
+ private _projectId: string | void | Promise;
+ private readonly _logger: Logger;
+ private readonly _auth: GoogleAuth;
+
+ private static readonly _cloudTrace = google.cloudtrace('v2');
+
+ constructor(options: StackdriverExporterOptions) {
+ this._logger = options.logger || new NoopLogger();
+
+ this._auth = new GoogleAuth({
+ credentials: options.credentials,
+ keyFile: options.keyFile,
+ keyFilename: options.keyFilename,
+ projectId: options.projectId,
+ scopes: ['https://www.googleapis.com/auth/cloud-platform'],
+ });
+
+ // Start this async process as early as possible. It will be
+ // awaited on the first export because constructors are synchronous
+ this._projectId = this._auth.getProjectId().catch(err => {
+ this._logger.error(err);
+ });
+ }
+
+ /**
+ * Publishes a list of spans to Stackdriver.
+ * @param spans The list of spans to transmit to Stackdriver
+ */
+ async export(
+ spans: ReadableSpan[],
+ resultCallback: (result: ExportResult) => void
+ ): Promise {
+ if (this._projectId instanceof Promise) {
+ this._projectId = await this._projectId;
+ }
+
+ if (!this._projectId) {
+ return resultCallback(ExportResult.FAILED_NOT_RETRYABLE);
+ }
+
+ this._logger.debug('StackDriver Trace export');
+ const authorizedSpans = await this._authorize(
+ spans.map(getReadableSpanTransformer(this._projectId))
+ );
+
+ if (!authorizedSpans) {
+ return resultCallback(ExportResult.FAILED_NOT_RETRYABLE);
+ }
+ this._logger.debug('StackDriver Trace got span authorization');
+
+ try {
+ await this._batchWriteSpans(authorizedSpans);
+ resultCallback(ExportResult.SUCCESS);
+ } catch (err) {
+ this._logger.error(`Stackdriver Trace failed to export ${err}`);
+ resultCallback(ExportResult.FAILED_RETRYABLE);
+ }
+ }
+
+ shutdown(): void {}
+
+ /**
+ * Sends new spans to new or existing traces in the Stackdriver format to the
+ * service.
+ * @param spans
+ */
+ private _batchWriteSpans(spans: SpansWithCredentials) {
+ this._logger.debug('StackDriver Trace batch writing traces');
+
+ return new Promise((resolve, reject) => {
+ // @todo Consider to use gRPC call (BatchWriteSpansRequest) for sending
+ // data to backend :
+ // https://cloud.google.com/trace/docs/reference/v2/rpc/google.devtools.
+ // cloudtrace.v2#google.devtools.cloudtrace.v2.TraceService
+ StackdriverTraceExporter._cloudTrace.projects.traces.batchWrite(
+ spans,
+ (err: Error | null) => {
+ if (err) {
+ err.message = `batchWriteSpans error: ${err.message}`;
+ this._logger.error(err.message);
+ reject(err);
+ } else {
+ const successMsg = 'batchWriteSpans successfully';
+ this._logger.debug(successMsg);
+ resolve(successMsg);
+ }
+ }
+ );
+ });
+ }
+
+ /**
+ * Gets the Google Application Credentials from the environment variables,
+ * authenticates the client and calls a method to send the spans data.
+ * @param stackdriverSpans The spans to export
+ */
+ private async _authorize(
+ spans: Span[]
+ ): Promise {
+ try {
+ return {
+ name: `projects/${this._projectId}`,
+ resource: { spans },
+ auth: await this._auth.getClient(),
+ };
+ } catch (err) {
+ err.message = `authorize error: ${err.message}`;
+ this._logger.error(err.message);
+ return null;
+ }
+ }
+}
diff --git a/packages/opentelemetry-exporter-stackdriver-trace/src/transform.ts b/packages/opentelemetry-exporter-stackdriver-trace/src/transform.ts
new file mode 100644
index 0000000000..ae17b053fd
--- /dev/null
+++ b/packages/opentelemetry-exporter-stackdriver-trace/src/transform.ts
@@ -0,0 +1,130 @@
+/*!
+ * 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 {
+ hrTimeToTimeStamp,
+ VERSION as CORE_VERSION,
+} from '@opentelemetry/core';
+import { ReadableSpan } from '@opentelemetry/tracing';
+import * as ot from '@opentelemetry/types';
+import {
+ AttributeMap,
+ Attributes,
+ AttributeValue,
+ Link,
+ LinkType,
+ Span,
+ TruncatableString,
+} from './types';
+import { VERSION } from './version';
+
+const AGENT_LABEL_KEY = 'g.co/agent';
+const AGENT_LABEL_VALUE = `opentelemetry-js [${CORE_VERSION}]; stackdriver-trace-exporter [${VERSION}]`;
+
+export function getReadableSpanTransformer(
+ projectId: string
+): (span: ReadableSpan) => Span {
+ return span => {
+ const attributes = transformAttributes(span.attributes, {
+ project_id: projectId,
+ [AGENT_LABEL_KEY]: AGENT_LABEL_VALUE,
+ });
+
+ const out: Span = {
+ attributes,
+ displayName: stringToTruncatableString(span.name),
+ links: {
+ link: span.links.map(transformLink),
+ },
+ endTime: hrTimeToTimeStamp(span.endTime),
+ startTime: hrTimeToTimeStamp(span.startTime),
+ name: `projects/${projectId}/traces/${span.spanContext.traceId}/spans/${span.spanContext.spanId}`,
+ spanId: span.spanContext.spanId,
+ sameProcessAsParentSpan: !span.spanContext.isRemote,
+ status: span.status,
+ timeEvents: {
+ timeEvent: span.events.map(e => ({
+ time: hrTimeToTimeStamp(e.time),
+ annotation: {
+ attributes: transformAttributes(e.attributes),
+ description: stringToTruncatableString(e.name),
+ },
+ })),
+ },
+ };
+
+ if (span.parentSpanId) {
+ out.parentSpanId = span.parentSpanId;
+ }
+
+ return out;
+ };
+}
+
+function transformLink(link: ot.Link): Link {
+ return {
+ attributes: transformAttributes(link.attributes),
+ spanId: link.spanContext.spanId,
+ traceId: link.spanContext.traceId,
+ type: LinkType.UNSPECIFIED,
+ };
+}
+
+function transformAttributes(
+ requestAttributes: ot.Attributes = {},
+ serviceAttributes: ot.Attributes = {}
+): Attributes {
+ const attributes = Object.assign({}, requestAttributes, serviceAttributes);
+ const attributeMap = transformAttributeValues(attributes);
+ return {
+ attributeMap: attributeMap,
+ // @todo get dropped attribute count from sdk ReadableSpan
+ droppedAttributesCount:
+ Object.keys(attributes).length - Object.keys(attributeMap).length,
+ };
+}
+
+function transformAttributeValues(attributes: ot.Attributes): AttributeMap {
+ const out: AttributeMap = {};
+ for (const [key, value] of Object.entries(attributes)) {
+ switch (typeof value) {
+ case 'number':
+ case 'boolean':
+ case 'string':
+ out[key] = valueToAttributeValue(value);
+ break;
+ }
+ }
+ return out;
+}
+
+function stringToTruncatableString(value: string): TruncatableString {
+ return { value };
+}
+
+function valueToAttributeValue(
+ value: string | number | boolean
+): AttributeValue {
+ switch (typeof value) {
+ case 'number':
+ // TODO: Consider to change to doubleValue when available in V2 API.
+ return { intValue: String(Math.round(value)) };
+ case 'boolean':
+ return { boolValue: value };
+ case 'string':
+ return { stringValue: stringToTruncatableString(value) };
+ }
+}
diff --git a/packages/opentelemetry-exporter-stackdriver-trace/src/types.ts b/packages/opentelemetry-exporter-stackdriver-trace/src/types.ts
new file mode 100644
index 0000000000..91dda1bd05
--- /dev/null
+++ b/packages/opentelemetry-exporter-stackdriver-trace/src/types.ts
@@ -0,0 +1,142 @@
+/*!
+ * 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 { Compute, JWT, OAuth2Client } from 'google-auth-library';
+
+export interface Span {
+ name?: string;
+ spanId?: string;
+ parentSpanId?: string;
+ displayName?: TruncatableString;
+ startTime?: string;
+ endTime?: string;
+ attributes?: Attributes;
+ // This property is currently unused. keeping it here as it is part
+ // of the stack driver trace types and may be used in the future
+ stackTrace?: StackTrace;
+ timeEvents?: TimeEvents;
+ links?: Links;
+ status?: Status;
+ sameProcessAsParentSpan?: boolean;
+ childSpanCount?: number;
+}
+
+export interface AttributeMap {
+ [key: string]: AttributeValue;
+}
+
+export interface Attributes {
+ attributeMap?: AttributeMap;
+ droppedAttributesCount?: number;
+}
+
+export interface AttributeValue {
+ boolValue?: boolean;
+ intValue?: string;
+ stringValue?: TruncatableString;
+}
+
+export interface TruncatableString {
+ value?: string;
+ truncatedByteCount?: number;
+}
+
+export interface Links {
+ droppedLinksCount?: number;
+ link?: Link[];
+}
+
+export interface Link {
+ attributes?: Attributes;
+ spanId?: string;
+ traceId?: string;
+ type?: LinkType;
+}
+
+export interface StackTrace {
+ stackFrames?: StackFrames;
+ stackTraceHashId?: string;
+}
+
+export interface StackFrames {
+ droppedFramesCount?: number;
+ frame?: StackFrame[];
+}
+
+export interface StackFrame {
+ columnNumber?: string;
+ fileName?: TruncatableString;
+ functionName?: TruncatableString;
+ lineNumber?: string;
+ loadModule?: Module;
+ originalFunctionName?: TruncatableString;
+ sourceVersion?: TruncatableString;
+}
+
+export interface Module {
+ buildId?: TruncatableString;
+ module?: TruncatableString;
+}
+
+export interface Status {
+ /** gRPC status code */
+ code?: number;
+ message?: string;
+}
+
+export interface TimeEvents {
+ droppedAnnotationsCount?: number;
+ droppedMessageEventsCount?: number;
+ timeEvent?: TimeEvent[];
+}
+
+export interface TimeEvent {
+ annotation?: Annotation;
+ time?: string;
+ // This property is currently unused. keeping it here as it is part
+ // of the stack driver trace types and may be used in the future
+ messageEvent?: MessageEvent;
+}
+
+export interface Annotation {
+ attributes?: Attributes;
+ description?: TruncatableString;
+}
+
+export interface MessageEvent {
+ id?: string;
+ type?: Type;
+ compressedSizeBytes?: string;
+ uncompressedSizeBytes?: string;
+}
+
+export enum Type {
+ TYPE_UNSPECIFIED = 0,
+ SENT = 1,
+ RECEIVED = 2,
+}
+
+export enum LinkType {
+ UNSPECIFIED = 0,
+ CHILD_LINKED_SPAN = 1,
+ PARENT_LINKED_SPAN = 2,
+}
+
+export interface SpansWithCredentials {
+ name: string;
+ resource: { spans: {} };
+ auth: JWT | OAuth2Client | Compute;
+}
diff --git a/packages/opentelemetry-exporter-stackdriver-trace/src/version.ts b/packages/opentelemetry-exporter-stackdriver-trace/src/version.ts
new file mode 100644
index 0000000000..60ca3ab548
--- /dev/null
+++ b/packages/opentelemetry-exporter-stackdriver-trace/src/version.ts
@@ -0,0 +1,18 @@
+/*!
+ * Copyright 2019, OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// this is autogenerated file, see scripts/version-update.js
+export const VERSION = '0.3.1';
diff --git a/packages/opentelemetry-exporter-stackdriver-trace/test/exporter.test.ts b/packages/opentelemetry-exporter-stackdriver-trace/test/exporter.test.ts
new file mode 100644
index 0000000000..cfacb9dc02
--- /dev/null
+++ b/packages/opentelemetry-exporter-stackdriver-trace/test/exporter.test.ts
@@ -0,0 +1,227 @@
+/*!
+ * 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 { ConsoleLogger, LogLevel } from '@opentelemetry/core';
+import { ReadableSpan } from '@opentelemetry/tracing';
+import * as types from '@opentelemetry/types';
+import * as assert from 'assert';
+import * as nock from 'nock';
+import * as sinon from 'sinon';
+import { StackdriverTraceExporter } from '../src';
+
+describe('Stackdriver Trace Exporter', () => {
+ beforeEach(() => {
+ process.env.GCLOUD_PROJECT = 'not-real';
+ nock.disableNetConnect();
+ });
+
+ describe('constructor', () => {
+ it('should construct an exporter', async () => {
+ const exporter = new StackdriverTraceExporter({
+ credentials: {
+ client_email: 'noreply@fake.example.com',
+ private_key: 'this is a key',
+ },
+ });
+
+ assert(exporter);
+ return (exporter['_projectId'] as Promise).then(id => {
+ assert.deepStrictEqual(id, 'not-real');
+ });
+ });
+ });
+
+ describe('export', () => {
+ let exporter: StackdriverTraceExporter;
+ let logger: ConsoleLogger;
+ let batchWrite: sinon.SinonSpy<[any, any], any>;
+ let debug: sinon.SinonSpy;
+ let info: sinon.SinonSpy;
+ let warn: sinon.SinonSpy;
+ let error: sinon.SinonSpy;
+ let getClientShouldFail: boolean;
+ let batchWriteShouldFail: boolean;
+
+ beforeEach(() => {
+ getClientShouldFail = false;
+ batchWriteShouldFail = false;
+ logger = new ConsoleLogger(LogLevel.ERROR);
+ exporter = new StackdriverTraceExporter({
+ logger,
+ });
+
+ batchWrite = sinon.spy(
+ (spans: any, callback: (err: Error | null) => void): any => {
+ if (batchWriteShouldFail) {
+ callback(new Error('fail'));
+ } else {
+ callback(null);
+ }
+ }
+ );
+
+ sinon.replace(
+ StackdriverTraceExporter['_cloudTrace'].projects.traces,
+ 'batchWrite',
+ batchWrite as any
+ );
+
+ sinon.replace(exporter['_auth'], 'getClient', () => {
+ if (getClientShouldFail) {
+ throw new Error('fail');
+ }
+ return {} as any;
+ });
+
+ debug = sinon.spy();
+ info = sinon.spy();
+ warn = sinon.spy();
+ error = sinon.spy();
+ sinon.replace(logger, 'debug', debug);
+ sinon.replace(logger, 'info', info);
+ sinon.replace(logger, 'warn', warn);
+ sinon.replace(logger, 'error', error);
+ });
+
+ afterEach(() => {
+ nock.restore();
+ sinon.restore();
+ });
+
+ it('should export spans', async () => {
+ const readableSpan: ReadableSpan = {
+ attributes: {},
+ duration: [32, 800000000],
+ startTime: [1566156729, 709],
+ endTime: [1566156731, 709],
+ events: [],
+ kind: types.SpanKind.CLIENT,
+ links: [],
+ name: 'my-span',
+ spanContext: {
+ traceId: 'd4cda95b652f4a1592b449d5929fda1b',
+ spanId: '6e0c63257de34c92',
+ isRemote: true,
+ },
+ status: { code: types.CanonicalCode.OK },
+ };
+
+ const result = await new Promise((resolve, reject) => {
+ exporter.export([readableSpan], result => {
+ resolve(result);
+ });
+ });
+
+ assert.deepStrictEqual(
+ batchWrite.getCall(0).args[0].resource.spans[0].displayName.value,
+ 'my-span'
+ );
+
+ assert.deepStrictEqual(result, ExportResult.SUCCESS);
+ });
+
+ it('should return not retryable if authorization fails', async () => {
+ const readableSpan: ReadableSpan = {
+ attributes: {},
+ duration: [32, 800000000],
+ startTime: [1566156729, 709],
+ endTime: [1566156731, 709],
+ events: [],
+ kind: types.SpanKind.CLIENT,
+ links: [],
+ name: 'my-span',
+ spanContext: {
+ traceId: 'd4cda95b652f4a1592b449d5929fda1b',
+ spanId: '6e0c63257de34c92',
+ isRemote: true,
+ },
+ status: { code: types.CanonicalCode.OK },
+ };
+
+ getClientShouldFail = true;
+
+ const result = await new Promise((resolve, reject) => {
+ exporter.export([readableSpan], result => {
+ resolve(result);
+ });
+ });
+
+ assert(batchWrite.notCalled);
+ assert(error.getCall(0).args[0].match(/authorize error: fail/));
+ assert.deepStrictEqual(result, ExportResult.FAILED_NOT_RETRYABLE);
+ });
+
+ it('should return retryable if span writing fails', async () => {
+ const readableSpan: ReadableSpan = {
+ attributes: {},
+ duration: [32, 800000000],
+ startTime: [1566156729, 709],
+ endTime: [1566156731, 709],
+ events: [],
+ kind: types.SpanKind.CLIENT,
+ links: [],
+ name: 'my-span',
+ spanContext: {
+ traceId: 'd4cda95b652f4a1592b449d5929fda1b',
+ spanId: '6e0c63257de34c92',
+ isRemote: true,
+ },
+ status: { code: types.CanonicalCode.OK },
+ };
+
+ batchWriteShouldFail = true;
+
+ const result = await new Promise((resolve, reject) => {
+ exporter.export([readableSpan], result => {
+ resolve(result);
+ });
+ });
+
+ assert.deepStrictEqual(result, ExportResult.FAILED_RETRYABLE);
+ });
+
+ it('should return not retryable if project id missing', async () => {
+ const readableSpan: ReadableSpan = {
+ attributes: {},
+ duration: [32, 800000000],
+ startTime: [1566156729, 709],
+ endTime: [1566156731, 709],
+ events: [],
+ kind: types.SpanKind.CLIENT,
+ links: [],
+ name: 'my-span',
+ spanContext: {
+ traceId: 'd4cda95b652f4a1592b449d5929fda1b',
+ spanId: '6e0c63257de34c92',
+ isRemote: true,
+ },
+ status: { code: types.CanonicalCode.OK },
+ };
+
+ await exporter['_projectId'];
+ exporter['_projectId'] = undefined;
+
+ const result = await new Promise((resolve, reject) => {
+ exporter.export([readableSpan], result => {
+ resolve(result);
+ });
+ });
+
+ assert.deepStrictEqual(result, ExportResult.FAILED_NOT_RETRYABLE);
+ });
+ });
+});
diff --git a/packages/opentelemetry-exporter-stackdriver-trace/test/transform.test.ts b/packages/opentelemetry-exporter-stackdriver-trace/test/transform.test.ts
new file mode 100644
index 0000000000..1a35b76d8c
--- /dev/null
+++ b/packages/opentelemetry-exporter-stackdriver-trace/test/transform.test.ts
@@ -0,0 +1,255 @@
+/*!
+ * 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 { VERSION as CORE_VERSION } from '@opentelemetry/core';
+import { ReadableSpan } from '@opentelemetry/tracing';
+import * as types from '@opentelemetry/types';
+import * as assert from 'assert';
+import { getReadableSpanTransformer } from '../src/transform';
+import { LinkType, Span } from '../src/types';
+import { VERSION } from '../src/version';
+
+describe('transform', () => {
+ let readableSpan: ReadableSpan;
+ let transformer: (readableSpan: ReadableSpan) => Span;
+ let spanContext: types.SpanContext;
+
+ beforeEach(() => {
+ spanContext = {
+ traceId: 'd4cda95b652f4a1592b449d5929fda1b',
+ spanId: '6e0c63257de34c92',
+ isRemote: true,
+ };
+
+ transformer = getReadableSpanTransformer('project-id');
+
+ readableSpan = {
+ attributes: {},
+ duration: [32, 800000000],
+ startTime: [1566156729, 709],
+ endTime: [1566156731, 709],
+ events: [],
+ kind: types.SpanKind.CLIENT,
+ links: [],
+ name: 'my-span',
+ spanContext,
+ status: { code: types.CanonicalCode.OK },
+ };
+ });
+
+ it('should transform spans', () => {
+ const result = transformer(readableSpan);
+
+ assert.deepStrictEqual(result, {
+ attributes: {
+ attributeMap: {
+ project_id: { stringValue: { value: 'project-id' } },
+ 'g.co/agent': {
+ stringValue: {
+ value: `opentelemetry-js [${CORE_VERSION}]; stackdriver-trace-exporter [${VERSION}]`,
+ },
+ },
+ },
+ droppedAttributesCount: 0,
+ },
+ displayName: { value: 'my-span' },
+ links: { link: [] },
+ endTime: '2019-08-18T19:32:11.000000709Z',
+ startTime: '2019-08-18T19:32:09.000000709Z',
+ name:
+ 'projects/project-id/traces/d4cda95b652f4a1592b449d5929fda1b/spans/6e0c63257de34c92',
+ spanId: '6e0c63257de34c92',
+ status: { code: 0 },
+ timeEvents: { timeEvent: [] },
+ sameProcessAsParentSpan: false,
+ });
+ });
+
+ it('should transform spans with parent', () => {
+ (readableSpan as any).parentSpanId = '3e0c63257de34c92';
+ const result = transformer(readableSpan);
+ assert.deepStrictEqual(result.parentSpanId, '3e0c63257de34c92');
+ });
+
+ it('should transform spans without parent', () => {
+ const result = transformer(readableSpan);
+ assert.deepStrictEqual(result.parentSpanId, undefined);
+ });
+
+ it('should transform remote spans', () => {
+ const remote = transformer(readableSpan);
+ assert.deepStrictEqual(remote.sameProcessAsParentSpan, false);
+ });
+
+ it('should transform local spans', () => {
+ readableSpan.spanContext.isRemote = false;
+ const local = transformer(readableSpan);
+ assert.deepStrictEqual(local.sameProcessAsParentSpan, true);
+ });
+
+ it('should transform attributes', () => {
+ readableSpan.attributes.testBool = true;
+ readableSpan.attributes.testInt = 3;
+ readableSpan.attributes.testString = 'str';
+
+ const result = transformer(readableSpan);
+
+ assert.deepStrictEqual(result.attributes!.attributeMap!.testBool, {
+ boolValue: true,
+ });
+ assert.deepStrictEqual(result.attributes!.attributeMap!.testInt, {
+ intValue: '3',
+ });
+ assert.deepStrictEqual(result.attributes!.attributeMap!.testString, {
+ stringValue: { value: 'str' },
+ });
+ assert.deepStrictEqual(result.attributes!.droppedAttributesCount, 0);
+ });
+
+ it('should drop unknown attribute types', () => {
+ readableSpan.attributes.testUnknownType = { message: 'dropped' };
+ const result = transformer(readableSpan);
+ assert.deepStrictEqual(result.attributes!.droppedAttributesCount, 1);
+ assert.deepStrictEqual(
+ Object.keys(result.attributes!.attributeMap!).length,
+ 2
+ );
+ });
+
+ it('should transform links', () => {
+ readableSpan.links.push({
+ spanContext: {
+ traceId: 'a4cda95b652f4a1592b449d5929fda1b',
+ spanId: '3e0c63257de34c92',
+ isRemote: true,
+ traceFlags: types.TraceFlags.SAMPLED,
+ },
+ });
+
+ const result = transformer(readableSpan);
+
+ assert.deepStrictEqual(result.links, {
+ link: [
+ {
+ attributes: {
+ attributeMap: {},
+ droppedAttributesCount: 0,
+ },
+ traceId: 'a4cda95b652f4a1592b449d5929fda1b',
+ spanId: '3e0c63257de34c92',
+ type: LinkType.UNSPECIFIED,
+ },
+ ],
+ });
+ });
+
+ it('should transform links with attributes', () => {
+ readableSpan.links.push({
+ spanContext: {
+ traceId: 'a4cda95b652f4a1592b449d5929fda1b',
+ spanId: '3e0c63257de34c92',
+ isRemote: true,
+ traceFlags: types.TraceFlags.SAMPLED,
+ },
+ attributes: {
+ testAttr: 'value',
+ droppedAttr: {},
+ },
+ });
+
+ const result = transformer(readableSpan);
+
+ assert.deepStrictEqual(result.links, {
+ link: [
+ {
+ attributes: {
+ attributeMap: {
+ testAttr: {
+ stringValue: {
+ value: 'value',
+ },
+ },
+ },
+ droppedAttributesCount: 1,
+ },
+ traceId: 'a4cda95b652f4a1592b449d5929fda1b',
+ spanId: '3e0c63257de34c92',
+ type: LinkType.UNSPECIFIED,
+ },
+ ],
+ });
+ });
+
+ it('should transform events', () => {
+ readableSpan.events.push({
+ name: 'something happened',
+ time: [1566156729, 809],
+ });
+
+ const result = transformer(readableSpan);
+
+ assert.deepStrictEqual(result.timeEvents, {
+ timeEvent: [
+ {
+ annotation: {
+ attributes: {
+ attributeMap: {},
+ droppedAttributesCount: 0,
+ },
+ description: {
+ value: 'something happened',
+ },
+ },
+ time: '2019-08-18T19:32:09.000000809Z',
+ },
+ ],
+ });
+ });
+
+ it('should transform events with attributes', () => {
+ readableSpan.events.push({
+ name: 'something happened',
+ attributes: {
+ error: true,
+ dropped: {},
+ },
+ time: [1566156729, 809],
+ });
+
+ const result = transformer(readableSpan);
+
+ assert.deepStrictEqual(result.timeEvents, {
+ timeEvent: [
+ {
+ annotation: {
+ attributes: {
+ attributeMap: {
+ error: {
+ boolValue: true,
+ },
+ },
+ droppedAttributesCount: 1,
+ },
+ description: {
+ value: 'something happened',
+ },
+ },
+ time: '2019-08-18T19:32:09.000000809Z',
+ },
+ ],
+ });
+ });
+});
diff --git a/packages/opentelemetry-exporter-stackdriver-trace/tsconfig.json b/packages/opentelemetry-exporter-stackdriver-trace/tsconfig.json
new file mode 100644
index 0000000000..a2042cd68b
--- /dev/null
+++ b/packages/opentelemetry-exporter-stackdriver-trace/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "extends": "../tsconfig.base",
+ "compilerOptions": {
+ "rootDir": ".",
+ "outDir": "build"
+ },
+ "include": [
+ "src/**/*.ts",
+ "test/**/*.ts"
+ ]
+}
diff --git a/packages/opentelemetry-exporter-stackdriver-trace/tslint.json b/packages/opentelemetry-exporter-stackdriver-trace/tslint.json
new file mode 100644
index 0000000000..0710b135d0
--- /dev/null
+++ b/packages/opentelemetry-exporter-stackdriver-trace/tslint.json
@@ -0,0 +1,4 @@
+{
+ "rulesDirectory": ["node_modules/tslint-microsoft-contrib"],
+ "extends": ["../../tslint.base.js", "./node_modules/tslint-consistent-codestyle"]
+}