├── .babelrc ├── .gitignore ├── .travis.yml ├── Changelog.md ├── LICENSE ├── README.md ├── __mocks__ ├── zipkin-option.js └── zipkin.js ├── docs └── examples.md ├── e2e-tests └── react │ ├── .gitignore │ ├── README.md │ ├── cypress.json │ ├── cypress │ ├── fixtures │ │ └── example.json │ ├── integration │ │ └── basic_spec.js │ ├── support │ │ ├── commands.js │ │ └── index.js │ └── utils │ │ └── index.js │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json │ ├── src │ ├── App.css │ ├── App.js │ ├── index.css │ ├── index.js │ ├── logo.svg │ └── screens │ │ ├── Basic.js │ │ └── index.js │ └── yarn.lock ├── examples └── vanillajs │ ├── advanced │ ├── client.js │ ├── index.js │ └── server.js │ ├── basic │ └── index.js │ └── recorder.js ├── package-lock.json ├── package.json ├── renovate.json ├── scripts └── publish-website.sh ├── src ├── __tests__ │ ├── __snapshots__ │ │ ├── e2e.js.snap │ │ └── index.js.snap │ ├── e2e.js │ └── index.js └── index.js ├── website ├── .gitignore ├── core │ └── Footer.js ├── i18n │ └── en.json ├── package.json ├── pages │ └── en │ │ ├── help.js │ │ ├── index.js │ │ └── users.js ├── sidebars.json ├── siteConfig.js ├── static │ ├── css │ │ └── custom.css │ └── img │ │ ├── docusaurus.svg │ │ ├── favicon.png │ │ ├── favicon │ │ └── favicon.ico │ │ ├── opentracing.svg │ │ ├── openzipkin.jpg │ │ └── oss_logo.png └── yarn.lock └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "targets": { 7 | "node": "3" 8 | } 9 | } 10 | ] 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | node_modules 3 | lib/ 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | matrix: 2 | include: 3 | # Unit Tests Node 8 4 | - language: node_js 5 | env: 6 | - NAME='Unit Tests' 7 | node_js: 8 8 | install: 9 | - yarn install 10 | script: 11 | - yarn test 12 | after_script: 13 | - 'cat coverage/lcov.info | ./node_modules/.bin/coveralls' 14 | # E2E Web 15 | - services: 16 | - docker 17 | env: 18 | - CI=false 19 | - NODE_VERSION="8.7" 20 | install: 21 | - yarn install 22 | - cd e2e-tests/react 23 | - yarn install 24 | before_script: 25 | - yarn run build 26 | - docker run -d -p 9411:9411 openzipkin/zipkin 27 | - ./node_modules/.bin/serve -p 3000 -s build & SERVER_PID=$! 28 | script: 29 | - yarn test 30 | after_script: 31 | - kill -9 $SERVER_PID 32 | 33 | # Website 34 | - language: node_js 35 | env: 36 | - NAME='Website' 37 | node_js: 8 38 | script: 39 | - scripts/publish-website.sh 40 | 41 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## NEXT 4 | 5 | * Fix sampler option. [#87](https://github.com/DanielMSchmidt/zipkin-javascript-opentracing/pull/87) 6 | 7 | ## 2.0.0 8 | 9 | * [Change carrier formats to be openzipkin ones](https://github.com/DanielMSchmidt/zipkin-javascript-opentracing/pull/72) 10 | 11 | ## 1.4.0 12 | 13 | * Add support for local spans through workaround. Waiting for 14 | [zipkin-js#156](https://github.com/openzipkin/zipkin-js/pull/156) to be 15 | merged. 16 | 17 | ## 1.3.0 18 | 19 | * Update of `peerDependencies`, so that zipkin v2 schema is supported 20 | * Fix babel transpilation directory 21 | 22 | ## 1.2.0 23 | 24 | Issues arised with [`react-scripts` throwing an error that this library is not 25 | transpiled](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#npm-run-build-fails-to-minify). 26 | To fix it we introduced babel and now ship with a transpiled `lib/` folder 27 | 28 | ## 1.1.0 29 | 30 | * [PR#10](https://github.com/costacruise/zipkin-javascript-opentracing/pull/10) 31 | adding support for a more simplified setup: 32 | 33 | ```js 34 | const ZipkinOpentracing = require("zipkin-javascript-opentracing"); 35 | 36 | const tracer = new ZipkinOpentracing({ 37 | serviceName: "My Service", 38 | endpoint: "http://localhost:9411", 39 | kind: "client" 40 | }); 41 | ``` 42 | 43 | ## 1.0.0 44 | 45 | * [PR#9](https://github.com/costacruise/zipkin-javascript-opentracing/pull/9) 46 | moving dev dependencies up to peer dependencies, breaking change for clients 47 | using older versions of `opentracing`, `zipkin` or `zipkin-transport-http` 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2020 Daniel Schmidt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Zipkin-Javascript-Opentracing [![Build Status](https://travis-ci.org/DanielMSchmidt/zipkin-javascript-opentracing.svg?branch=master)](https://travis-ci.org/DanielMSchmidt/zipkin-javascript-opentracing) [![Coverage Status](https://coveralls.io/repos/github/DanielMSchmidt/zipkin-javascript-opentracing/badge.svg?branch=master)](https://coveralls.io/github/DanielMSchmidt/zipkin-javascript-opentracing?branch=master) 2 | 3 | ## Installation 4 | 5 | Run `npm install --save zipkin-javascript-opentracing` to install the library. 6 | 7 | For usage instructions, please see the examples in the [`examples/`](examples/) 8 | directory. There is a [basic vanilly 9 | javascript](https://github.com/DanielMSchmidt/zipkin-javascript-opentracing/tree/master/examples/vanillajs/basic) 10 | example that shows how to use the tracer in the context of a single express 11 | server and there is an [advanced vanilla 12 | javascript](https://github.com/DanielMSchmidt/zipkin-javascript-opentracing/tree/master/examples/vanillajs/advanced) 13 | example that shows how multiple services (express API and frontend) might 14 | interact and share a tracing context. 15 | 16 | ## Limitations 17 | 18 | ### injecting and ejecting 19 | 20 | We currently only support HTTP Headers. If you need your own mechanism, feel 21 | free to do a PR. Also we assume that you only inject the HTTP Headers once, 22 | otherwise we will send multiple `ClientSend` annotations for you. 23 | 24 | Also you can only finish spans which were not extracted. If you like this 25 | behaviour to be different, please open an issue. 26 | 27 | ### Flags 28 | 29 | They are currently not supported, feel free to do a PR. 30 | 31 | ### Follows From (zipkin) 32 | 33 | FollowsFrom is not supported by openTracing, as far as I understand. 34 | 35 | ### Additional options for starting a span 36 | 37 | We need to know if this is a server or client to set the right annotations. 38 | Therefore we need the kind attribute to be set. 39 | 40 | ## Example 41 | 42 | All examples need to run zipkin on `"localhost:9411"`. This is best achieved by 43 | using docker: 44 | 45 | ```bash 46 | docker run -d -p 9411:9411 openzipkin/zipkin 47 | ``` 48 | 49 | ### Basic 50 | 51 | To see how to use this library with only one service see 52 | `examples/vanillajs/basic`. You can run the example with `npm run example:basic`. 53 | 54 | ### Advanced 55 | 56 | In order to see how different services may pick up spans and extend them, please 57 | see the advanced example at `examples/vaniallajs/advanced`. You can run the 58 | example with `npm run example:advanced`. 59 | -------------------------------------------------------------------------------- /__mocks__/zipkin-option.js: -------------------------------------------------------------------------------- 1 | const None = { 2 | get type() { 3 | return "None"; 4 | }, 5 | map() { 6 | return None; 7 | }, 8 | ifPresent() {}, 9 | flatMap() { 10 | return None; 11 | }, 12 | getOrElse(f) { 13 | if (f instanceof Function) { 14 | return f(); 15 | } else { 16 | return f; 17 | } 18 | }, 19 | equals(other) { 20 | return other.type === "None"; 21 | }, 22 | toString() { 23 | return "None"; 24 | }, 25 | get present() { 26 | return false; 27 | } 28 | }; 29 | 30 | class Some { 31 | constructor(value) { 32 | this.value = value; 33 | } 34 | map(f) { 35 | return new Some(f(this.value)); 36 | } 37 | ifPresent(f) { 38 | return this.map(f); 39 | } 40 | flatMap(f) { 41 | return this.map(f).getOrElse(None); 42 | } 43 | getOrElse() { 44 | return this.value; 45 | } 46 | equals(other) { 47 | return other instanceof Some && other.value === this.value; 48 | } 49 | toString() { 50 | return `Some(${this.value.toString()})`; 51 | } 52 | get present() { 53 | return true; 54 | } 55 | get type() { 56 | return "Some"; 57 | } 58 | } 59 | 60 | // Used to validate input arguments 61 | function isOptional(data) { 62 | return ( 63 | data != null && 64 | (data instanceof Some || 65 | data === None || 66 | data.type === "Some" || 67 | data.type === "None") 68 | ); 69 | } 70 | 71 | function verifyIsOptional(data) { 72 | if (data == null) { 73 | throw new Error("Error: data is not Optional - it's null"); 74 | } 75 | if (isOptional(data)) { 76 | if (isOptional(data.value)) { 77 | throw new Error( 78 | `Error: data (${data.value.toString()}) is wrapped in Option twice` 79 | ); 80 | } 81 | } else { 82 | throw new Error(`Error: data (${data}) is not an Option!`); 83 | } 84 | } 85 | 86 | function fromNullable(nullable) { 87 | if (nullable != null) { 88 | return new Some(nullable); 89 | } else { 90 | return None; 91 | } 92 | } 93 | 94 | module.exports.Some = Some; 95 | module.exports.None = None; 96 | module.exports.verifyIsOptional = verifyIsOptional; 97 | module.exports.fromNullable = fromNullable; 98 | -------------------------------------------------------------------------------- /__mocks__/zipkin.js: -------------------------------------------------------------------------------- 1 | const None = { 2 | get type() { 3 | return "None"; 4 | }, 5 | map() { 6 | return None; 7 | }, 8 | ifPresent() {}, 9 | flatMap() { 10 | return None; 11 | }, 12 | getOrElse(f) { 13 | if (f instanceof Function) { 14 | return f(); 15 | } else { 16 | return f; 17 | } 18 | }, 19 | equals(other) { 20 | return other.type === "None"; 21 | }, 22 | toString() { 23 | return "None"; 24 | }, 25 | get present() { 26 | return false; 27 | } 28 | }; 29 | 30 | class Some { 31 | constructor(value) { 32 | this.value = value; 33 | } 34 | map(f) { 35 | return new Some(f(this.value)); 36 | } 37 | ifPresent(f) { 38 | return this.map(f); 39 | } 40 | flatMap(f) { 41 | return this.map(f).getOrElse(None); 42 | } 43 | getOrElse() { 44 | return this.value; 45 | } 46 | equals(other) { 47 | return other instanceof Some && other.value === this.value; 48 | } 49 | toString() { 50 | return `Some(${this.value.toString()})`; 51 | } 52 | get present() { 53 | return true; 54 | } 55 | get type() { 56 | return "Some"; 57 | } 58 | } 59 | 60 | class InetAddress { 61 | constructor(addr) { 62 | this.addr = addr; 63 | } 64 | toInt() { 65 | // e.g. 10.57.50.83 66 | // should become 67 | // 171520595 68 | const parts = this.addr.split("."); 69 | 70 | // The jshint tool always complains about using bitwise operators, 71 | // but in this case it's actually intentional, so we disable the warning: 72 | // jshint bitwise: false 73 | return (parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3]; 74 | } 75 | toString() { 76 | return `InetAddress(${this.addr})`; 77 | } 78 | } 79 | 80 | module.exports = { 81 | Annotation: { 82 | BinaryAnnotation: jest.fn(), 83 | ClientAddr: jest.fn(), 84 | ClientRecv: jest.fn(), 85 | ClientSend: jest.fn(), 86 | ServerAddr: jest.fn(), 87 | ServerRecv: jest.fn(), 88 | ServerSend: jest.fn(), 89 | Rpc: jest.fn() 90 | }, 91 | InetAddress, 92 | ExplicitContext: jest.fn(), 93 | Tracer: jest.fn(() => ({ 94 | createRootId: jest.fn(() => { 95 | return { 96 | traceId: new Some("traceId:" + new Date() + Math.random()), 97 | parentId: new Some("parentId:" + new Date() + Math.random()), 98 | spanId: "spanId:" + new Date() + Math.random(), 99 | sampled: new Some("sampled:" + new Date() + Math.random()) 100 | }; 101 | }), 102 | recordAnnotation: jest.fn(), 103 | recordMessage: jest.fn(), 104 | recordBinary: jest.fn(), 105 | recordServiceName: jest.fn(), 106 | scoped: jest.fn(cb => process.nextTick(cb)), 107 | setId: jest.fn() 108 | })), 109 | TraceId: jest.fn(traceId => Object.assign({}, traceId)), 110 | Request: { 111 | addZipkinHeaders: jest.fn((_, base) => ({ 112 | headers: { 113 | "X-B3-TraceId": base.traceId, 114 | "X-B3-SpanId": base.spanId, 115 | "X-B3-Sampled": base.sampled 116 | } 117 | })) 118 | }, 119 | HttpHeaders: { 120 | TraceId: "X-B3-TraceId", 121 | SpanId: "X-B3-SpanId", 122 | ParentSpanId: "X-B3-ParentSpanId", 123 | Sampled: "X-B3-Sampled", 124 | Flags: "X-B3-Flags" 125 | }, 126 | option: require("zipkin-option"), 127 | sampler: { 128 | alwaysSample: jest.fn(() => true), 129 | Sampler: jest.fn() 130 | }, 131 | jsonEncoder: { 132 | JSON_V1: jest.fn() 133 | } 134 | }; 135 | -------------------------------------------------------------------------------- /docs/examples.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: examples 3 | title: Examples 4 | sidebar_label: Examples 5 | --- 6 | 7 | ## Getting started with opentracing 8 | 9 | To get a better understanding of tracing in general you can either check out 10 | this [blog 11 | series](https://medium.com/@dschmidt1992/performance-monitoring-for-the-frontend-an-introduction-e0ab422f131c) 12 | or the [opentracing 13 | documentation](https://github.com/opentracing/specification/blob/master/specification.md) 14 | 15 | ## Examples with Node.js and Vanilla Javascript 16 | 17 | We have a dedicated example folder with two examples, [one for showing how to do 18 | client only 19 | tracing](https://github.com/DanielMSchmidt/zipkin-javascript-opentracing/tree/master/examples/vanillajs/basic) 20 | and [one for showing the interaction between two 21 | systems](https://github.com/DanielMSchmidt/zipkin-javascript-opentracing/tree/master/examples/vanillajs/advanced). 22 | 23 | ## Example with React.js 24 | 25 | 26 | 27 | ## Example with React Native 28 | 29 |
30 | 31 | -------------------------------------------------------------------------------- /e2e-tests/react/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | 23 | cypress/videos/ 24 | -------------------------------------------------------------------------------- /e2e-tests/react/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React 2 | App](https://github.com/facebookincubator/create-react-app). 3 | 4 | Below you will find some information on how to perform common tasks.
You can 5 | find the most recent version of this guide 6 | [here](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md). 7 | 8 | ## Table of Contents 9 | 10 | * [Available Scripts](#available-scripts) 11 | * [npm start](#npm-start) 12 | * [npm test](#npm-test) 13 | * [npm run build](#npm-run-build) 14 | 15 | ## Available Scripts 16 | 17 | In the project directory, you can run: 18 | 19 | ### `npm start` 20 | 21 | Runs the app in the development mode.
Open 22 | [http://localhost:3000](http://localhost:3000) to view it in the browser. 23 | 24 | The page will reload if you make edits.
You will also see any lint errors in 25 | the console. 26 | 27 | ### `npm test` 28 | 29 | Launches the E2E tests 30 | 31 | ### `npm run build` 32 | 33 | Builds the app for production to the `build` folder.
It correctly bundles 34 | React in production mode and optimizes the build for the best performance. 35 | -------------------------------------------------------------------------------- /e2e-tests/react/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "http://localhost:3000", 3 | "projectId": "5gpnqw" 4 | } 5 | -------------------------------------------------------------------------------- /e2e-tests/react/cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } 6 | -------------------------------------------------------------------------------- /e2e-tests/react/cypress/integration/basic_spec.js: -------------------------------------------------------------------------------- 1 | require("babel-polyfill"); 2 | import fetch from "node-fetch"; 3 | 4 | const getTraceAmount = () => 5 | fetch("http://localhost:9411/api/v2/traces") 6 | .then(res => res.json()) 7 | .then(res => res.length); 8 | 9 | const getLastTrace = () => 10 | fetch("http://localhost:9411/api/v2/traces") 11 | .then(res => res.json()) 12 | .then(res => res[res.length - 1][0]); 13 | const wait = (time = 2000) => new Promise(resolve => setTimeout(resolve, time)); 14 | 15 | describe("Basic", () => { 16 | describe("Setup", () => { 17 | it("should be able to access zipkin", async () => { 18 | const response = await fetch("http://localhost:9411/api/v2/traces"); 19 | expect(response.status).to.equal(200); 20 | }); 21 | }); 22 | 23 | describe("Span", () => { 24 | it("should be able interact with the basic example", async () => { 25 | const before = await getTraceAmount(); 26 | cy.visit("/"); 27 | cy.get("#Basic").click(); 28 | cy.get("#buttonLabel").should($p => { 29 | expect($p.first()).to.contain("Not-Pressed"); 30 | }); 31 | cy.get("#basicButton").click(); 32 | cy.get("#buttonLabel").should(async $p => { 33 | expect($p.first()).to.contain("Is-Pressed"); 34 | const after = await getTraceAmount(); 35 | expect(after - before).to.equal(1); 36 | }); 37 | }); 38 | 39 | it("should be able to get the right span name", async () => { 40 | cy.visit("/"); 41 | cy.get("#Basic").click(); 42 | cy.get("#buttonLabel").should($p => { 43 | expect($p.first()).to.contain("Not-Pressed"); 44 | }); 45 | cy.get("#basicButton").click(); 46 | 47 | cy.get("#buttonLabel").should(async $p => { 48 | expect($p.first()).to.contain("Is-Pressed"); 49 | }); 50 | await wait(); 51 | const trace = await getLastTrace(); 52 | expect(trace.name).to.equal("firstspan"); 53 | }); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /e2e-tests/react/cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This is will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /e2e-tests/react/cypress/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import "./commands"; 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /e2e-tests/react/cypress/utils/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielMSchmidt/zipkin-javascript-opentracing/62bcc833a5f94ac0bc64af19cd2264a2c92cdef1/e2e-tests/react/cypress/utils/index.js -------------------------------------------------------------------------------- /e2e-tests/react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "opentracing": "0.14.3", 7 | "react": "16.3.1", 8 | "react-dom": "16.3.1", 9 | "react-scripts": "1.1.4", 10 | "zipkin": "0.10.1", 11 | "zipkin-javascript-opentracing": "file:../..", 12 | "zipkin-transport-http": "0.10.1" 13 | }, 14 | "scripts": { 15 | "start": "react-scripts start", 16 | "build": "react-scripts build", 17 | "eject": "react-scripts eject", 18 | "test:build": "serve -p 3000 -s build", 19 | "test": "cypress run" 20 | }, 21 | "devDependencies": { 22 | "babel-polyfill": "6.26.0", 23 | "cypress": "3.8.2", 24 | "node-fetch": "2.1.2", 25 | "serve": "6.5.5" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /e2e-tests/react/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielMSchmidt/zipkin-javascript-opentracing/62bcc833a5f94ac0bc64af19cd2264a2c92cdef1/e2e-tests/react/public/favicon.ico -------------------------------------------------------------------------------- /e2e-tests/react/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /e2e-tests/react/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /e2e-tests/react/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 80px; 8 | } 9 | 10 | .App-header { 11 | background-color: #222; 12 | height: 150px; 13 | padding: 20px; 14 | color: white; 15 | } 16 | 17 | .App-title { 18 | font-size: 1.5em; 19 | } 20 | 21 | .App-intro { 22 | font-size: large; 23 | } 24 | 25 | @keyframes App-logo-spin { 26 | from { 27 | transform: rotate(0deg); 28 | } 29 | to { 30 | transform: rotate(360deg); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /e2e-tests/react/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import "./App.css"; 3 | import { Basic } from "./screens"; 4 | 5 | class App extends Component { 6 | state = { 7 | screen: false 8 | }; 9 | 10 | renderScreenButton(title, component) { 11 | return ( 12 | { 15 | this.setState({ screen: component }); 16 | }} 17 | > 18 | {title} 19 | 20 | ); 21 | } 22 | 23 | render() { 24 | if (!this.state.screen) { 25 | return ( 26 |
{this.renderScreenButton("Basic", Basic)}
27 | ); 28 | } 29 | 30 | const Screen = this.state.screen; 31 | return ; 32 | } 33 | } 34 | 35 | export default App; 36 | -------------------------------------------------------------------------------- /e2e-tests/react/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /e2e-tests/react/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | 6 | ReactDOM.render(, document.getElementById("root")); 7 | -------------------------------------------------------------------------------- /e2e-tests/react/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /e2e-tests/react/src/screens/Basic.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | const ZipkinJavascriptOpentracing = require("zipkin-javascript-opentracing"); 3 | const { BatchRecorder } = require("zipkin"); 4 | const { HttpLogger } = require("zipkin-transport-http"); 5 | 6 | const tracer = new ZipkinJavascriptOpentracing({ 7 | serviceName: "basic", 8 | recorder: new BatchRecorder({ 9 | logger: new HttpLogger({ 10 | endpoint: "http://localhost:9411/api/v2/spans" 11 | }) 12 | }), 13 | kind: "client" 14 | }); 15 | 16 | export default class BasicScreen extends Component { 17 | constructor(props) { 18 | super(props); 19 | this.state = { 20 | pressed: false 21 | }; 22 | } 23 | 24 | render() { 25 | return ( 26 |
27 | { 30 | const span = tracer.startSpan("FirstSpan"); 31 | this.setState({ pressed: true }); 32 | span.finish(); 33 | }} 34 | > 35 | 36 | {this.state.pressed ? "Is-Pressed" : "Not-Pressed"} 37 | 38 | 39 |
40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /e2e-tests/react/src/screens/index.js: -------------------------------------------------------------------------------- 1 | import Basic from "./Basic"; 2 | 3 | export { Basic }; 4 | -------------------------------------------------------------------------------- /examples/vanillajs/advanced/client.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const fetch = require("node-fetch"); 3 | const ZipkinJavascriptOpentracing = require("../../lib/index"); 4 | const { recorder } = require("../recorder"); 5 | const availableTags = require("opentracing").Tags; 6 | 7 | const app = express(); 8 | const tracer = new ZipkinJavascriptOpentracing({ 9 | serviceName: "My Client", 10 | recorder, 11 | kind: "client" 12 | }); 13 | 14 | app.use(function zipkinExpressMiddleware(req, res, next) { 15 | console.log("client middleware start"); 16 | setTimeout(() => { 17 | const headers = {}; 18 | const span = tracer.startSpan("Client Span"); 19 | 20 | span.setTag(availableTags.PEER_ADDRESS, "127.0.0.1:8081"); 21 | 22 | span.log({ 23 | statusCode: "200", 24 | objectId: "42" 25 | }); 26 | 27 | tracer.inject( 28 | span, 29 | ZipkinJavascriptOpentracing.FORMAT_HTTP_HEADERS, 30 | headers 31 | ); 32 | 33 | const preRequestSpan = tracer.startSpan("Pre Request", { 34 | childOf: span 35 | }); 36 | 37 | setTimeout(() => { 38 | preRequestSpan.finish(); 39 | 40 | fetch("http://localhost:8082/", { 41 | headers: headers 42 | }).then(response => { 43 | const responseSpan = tracer.startSpan("Render Response", { 44 | childOf: span 45 | }); 46 | 47 | responseSpan.log({ 48 | response: JSON.stringify(response) 49 | }); 50 | 51 | setTimeout(() => { 52 | responseSpan.finish(); 53 | }, 100); 54 | 55 | setTimeout(() => { 56 | span.finish(); 57 | next(); 58 | }, 200); 59 | }); 60 | }, 100); 61 | }, 100); 62 | }); 63 | 64 | app.get("/", (req, res) => { 65 | res.send(Date.now().toString()); 66 | }); 67 | 68 | app.listen(8081, () => { 69 | console.log("Frontend listening on port 8081!"); 70 | }); 71 | -------------------------------------------------------------------------------- /examples/vanillajs/advanced/index.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const childProc = require("child_process"); 3 | 4 | childProc.fork(path.join(__dirname, "client.js")); 5 | childProc.fork(path.join(__dirname, "server.js")); 6 | -------------------------------------------------------------------------------- /examples/vanillajs/advanced/server.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const ZipkinJavascriptOpentracing = require("../../lib/index"); 3 | const { recorder } = require("../recorder"); 4 | const availableTags = require("opentracing").Tags; 5 | 6 | const app = express(); 7 | const tracer = new ZipkinJavascriptOpentracing({ 8 | serviceName: "My Server", 9 | recorder, 10 | kind: "server" 11 | }); 12 | 13 | app.use(function zipkinExpressMiddleware(req, res, next) { 14 | console.log("server middleware start"); 15 | const context = tracer.extract( 16 | ZipkinJavascriptOpentracing.FORMAT_HTTP_HEADERS, 17 | req.headers 18 | ); 19 | 20 | const span = tracer.startSpan("Server Span", { childOf: context }); 21 | 22 | span.setTag(availableTags.PEER_ADDRESS, "127.0.0.1:8082"); 23 | 24 | setTimeout(() => { 25 | span.log({ 26 | serverVisited: "yes" 27 | }); 28 | }, 100); 29 | 30 | setTimeout(() => { 31 | console.log("finish server"); 32 | span.finish(); 33 | next(); 34 | }, 200); 35 | }); 36 | 37 | app.get("/", (req, res) => { 38 | res.send(Date.now().toString()); 39 | }); 40 | 41 | app.listen(8082, () => { 42 | console.log("Frontend listening on port 8082!"); 43 | }); 44 | -------------------------------------------------------------------------------- /examples/vanillajs/basic/index.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const ZipkinJavascriptOpentracing = require("../../lib/index"); 3 | 4 | const { recorder } = require("../recorder"); 5 | 6 | const app = express(); 7 | const tracer = new ZipkinJavascriptOpentracing({ 8 | serviceName: "My Service", 9 | recorder, 10 | kind: "client" 11 | }); 12 | 13 | app.use(function zipkinExpressMiddleware(req, res, next) { 14 | const span = tracer.startSpan("My Span"); 15 | 16 | setTimeout(() => { 17 | span.log({ 18 | statusCode: "200", 19 | objectId: "42" 20 | }); 21 | }, 100); 22 | 23 | setTimeout(() => { 24 | span.finish(); 25 | }, 200); 26 | 27 | next(); 28 | }); 29 | 30 | app.get("/", (req, res) => res.send(Date.now().toString())); 31 | 32 | app.listen(8081, () => { 33 | console.log("Frontend listening on port 8081!"); 34 | }); 35 | -------------------------------------------------------------------------------- /examples/vanillajs/recorder.js: -------------------------------------------------------------------------------- 1 | const { BatchRecorder } = require("zipkin"); 2 | const { HttpLogger } = require("zipkin-transport-http"); 3 | 4 | module.exports.recorder = new BatchRecorder({ 5 | logger: new HttpLogger({ 6 | endpoint: "http://localhost:9411/api/v1/spans" 7 | }) 8 | }); 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zipkin-javascript-opentracing", 3 | "version": "3.0.0", 4 | "description": "An opentracing implementation for zipkin", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "build": "babel src -d lib && rm -r lib/__tests__", 8 | "example:basic": "node example/basic/index.js", 9 | "example:advanced": "node example/advanced/index.js", 10 | "dev-example:basic": "nodemon example/basic/index.js", 11 | "dev-example:advanced": "nodemon example/advanced/index.js", 12 | "test": "jest", 13 | "precommit": "lint-staged", 14 | "prepare": "npm run build", 15 | "fmt": "prettier '**/*.{js,json,css,md}' --write" 16 | }, 17 | "jest": { 18 | "collectCoverage": true, 19 | "coverageThreshold": { 20 | "global": { 21 | "statements": 100, 22 | "branches": 100, 23 | "functions": 100, 24 | "lines": 100 25 | } 26 | } 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "git+ssh://git@github.com/DanielMSchmidt/zipkin-javascript-opentracing.git" 31 | }, 32 | "keywords": [ 33 | "zipkin", 34 | "opentracing", 35 | "tracing", 36 | "tracking", 37 | "performance", 38 | "benchmarking" 39 | ], 40 | "author": "Daniel Schmidt", 41 | "license": "MIT", 42 | "bugs": { 43 | "url": "https://github.com/DanielMSchmidt/zipkin-javascript-opentracing/issues" 44 | }, 45 | "files": [ 46 | "lib/index.js" 47 | ], 48 | "peerDependencies": { 49 | "opentracing": "*", 50 | "zipkin": ">=0.10.1", 51 | "zipkin-transport-http": ">=0.10.1" 52 | }, 53 | "homepage": "https://github.com/DanielMSchmidt/zipkin-javascript-opentracing#readme", 54 | "devDependencies": { 55 | "babel-cli": "6.26.0", 56 | "babel-preset-env": "1.6.1", 57 | "coveralls": "3.0.0", 58 | "express": "4.16.3", 59 | "husky": "0.14.3", 60 | "jest": "22.4.3", 61 | "lint-staged": "7.0.4", 62 | "nodemon": "1.17.3", 63 | "opentracing": "0.14.3", 64 | "prettier": "1.12.0", 65 | "zipkin": "0.10.1", 66 | "zipkin-transport-http": "0.10.1" 67 | }, 68 | "lint-staged": { 69 | "*.{js,css,md}": [ 70 | "prettier --write", 71 | "git add" 72 | ] 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /scripts/publish-website.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | if [ "$TRAVIS_PULL_REQUEST" = "false" ] && [ "$TRAVIS_BRANCH" = "master" ]; then 4 | git config user.email "$GIT_USER@users.noreply.github.com" 5 | git config user.name "$GIT_USER" 6 | echo "machine github.com login $GIT_USER password $GIT_TOKEN" > ~/.netrc 7 | 8 | cd website 9 | npm install 10 | GIT_USER=$GIT_USER CURRENT_BRANCH=master npm run publish-gh-pages 11 | else 12 | echo 'Not deploying the website' 13 | exit 0; 14 | fi 15 | 16 | -------------------------------------------------------------------------------- /src/__tests__/__snapshots__/e2e.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`simplified setup endpoint should throw an error if the endpoint doesnt start with http 1`] = `"endpoint value needs to start with http:// or https://"`; 4 | -------------------------------------------------------------------------------- /src/__tests__/__snapshots__/index.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` 1`] = `"Cannot read property 'extract' of undefined"`; 4 | 5 | exports[`Opentracing interface extract (deserialize) HTTP Headers should fail with an invalid carrier 1`] = `"extract called without a carrier"`; 6 | 7 | exports[`Opentracing interface extract (deserialize) Text Map should throw an error, because it is unsupported 1`] = `"extract called with unsupported format"`; 8 | 9 | exports[`Opentracing interface inject (serialize) Binary should throw an error, because it is unsupported 1`] = `"inject called with unsupported format"`; 10 | 11 | exports[`Opentracing interface inject (serialize) HTTP Headers should throw without a carrier 1`] = `"inject called without a carrier object"`; 12 | 13 | exports[`Opentracing interface inject (serialize) HTTP Headers should throw without a span 1`] = `"inject called without a span"`; 14 | 15 | exports[`Opentracing interface inject (serialize) Text Map should throw an error, because it is unsupported 1`] = `"inject called with unsupported format"`; 16 | 17 | exports[`Opentracing interface should be have kind server, client and local 1`] = `"kind option needs to be provided as either \\"local\\", \\"client\\" or \\"server\\""`; 18 | 19 | exports[`Opentracing interface should fail to init tracer with a malformed kind 1`] = `"kind option needs to be provided as either \\"local\\", \\"client\\" or \\"server\\""`; 20 | 21 | exports[`Opentracing interface should fail to init tracer without a kind 1`] = `"kind option needs to be provided as either \\"local\\", \\"client\\" or \\"server\\""`; 22 | 23 | exports[`Opentracing interface should fail to init tracer without a recorder 1`] = `"recorder or endpoint option needs to be provided"`; 24 | 25 | exports[`Opentracing interface should fail to init tracer without a service name 1`] = `"serviceName option needs to be provided"`; 26 | 27 | exports[`Opentracing interface spans span object should fail set tag if key has wrong type of value 1`] = `"Tag peer.address needs a string"`; 28 | -------------------------------------------------------------------------------- /src/__tests__/e2e.js: -------------------------------------------------------------------------------- 1 | jest.disableAutomock(); 2 | jest.useFakeTimers(); 3 | 4 | let mockFetch = jest.fn(); 5 | jest.mock("node-fetch", () => (endpoint, ...args) => { 6 | mockFetch(endpoint, ...args); 7 | return Promise.resolve({ 8 | status: 202 9 | }); 10 | }); 11 | 12 | jest.unmock("zipkin"); 13 | jest.unmock("zipkin-transport-http"); 14 | jest.unmock("opentracing"); 15 | 16 | const { 17 | Annotation, 18 | BatchRecorder, 19 | ExplicitContext, 20 | Tracer 21 | } = require("zipkin"); 22 | const { HttpLogger } = require("zipkin-transport-http"); 23 | const fetch = require("node-fetch"); 24 | const opentracing = require("opentracing"); 25 | 26 | const ZipkinJavascriptOpentracing = require("../index"); 27 | 28 | describe("mock", () => { 29 | it("should capture fetches", () => { 30 | fetch("http://foo"); 31 | expect(mockFetch).toHaveBeenCalledWith("http://foo"); 32 | }); 33 | 34 | describe("zipkin", () => { 35 | let tracer; 36 | beforeEach(() => { 37 | tracer = new Tracer({ 38 | ctxImpl: new ExplicitContext(), 39 | recorder: new BatchRecorder({ 40 | logger: new HttpLogger({ 41 | endpoint: "http://localhost:9411/api/v2/spans" 42 | }) 43 | }) 44 | }); 45 | mockFetch.mockReset(); 46 | }); 47 | 48 | describe("startSpan", () => { 49 | it("should record a simple request", () => { 50 | tracer.scoped(() => { 51 | tracer.setId(tracer.createRootId()); 52 | tracer.recordServiceName("My Service"); 53 | tracer.recordBinary("spanName", "My Span"); 54 | tracer.recordAnnotation(new Annotation.ServerSend()); 55 | }); 56 | 57 | jest.runOnlyPendingTimers(); 58 | 59 | expect(mockFetch).toHaveBeenCalled(); 60 | const [endpoint, { method, body, headers }] = mockFetch.mock.calls[0]; 61 | 62 | expect(endpoint).toBe("http://localhost:9411/api/v2/spans"); 63 | expect(method).toBe("POST"); 64 | expect(Object.keys(headers)).toEqual( 65 | expect.arrayContaining(["Accept", "Content-Type"]) 66 | ); 67 | const json = JSON.parse(body); 68 | expect(json.length).toBe(1); 69 | expect(Object.keys(json[0])).toEqual( 70 | expect.arrayContaining([ 71 | "traceId", 72 | "name", 73 | "id", 74 | "annotations", 75 | "binaryAnnotations", 76 | "timestamp", 77 | "duration" 78 | ]) 79 | ); 80 | expect(json[0].annotations.length).toBe(2); 81 | expect(json[0].annotations[0].endpoint.serviceName).toBe("my service"); 82 | expect(json[0].binaryAnnotations.length).toBe(1); 83 | expect(json[0].binaryAnnotations[0].value).toBe("My Span"); 84 | }); 85 | 86 | it("should record logs", () => { 87 | tracer.scoped(() => { 88 | tracer.setId(tracer.createRootId()); 89 | tracer.recordServiceName("My Service"); 90 | tracer.recordBinary("spanName", "My Span"); 91 | tracer.recordBinary("statusCode", "200"); 92 | tracer.recordBinary("objectId", "42"); 93 | tracer.recordAnnotation(new Annotation.ServerSend()); 94 | }); 95 | 96 | jest.runOnlyPendingTimers(); 97 | 98 | expect(mockFetch).toHaveBeenCalled(); 99 | const [endpoint, { method, body, headers }] = mockFetch.mock.calls[0]; 100 | 101 | expect(endpoint).toBe("http://localhost:9411/api/v2/spans"); 102 | expect(method).toBe("POST"); 103 | expect(Object.keys(headers)).toEqual( 104 | expect.arrayContaining(["Accept", "Content-Type"]) 105 | ); 106 | const json = JSON.parse(body); 107 | expect(json.length).toBe(1); 108 | expect(Object.keys(json[0])).toEqual( 109 | expect.arrayContaining([ 110 | "traceId", 111 | "name", 112 | "id", 113 | "annotations", 114 | "binaryAnnotations", 115 | "timestamp", 116 | "duration" 117 | ]) 118 | ); 119 | expect(json[0].annotations.length).toBe(2); 120 | expect(json[0].annotations[0].endpoint.serviceName).toBe("my service"); 121 | expect(json[0].binaryAnnotations.length).toBe(3); 122 | expect(json[0].binaryAnnotations[0].key).toBe("spanName"); 123 | expect(json[0].binaryAnnotations[0].value).toBe("My Span"); 124 | expect(json[0].binaryAnnotations[1].key).toBe("statusCode"); 125 | expect(json[0].binaryAnnotations[1].value).toBe("200"); 126 | expect(json[0].binaryAnnotations[2].key).toBe("objectId"); 127 | expect(json[0].binaryAnnotations[2].value).toBe("42"); 128 | }); 129 | }); 130 | }); 131 | }); 132 | 133 | describe("zipkin-javascript-opentracing", () => { 134 | let tracer; 135 | beforeEach(() => { 136 | mockFetch.mockReset(); 137 | tracer = new ZipkinJavascriptOpentracing({ 138 | serviceName: "My Service", 139 | recorder: new BatchRecorder({ 140 | logger: new HttpLogger({ 141 | endpoint: "http://localhost:9411/api/v2/spans" 142 | }) 143 | }), 144 | kind: "server" 145 | }); 146 | }); 147 | 148 | describe("startSpan", () => { 149 | it("should record a simple request", () => { 150 | const span = tracer.startSpan("My Span"); 151 | span.finish(); 152 | jest.runOnlyPendingTimers(); 153 | jest.runOnlyPendingTimers(); 154 | jest.runOnlyPendingTimers(); 155 | 156 | expect(mockFetch).toHaveBeenCalled(); 157 | const [endpoint, { method, body, headers }] = mockFetch.mock.calls[0]; 158 | 159 | expect(endpoint).toBe("http://localhost:9411/api/v2/spans"); 160 | expect(method).toBe("POST"); 161 | expect(Object.keys(headers)).toEqual( 162 | expect.arrayContaining(["Accept", "Content-Type"]) 163 | ); 164 | const json = JSON.parse(body); 165 | expect(json.length).toBe(1); 166 | expect(Object.keys(json[0])).toEqual( 167 | expect.arrayContaining([ 168 | "traceId", 169 | "name", 170 | "id", 171 | "annotations", 172 | "timestamp", 173 | "duration" 174 | ]) 175 | ); 176 | expect(json[0].annotations.length).toBe(2); 177 | expect(json[0].annotations[0].endpoint.serviceName).toBe("my service"); 178 | expect(json[0].name).toBe("my span"); 179 | }); 180 | 181 | it("should record logs", () => { 182 | const span = tracer.startSpan("My Span"); 183 | span.log("LogMessage"); 184 | 185 | span.finish(); 186 | 187 | jest.runOnlyPendingTimers(); 188 | 189 | expect(mockFetch).toHaveBeenCalled(); 190 | const [endpoint, { method, body, headers }] = mockFetch.mock.calls[0]; 191 | 192 | expect(endpoint).toBe("http://localhost:9411/api/v2/spans"); 193 | expect(method).toBe("POST"); 194 | expect(Object.keys(headers)).toEqual( 195 | expect.arrayContaining(["Accept", "Content-Type"]) 196 | ); 197 | const json = JSON.parse(body); 198 | expect(json.length).toBe(1); 199 | expect(Object.keys(json[0])).toEqual( 200 | expect.arrayContaining([ 201 | "traceId", 202 | "name", 203 | "id", 204 | "annotations", 205 | "duration" 206 | ]) 207 | ); 208 | expect(json[0].annotations.length).toBe(3); 209 | expect(json[0].annotations[0].endpoint.serviceName).toBe("my service"); 210 | expect(json[0].annotations[0].value).toBe("LogMessage"); 211 | expect(json[0].name).toBe("my span"); 212 | }); 213 | }); 214 | 215 | describe("inject", () => { 216 | it("should set every defined HTTP Header", () => { 217 | const span = tracer.startSpan("My Span"); 218 | 219 | const carrier = {}; 220 | tracer.inject( 221 | span, 222 | ZipkinJavascriptOpentracing.FORMAT_HTTP_HEADERS, 223 | carrier 224 | ); 225 | 226 | expect(carrier["x-b3-traceid"]).toBeDefined(); 227 | expect(carrier["x-b3-spanid"]).toBeDefined(); 228 | expect(carrier["x-b3-sampled"]).toBeDefined(); 229 | }); 230 | 231 | it("should set the parentId", () => { 232 | const parent = tracer.startSpan("ParentSpan"); 233 | const child = tracer.startSpan("ChildSpan", { 234 | childOf: parent 235 | }); 236 | 237 | const carrier = {}; 238 | tracer.inject( 239 | child, 240 | ZipkinJavascriptOpentracing.FORMAT_HTTP_HEADERS, 241 | carrier 242 | ); 243 | 244 | expect(carrier["x-b3-traceid"]).toBeDefined(); 245 | expect(carrier["x-b3-spanid"]).toBeDefined(); 246 | expect(carrier["x-b3-parentspanid"]).toBeDefined(); 247 | expect(carrier["x-b3-sampled"]).toBeDefined(); 248 | }); 249 | }); 250 | 251 | describe("extract", () => { 252 | it("should use the span and trace id of the given headers", () => { 253 | const previousHeaders = { 254 | "x-b3-traceid": "30871be42b0fd4781", 255 | "x-b3-spanid": "30871be42b0fd4782" 256 | }; 257 | 258 | const span = tracer.extract( 259 | ZipkinJavascriptOpentracing.FORMAT_HTTP_HEADERS, 260 | previousHeaders 261 | ); 262 | 263 | const childSpan = tracer.startSpan("child", { 264 | childOf: span 265 | }); 266 | childSpan.finish(); 267 | 268 | jest.runOnlyPendingTimers(); 269 | 270 | expect(mockFetch).toHaveBeenCalled(); 271 | const [endpoint, { method, body, headers }] = mockFetch.mock.calls[0]; 272 | 273 | expect(endpoint).toBe("http://localhost:9411/api/v2/spans"); 274 | expect(method).toBe("POST"); 275 | expect(Object.keys(headers)).toEqual( 276 | expect.arrayContaining(["Accept", "Content-Type"]) 277 | ); 278 | const json = JSON.parse(body); 279 | expect(json[0].traceId).toBe("30871be42b0fd4781"); 280 | expect(json[0].parentId).toBe("30871be42b0fd4782"); 281 | }); 282 | }); 283 | 284 | describe("inject + extract", () => { 285 | let tracer; 286 | let zipkinTracer; 287 | beforeEach(() => { 288 | tracer = new ZipkinJavascriptOpentracing({ 289 | serviceName: "MyService", 290 | recorder: new BatchRecorder({ 291 | logger: new HttpLogger({ 292 | endpoint: "http://localhost:9411/api/v2/spans" 293 | }) 294 | }), 295 | kind: "server" 296 | }); 297 | zipkinTracer = tracer._zipkinTracer; 298 | }); 299 | 300 | describe("HTTP Headers", () => { 301 | it("should work with injecting and extracting in a row", () => { 302 | const span = tracer.startSpan("My Span"); 303 | 304 | const headers = {}; 305 | tracer.inject( 306 | span, 307 | ZipkinJavascriptOpentracing.FORMAT_HTTP_HEADERS, 308 | headers 309 | ); 310 | const newSpan = tracer.extract( 311 | ZipkinJavascriptOpentracing.FORMAT_HTTP_HEADERS, 312 | headers 313 | ); 314 | 315 | expect(newSpan.id.spanId).toEqual(span.id._spanId); 316 | expect(newSpan.id.traceId).toEqual(span.id.traceId); 317 | }); 318 | 319 | it("should work with extracting and injecting in a row", () => { 320 | const headers = { 321 | "x-b3-sampled": "0", 322 | "x-b3-spanid": "a07ee38e6b11dc0c1", 323 | "x-b3-traceid": "a07ee38e6b11dc0c2", 324 | "x-b3-parentspanid": "a07ee38e6b11dc0c3" 325 | }; 326 | const span = tracer.extract( 327 | ZipkinJavascriptOpentracing.FORMAT_HTTP_HEADERS, 328 | headers 329 | ); 330 | 331 | const newHeaders = {}; 332 | tracer.inject( 333 | span, 334 | ZipkinJavascriptOpentracing.FORMAT_HTTP_HEADERS, 335 | newHeaders 336 | ); 337 | 338 | expect(newHeaders).toEqual(headers); 339 | }); 340 | }); 341 | }); 342 | }); 343 | 344 | describe("simplified setup", () => { 345 | describe("endpoint", () => { 346 | it("should throw an error if the endpoint doesnt start with http", () => { 347 | expect(() => { 348 | new ZipkinJavascriptOpentracing({ 349 | serviceName: "My Service", 350 | endpoint: "localhost:9411", 351 | kind: "server" 352 | }); 353 | }).toThrowErrorMatchingSnapshot(); 354 | }); 355 | 356 | it("should record a simple request", () => { 357 | mockFetch.mockReset(); 358 | const tracer = new ZipkinJavascriptOpentracing({ 359 | serviceName: "My Service", 360 | endpoint: "http://localhost:9411", 361 | kind: "server" 362 | }); 363 | 364 | const span = tracer.startSpan("My Span"); 365 | span.finish(); 366 | jest.runOnlyPendingTimers(); 367 | jest.runOnlyPendingTimers(); 368 | jest.runOnlyPendingTimers(); 369 | 370 | expect(mockFetch).toHaveBeenCalled(); 371 | const [endpoint, { method, body, headers }] = mockFetch.mock.calls[0]; 372 | 373 | expect(endpoint).toBe("http://localhost:9411/api/v2/spans"); 374 | expect(method).toBe("POST"); 375 | expect(Object.keys(headers)).toEqual( 376 | expect.arrayContaining(["Accept", "Content-Type"]) 377 | ); 378 | const json = JSON.parse(body); 379 | expect(json.length).toBe(1); 380 | expect(Object.keys(json[0])).toEqual( 381 | expect.arrayContaining([ 382 | "traceId", 383 | "id", 384 | "name", 385 | "kind", 386 | "timestamp", 387 | "duration", 388 | "localEndpoint" 389 | ]) 390 | ); 391 | expect(json[0].localEndpoint.serviceName).toBe("my service"); 392 | 393 | expect(json[0].name).toBe("my span"); 394 | }); 395 | }); 396 | }); 397 | -------------------------------------------------------------------------------- /src/__tests__/index.js: -------------------------------------------------------------------------------- 1 | const opentracing = require("opentracing"); 2 | const Tracer = require("../index"); 3 | const zipkin = require("zipkin"); 4 | const { 5 | sampler: { Sampler } 6 | } = zipkin; 7 | 8 | describe("Opentracing interface", () => { 9 | it("should fail to init tracer without a service name", () => { 10 | expect(() => { 11 | new Tracer(); 12 | }).toThrowErrorMatchingSnapshot(); 13 | }); 14 | 15 | it("should fail to init tracer without a recorder", () => { 16 | expect(() => { 17 | new Tracer({ 18 | serviceName: "MyService" 19 | }); 20 | }).toThrowErrorMatchingSnapshot(); 21 | }); 22 | 23 | it("should fail to init tracer without a kind", () => { 24 | expect(() => { 25 | new Tracer({ 26 | serviceName: "MyService", 27 | recorder: {} 28 | }); 29 | }).toThrowErrorMatchingSnapshot(); 30 | }); 31 | 32 | it("should fail to init tracer with a malformed kind", () => { 33 | expect(() => { 34 | new Tracer({ 35 | serviceName: "MyService", 36 | recorder: {}, 37 | kind: "mobile" 38 | }); 39 | }).toThrowErrorMatchingSnapshot(); 40 | }); 41 | 42 | it("should create a tracer", () => { 43 | const tracer = new Tracer({ 44 | serviceName: "MyService", 45 | recorder: {}, 46 | kind: "server" 47 | }); 48 | 49 | expect(tracer).toBeInstanceOf(Tracer); 50 | }); 51 | 52 | it("should initialize a zipkin tracer", () => { 53 | const tracer = new Tracer({ 54 | serviceName: "MyService", 55 | recorder: {}, 56 | kind: "server" 57 | }); 58 | 59 | expect(zipkin.Tracer).toHaveBeenCalled(); 60 | }); 61 | 62 | it("should set a global tracer", () => { 63 | opentracing.initGlobalTracer( 64 | new Tracer({ 65 | serviceName: "MyService", 66 | recorder: {}, 67 | kind: "server" 68 | }) 69 | ); 70 | 71 | const tracer = opentracing.globalTracer(); 72 | expect(tracer.startSpan).toBeInstanceOf(Function); 73 | }); 74 | 75 | it("should be have kind server, client and local ", () => { 76 | expect(() => { 77 | new Tracer({ 78 | serviceName: "MyService", 79 | recorder: {}, 80 | kind: "client" 81 | }); 82 | }).not.toThrowError(); 83 | 84 | expect(() => { 85 | new Tracer({ 86 | serviceName: "MyService", 87 | recorder: {}, 88 | kind: "server" 89 | }); 90 | }).not.toThrowError(); 91 | 92 | expect(() => { 93 | new Tracer({ 94 | serviceName: "MyService", 95 | recorder: {}, 96 | kind: "local" 97 | }); 98 | }).not.toThrowError(); 99 | 100 | expect(() => { 101 | new Tracer({ 102 | serviceName: "MyService", 103 | recorder: {}, 104 | kind: "peter" 105 | }); 106 | }).toThrowErrorMatchingSnapshot(); 107 | }); 108 | 109 | it("should support 128bit trace Ids lengths"); 110 | 111 | describe("spans", () => { 112 | let tracer; 113 | let zipkinTracer; 114 | beforeEach(() => { 115 | tracer = new Tracer({ 116 | serviceName: "MyService", 117 | recorder: {}, 118 | kind: "server" 119 | }); 120 | zipkinTracer = tracer._zipkinTracer; 121 | zipkinTracer.scoped.mockReset(); 122 | }); 123 | 124 | it("should not start a span without an operation name", () => { 125 | expect(() => { 126 | tracer.startSpan(); 127 | }).toThrowError(); 128 | }); 129 | 130 | // used for extracted spans 131 | it("should start a span with no name, provided an empty string", () => { 132 | tracer.startSpan(""); 133 | 134 | // should do it in a scope 135 | expect(zipkinTracer.scoped).toHaveBeenCalled(); 136 | zipkinTracer.scoped.mock.calls[0][0](); 137 | 138 | expect(zipkinTracer.recordBinary).not.toHaveBeenCalled(); 139 | }); 140 | 141 | it("startSpan should start a span", () => { 142 | zipkinTracer.createRootId.mockImplementationOnce(() => 42); 143 | tracer.startSpan("MyName"); 144 | 145 | // should do it in a scope 146 | expect(zipkinTracer.scoped).toHaveBeenCalled(); 147 | zipkinTracer.scoped.mock.calls[0][0](); 148 | 149 | expect(zipkinTracer.createRootId).toHaveBeenCalled(); 150 | expect(zipkinTracer.setId).toHaveBeenCalledWith(42); 151 | expect(zipkin.Annotation.Rpc).toHaveBeenCalledWith("MyName"); 152 | expect(zipkinTracer.recordServiceName).toHaveBeenCalledWith("MyService"); 153 | }); 154 | 155 | it("should not fail with parent span set to null", () => { 156 | const child = tracer.startSpan("ChildSpan", { 157 | childOf: null, 158 | kind: "server" 159 | }); 160 | // Constructor of child span 161 | expect(zipkinTracer.scoped).toHaveBeenCalled(); 162 | zipkinTracer.scoped.mock.calls[0][0](); 163 | zipkinTracer.scoped.mockReset(); 164 | 165 | expect(zipkinTracer.createRootId).toHaveBeenCalled(); 166 | }); 167 | 168 | it("should start a span with parent span", () => { 169 | const parent = tracer.startSpan("ParentSpan"); 170 | // Constructor of parent span 171 | expect(zipkinTracer.scoped).toHaveBeenCalled(); 172 | zipkinTracer.scoped.mock.calls[0][0](); 173 | zipkinTracer.scoped.mockReset(); 174 | 175 | // make sure we have the right ids set at the parent 176 | expect(parent.id.traceId).toBeTruthy(); 177 | expect(parent.id.parentId).toBeTruthy(); 178 | 179 | const child = tracer.startSpan("ChildSpan", { 180 | childOf: parent, 181 | kind: "server" 182 | }); 183 | // Constructor of child span 184 | expect(zipkinTracer.scoped).toHaveBeenCalled(); 185 | zipkinTracer.scoped.mock.calls[0][0](); 186 | zipkinTracer.scoped.mockReset(); 187 | 188 | expect(zipkin.TraceId).toHaveBeenCalled(); 189 | 190 | // Uses TraceId to get a new TraceId 191 | const call = zipkin.TraceId.mock.calls[0][0]; 192 | expect(call.parentId.present).toBeTruthy(); 193 | }); 194 | 195 | it("should send a server receive annotation if tracer is of kind server", () => { 196 | tracer = new Tracer({ 197 | serviceName: "MyService", 198 | recorder: {}, 199 | kind: "server" 200 | }); 201 | zipkinTracer = tracer._zipkinTracer; 202 | zipkinTracer.scoped.mockReset(); 203 | 204 | tracer.startSpan("MyName"); 205 | 206 | expect(zipkinTracer.scoped).toHaveBeenCalled(); 207 | zipkinTracer.scoped.mock.calls[0][0](); 208 | 209 | expect(zipkin.Annotation.ServerRecv).toHaveBeenCalled(); 210 | expect(zipkinTracer.recordAnnotation).toHaveBeenCalledTimes(2); 211 | }); 212 | 213 | it("should send a client send annotation if tracer is of kind client", () => { 214 | tracer = new Tracer({ 215 | serviceName: "MyService", 216 | recorder: {}, 217 | kind: "client" 218 | }); 219 | zipkinTracer = tracer._zipkinTracer; 220 | zipkinTracer.scoped.mockReset(); 221 | 222 | tracer.startSpan("MyName"); 223 | 224 | expect(zipkinTracer.scoped).toHaveBeenCalled(); 225 | zipkinTracer.scoped.mock.calls[0][0](); 226 | 227 | expect(zipkin.Annotation.ClientSend).toHaveBeenCalled(); 228 | expect(zipkinTracer.recordAnnotation).toHaveBeenCalledTimes(2); 229 | }); 230 | 231 | describe("span object", () => { 232 | let span; 233 | beforeEach(() => { 234 | span = tracer.startSpan("Ponyfoo"); 235 | 236 | expect(zipkinTracer.scoped).toHaveBeenCalled(); 237 | zipkinTracer.scoped.mock.calls[0][0](); 238 | zipkinTracer.scoped.mockReset(); 239 | zipkinTracer.recordAnnotation.mockReset(); 240 | }); 241 | 242 | it("should expose spanId", () => { 243 | expect(span.id).toBeDefined(); 244 | 245 | // Test if mock is sufficient 246 | expect(span.id.traceId).toBeDefined(); 247 | expect(span.id.parentId).toBeDefined(); 248 | expect(span.id.spanId).toBeDefined(); 249 | expect(span.id.sampled).toBeDefined(); 250 | }); 251 | 252 | it("should expose its context", () => { 253 | const spanContext = span.context(); 254 | expect(spanContext.toSpanId()).toBe(span.id.spanId); 255 | expect(spanContext.toTraceId()).toBe(span.id.traceId); 256 | }); 257 | 258 | it("should log data", () => { 259 | span.log("data to log"); 260 | 261 | // should do it in a scope 262 | expect(zipkinTracer.scoped).toHaveBeenCalled(); 263 | zipkinTracer.scoped.mock.calls[0][0](); 264 | 265 | expect(zipkinTracer.recordMessage).toHaveBeenCalledWith("data to log"); 266 | }); 267 | 268 | it("should not log if empty data is passed", () => { 269 | span.log(); 270 | 271 | // should do it in a scope 272 | expect(zipkinTracer.scoped).toHaveBeenCalled(); 273 | zipkinTracer.scoped.mock.calls[0][0](); 274 | 275 | expect(zipkinTracer.recordMessage).not.toHaveBeenCalled(); 276 | }); 277 | 278 | it("should use the right id in log", () => { 279 | const otherSpan = tracer.startSpan("Other Span", { 280 | kind: "client" 281 | }); 282 | 283 | zipkinTracer.scoped.mockReset(); 284 | zipkinTracer.setId.mockReset(); 285 | span.log("other event"); 286 | 287 | // should do it in a scope 288 | expect(zipkinTracer.scoped).toHaveBeenCalled(); 289 | zipkinTracer.scoped.mock.calls[0][0](); 290 | expect(zipkinTracer.setId).toHaveBeenLastCalledWith(span.id); 291 | zipkinTracer.scoped.mockReset(); 292 | zipkinTracer.setId.mockReset(); 293 | 294 | otherSpan.log("yet another event"); 295 | // should do it in a scope 296 | expect(zipkinTracer.scoped).toHaveBeenCalled(); 297 | zipkinTracer.scoped.mock.calls[0][0](); 298 | expect(zipkinTracer.setId).toHaveBeenLastCalledWith(otherSpan.id); 299 | }); 300 | 301 | it("should not fail without an argument", () => { 302 | expect(() => { 303 | span.log(); 304 | // should do it in a scope 305 | expect(zipkinTracer.scoped).toHaveBeenCalled(); 306 | zipkinTracer.scoped.mock.calls[0][0](); 307 | }).not.toThrowError(); 308 | }); 309 | 310 | it("should finish a span", () => { 311 | span.finish(); 312 | 313 | expect(zipkinTracer.scoped).toHaveBeenCalled(); 314 | zipkinTracer.scoped.mock.calls[0][0](); 315 | 316 | expect(zipkinTracer.setId).toHaveBeenCalled(); 317 | }); 318 | 319 | it("should send a server send annotation on finish if tracer is of kind server", () => { 320 | // Start a tracer 321 | tracer = new Tracer({ 322 | serviceName: "MyService", 323 | recorder: {}, 324 | kind: "server" 325 | }); 326 | zipkinTracer = tracer._zipkinTracer; 327 | zipkinTracer.scoped.mockReset(); 328 | 329 | // Start a span 330 | span = tracer.startSpan("Ponyfoo"); 331 | 332 | expect(zipkinTracer.scoped).toHaveBeenCalled(); 333 | zipkinTracer.scoped.mock.calls[0][0](); 334 | zipkinTracer.scoped.mockReset(); 335 | zipkinTracer.recordAnnotation.mockReset(); 336 | 337 | span.finish(); 338 | 339 | expect(zipkinTracer.scoped).toHaveBeenCalled(); 340 | zipkinTracer.scoped.mock.calls[0][0](); 341 | 342 | expect(zipkin.Annotation.ServerSend).toHaveBeenCalled(); 343 | expect(zipkinTracer.recordAnnotation).toHaveBeenCalledTimes(1); 344 | }); 345 | 346 | it("should send a client receive annotation on finish if tracer is of kind client", () => { 347 | // Start a tracer 348 | tracer = new Tracer({ 349 | serviceName: "MyService", 350 | recorder: {}, 351 | kind: "client" 352 | }); 353 | zipkinTracer = tracer._zipkinTracer; 354 | zipkinTracer.scoped.mockReset(); 355 | 356 | // Start a span 357 | span = tracer.startSpan("Ponyfoo"); 358 | 359 | expect(zipkinTracer.scoped).toHaveBeenCalled(); 360 | zipkinTracer.scoped.mock.calls[0][0](); 361 | zipkinTracer.scoped.mockReset(); 362 | zipkinTracer.recordAnnotation.mockReset(); 363 | 364 | span.finish(); 365 | 366 | expect(zipkinTracer.scoped).toHaveBeenCalled(); 367 | zipkinTracer.scoped.mock.calls[0][0](); 368 | 369 | expect(zipkin.Annotation.ClientRecv).toHaveBeenCalled(); 370 | expect(zipkinTracer.recordAnnotation).toHaveBeenCalledTimes(1); 371 | }); 372 | 373 | it("should use the right id in finish", () => { 374 | const otherSpan = tracer.startSpan("Other Span", { 375 | kind: "server" 376 | }); 377 | 378 | zipkinTracer.scoped.mockReset(); 379 | zipkinTracer.setId.mockReset(); 380 | span.finish(); 381 | 382 | // should do it in a scope 383 | expect(zipkinTracer.scoped).toHaveBeenCalled(); 384 | zipkinTracer.scoped.mock.calls[0][0](); 385 | expect(zipkinTracer.setId).toHaveBeenLastCalledWith(span.id); 386 | zipkinTracer.scoped.mockReset(); 387 | zipkinTracer.setId.mockReset(); 388 | 389 | otherSpan.finish(); 390 | // should do it in a scope 391 | expect(zipkinTracer.scoped).toHaveBeenCalled(); 392 | zipkinTracer.scoped.mock.calls[0][0](); 393 | expect(zipkinTracer.setId).toHaveBeenLastCalledWith(otherSpan.id); 394 | }); 395 | 396 | // TODO: supported tags: https://github.com/opentracing/specification/blob/master/semantic_conventions.md#span-tags-table 397 | // We might need to extend them in the future 398 | 399 | it("should set tag with an ip address", () => { 400 | span.setTag("peer.address", "128.167.178.4:5678"); 401 | 402 | // should do it in a scope 403 | expect(zipkinTracer.scoped).toHaveBeenCalled(); 404 | zipkinTracer.scoped.mock.calls[0][0](); 405 | 406 | expect(zipkin.Annotation.ServerAddr).toHaveBeenLastCalledWith({ 407 | serviceName: "MyService", 408 | host: new zipkin.InetAddress("128.167.178.4"), 409 | port: 5678 410 | }); 411 | expect(zipkinTracer.recordAnnotation).toHaveBeenCalledTimes(1); 412 | }); 413 | 414 | it("should set tag with an ip address with default port", () => { 415 | span.setTag("peer.address", "128.167.178.4"); 416 | 417 | // should do it in a scope 418 | expect(zipkinTracer.scoped).toHaveBeenCalled(); 419 | zipkinTracer.scoped.mock.calls[0][0](); 420 | 421 | expect(zipkin.Annotation.ServerAddr).toHaveBeenLastCalledWith({ 422 | serviceName: "MyService", 423 | host: new zipkin.InetAddress("128.167.178.4"), 424 | port: 80 425 | }); 426 | expect(zipkinTracer.recordAnnotation).toHaveBeenCalledTimes(1); 427 | }); 428 | 429 | it("should set arbitrary key/value tags", () => { 430 | span.setTag("ponies", 42); 431 | 432 | // should do it in a scope 433 | expect(zipkinTracer.scoped).toHaveBeenCalled(); 434 | zipkinTracer.scoped.mock.calls[0][0](); 435 | expect(zipkin.Annotation.BinaryAnnotation).toHaveBeenLastCalledWith( 436 | "ponies", 437 | 42 438 | ); 439 | expect(zipkinTracer.recordAnnotation).toHaveBeenCalledTimes(1); 440 | }); 441 | 442 | it("should fail set tag if key has wrong type of value", () => { 443 | expect(() => { 444 | span.setTag("peer.address", 42); 445 | 446 | // should do it in a scope 447 | expect(zipkinTracer.scoped).toHaveBeenCalled(); 448 | zipkinTracer.scoped.mock.calls[0][0](); 449 | }).toThrowErrorMatchingSnapshot(); 450 | }); 451 | 452 | it("should set tag for address on client", () => { 453 | // Set tracer 454 | tracer = new Tracer({ 455 | serviceName: "MyService", 456 | recorder: {}, 457 | kind: "client" 458 | }); 459 | zipkinTracer = tracer._zipkinTracer; 460 | zipkinTracer.scoped.mockReset(); 461 | 462 | // Set span 463 | span = tracer.startSpan("Ponyfoo"); 464 | 465 | expect(zipkinTracer.scoped).toHaveBeenCalled(); 466 | zipkinTracer.scoped.mock.calls[0][0](); 467 | zipkinTracer.scoped.mockReset(); 468 | zipkinTracer.recordAnnotation.mockReset(); 469 | 470 | // Actual test 471 | span.setTag("peer.address", "128.167.178.4:5678"); 472 | 473 | // should do it in a scope 474 | expect(zipkinTracer.scoped).toHaveBeenCalled(); 475 | zipkinTracer.scoped.mock.calls[0][0](); 476 | 477 | expect(zipkin.Annotation.ClientAddr).toHaveBeenLastCalledWith({ 478 | serviceName: "MyService", 479 | host: new zipkin.InetAddress("128.167.178.4"), 480 | port: 5678 481 | }); 482 | expect(zipkinTracer.recordAnnotation).toHaveBeenCalledTimes(1); 483 | }); 484 | }); 485 | 486 | it("should expose inject/extract formats", () => { 487 | expect(Tracer.FORMAT_TEXT_MAP).toBeDefined(); 488 | expect(Tracer.FORMAT_HTTP_HEADERS).toBeDefined(); 489 | expect(Tracer.FORMAT_BINARY).toBeDefined(); 490 | }); 491 | }); 492 | 493 | describe("usage of sampler", () => { 494 | it("tracer should be initialize zipkin with a sampler"); 495 | it("span should be aware of sampler"); 496 | it("startSpan with parent should use parents sample value"); 497 | }); 498 | 499 | describe("usage of recorder", () => { 500 | it("should initialize zipkin with the recorder provided", () => { 501 | new Tracer({ 502 | serviceName: "MyService", 503 | recorder: { id: 42 }, 504 | kind: "server" 505 | }); 506 | 507 | expect(zipkin.Tracer).toHaveBeenCalledWith({ 508 | ctxImpl: {}, 509 | recorder: { id: 42 }, 510 | sampler: expect.any(Sampler) 511 | }); 512 | }); 513 | }); 514 | 515 | describe("inject (serialize)", () => { 516 | let tracer; 517 | let zipkinTracer; 518 | beforeEach(() => { 519 | tracer = new Tracer({ 520 | serviceName: "MyService", 521 | recorder: {}, 522 | kind: "server" 523 | }); 524 | zipkinTracer = tracer._zipkinTracer; 525 | }); 526 | 527 | // not relevant for us 528 | describe("Text Map", () => { 529 | it("should throw an error, because it is unsupported", () => { 530 | const span = tracer.startSpan("Span"); 531 | 532 | expect(zipkinTracer.scoped).toHaveBeenCalled(); 533 | zipkinTracer.scoped.mock.calls[0][0](); 534 | 535 | expect(() => { 536 | const carrier = {}; 537 | tracer.inject(span, Tracer.FORMAT_TEXT_MAP, carrier); 538 | }).toThrowErrorMatchingSnapshot(); 539 | }); 540 | }); 541 | 542 | describe("HTTP Headers", () => { 543 | it("should throw without a span", () => { 544 | expect(() => { 545 | const carrier = {}; 546 | tracer.inject(undefined, Tracer.FORMAT_HTTP_HEADERS, carrier); 547 | }).toThrowErrorMatchingSnapshot(); 548 | }); 549 | 550 | it("should throw without a carrier", () => { 551 | const span = tracer.startSpan("Span"); 552 | 553 | expect(zipkinTracer.scoped).toHaveBeenCalled(); 554 | zipkinTracer.scoped.mock.calls[0][0](); 555 | expect(() => { 556 | tracer.inject(span, Tracer.FORMAT_HTTP_HEADERS); 557 | }).toThrowErrorMatchingSnapshot(); 558 | }); 559 | 560 | it("should set headers", () => { 561 | const span = tracer.startSpan("Span"); 562 | 563 | expect(zipkinTracer.scoped).toHaveBeenCalled(); 564 | zipkinTracer.scoped.mock.calls[0][0](); 565 | 566 | const carrier = { couldBe: "anything request related" }; 567 | tracer.inject(span, Tracer.FORMAT_HTTP_HEADERS, carrier); 568 | 569 | expect(carrier["x-b3-spanid"]).toBe(span.id.spanId); 570 | }); 571 | }); 572 | 573 | // not relevant for us 574 | describe("Binary", () => { 575 | it("should throw an error, because it is unsupported", () => { 576 | const span = tracer.startSpan("Span"); 577 | expect(() => { 578 | const carrier = {}; 579 | tracer.inject(span, Tracer.FORMAT_BINARY, carrier); 580 | }).toThrowErrorMatchingSnapshot(); 581 | }); 582 | }); 583 | }); 584 | 585 | describe("extract (deserialize)", () => { 586 | let tracer; 587 | let zipkinTracer; 588 | beforeEach(() => { 589 | tracer = new Tracer({ 590 | serviceName: "MyService", 591 | recorder: {}, 592 | kind: "client" 593 | }); 594 | zipkinTracer = tracer._zipkinTracer; 595 | }); 596 | 597 | // not relevant for us 598 | describe("Text Map", () => { 599 | it("should throw an error, because it is unsupported", () => { 600 | expect(() => { 601 | const carrier = {}; 602 | tracer.extract(Tracer.FORMAT_TEXT_MAP, carrier); 603 | }).toThrowErrorMatchingSnapshot(); 604 | }); 605 | }); 606 | 607 | describe("HTTP Headers", () => { 608 | it("should fail with an invalid carrier", () => { 609 | expect(() => { 610 | tracer.extract(Tracer.FORMAT_HTTP_HEADERS, true); 611 | }).toThrowErrorMatchingSnapshot(); 612 | }); 613 | 614 | it("should return null without http headers", () => { 615 | expect(() => { 616 | const span = tracer.extract(Tracer.FORMAT_HTTP_HEADERS, {}); 617 | expect(span).toBe(null); 618 | }).not.toThrowError(); 619 | }); 620 | 621 | it("should handle child spans correctly", () => { 622 | const httpHeaders = { 623 | "x-b3-traceid": "myTraceId", 624 | "x-b3-spanid": "mySpanId", 625 | "x-b3-sampled": "1" 626 | }; 627 | const span = tracer.extract(Tracer.FORMAT_HTTP_HEADERS, httpHeaders); 628 | expect(zipkinTracer.scoped).not.toHaveBeenCalled(); 629 | expect(zipkinTracer.recordAnnotation).not.toHaveBeenCalled(); 630 | expect(zipkinTracer.recordServiceName).not.toHaveBeenCalled(); 631 | 632 | expect(span.id.traceId.value).toBe("myTraceId"); 633 | expect(span.id.spanId).toBe("mySpanId"); 634 | expect(span.id.sampled.value).toBe("1"); 635 | expect(span.log).toBeInstanceOf(Function); 636 | expect(span.finish).toBeInstanceOf(Function); 637 | }); 638 | 639 | it("should handle parent spans correctly", () => { 640 | const httpHeaders = { 641 | "x-b3-traceid": "myTraceId", 642 | "x-b3-spanid": "mySpanId", 643 | "x-b3-parentspanid": "myParentSpanId", 644 | "x-b3-sampled": "1" 645 | }; 646 | const span = tracer.extract(Tracer.FORMAT_HTTP_HEADERS, httpHeaders); 647 | expect(zipkinTracer.scoped).not.toHaveBeenCalled(); 648 | expect(zipkinTracer.recordAnnotation).not.toHaveBeenCalled(); 649 | expect(zipkinTracer.recordServiceName).not.toHaveBeenCalled(); 650 | 651 | expect(span.id.traceId.value).toBe("myTraceId"); 652 | expect(span.id.spanId).toBe("mySpanId"); 653 | expect(span.id.parentId.value).toBe("myParentSpanId"); 654 | expect(span.id.sampled.value).toBe("1"); 655 | expect(span.log).toBeInstanceOf(Function); 656 | expect(span.finish).toBeInstanceOf(Function); 657 | }); 658 | }); 659 | 660 | // not relevant for us 661 | describe("Binary", () => { 662 | expect(() => { 663 | const carrier = {}; 664 | tracer.extract(Tracer.FORMAT_BINARY, carrier); 665 | }).toThrowErrorMatchingSnapshot(); 666 | }); 667 | }); 668 | 669 | describe("helpers", () => { 670 | describe("makeOptional", () => { 671 | let makeOptional; 672 | beforeEach(() => { 673 | makeOptional = Tracer.makeOptional; 674 | }); 675 | 676 | it("should return none if null is given", () => { 677 | expect(makeOptional(null).toString()).toBe("None"); 678 | }); 679 | 680 | it("should return some if something is given", () => { 681 | expect(makeOptional(3).toString()).toBe("Some(3)"); 682 | }); 683 | 684 | it("should not fail on double call", () => { 685 | expect(makeOptional(makeOptional(3)).toString()).toBe("Some(3)"); 686 | }); 687 | }); 688 | }); 689 | }); 690 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const { 2 | Annotation, 3 | BatchRecorder, 4 | ExplicitContext, 5 | TraceId, 6 | option: { Some, None }, 7 | Tracer, 8 | InetAddress, 9 | sampler, 10 | jsonEncoder 11 | } = require("zipkin"); 12 | const { HttpLogger } = require("zipkin-transport-http"); 13 | const availableTags = require("opentracing").Tags; 14 | const { 15 | FORMAT_BINARY, 16 | FORMAT_TEXT_MAP, 17 | FORMAT_HTTP_HEADERS 18 | } = require("opentracing"); 19 | const { JSON_V2 } = jsonEncoder; 20 | 21 | const HttpHeaders = { 22 | TraceId: "x-b3-traceid", 23 | ParentSpanId: "x-b3-parentspanid", 24 | SpanId: "x-b3-spanid", 25 | Sampled: "x-b3-sampled" 26 | }; 27 | 28 | const startSpanAnnotation = { 29 | client: Annotation.ClientSend, 30 | local: Annotation.ClientSend, // waiting for local PR in zipkin to get merged 31 | server: Annotation.ServerRecv 32 | }; 33 | 34 | const addressAnnotation = { 35 | client: Annotation.ClientAddr, 36 | local: Annotation.ClientAddr, // waiting for local PR in zipkin to get merged 37 | server: Annotation.ServerAddr 38 | }; 39 | 40 | const finishSpanAnnotation = { 41 | client: Annotation.ClientRecv, 42 | local: Annotation.ClientRecv, // waiting for local PR in zipkin to get merged 43 | server: Annotation.ServerSend 44 | }; 45 | 46 | // copied from https://github.com/openzipkin/zipkin-js/blob/08f86b63a5fd7ded60762f537be1845ede588ffa/packages/zipkin/src/tracer/randomTraceId.js 47 | function randomTraceId() { 48 | // === Generate a random 64-bit number in fixed-length hex format 49 | const digits = "0123456789abcdef"; 50 | let n = ""; 51 | for (let i = 0; i < 16; i++) { 52 | const rand = Math.floor(Math.random() * 16); 53 | n += digits[rand]; 54 | } 55 | return n; 56 | } 57 | 58 | function makeOptional(val) { 59 | if ( 60 | val && 61 | typeof val.toString === "function" && 62 | (val.toString().indexOf("Some") !== -1 || 63 | val.toString().indexOf("None") !== -1) 64 | ) { 65 | return val; 66 | } 67 | 68 | if (val != null) { 69 | return new Some(val); 70 | } else { 71 | return None; 72 | } 73 | } 74 | 75 | function SpanCreator({ tracer, serviceName, kind }) { 76 | return class Span { 77 | _constructedFromOutside(options) { 78 | return ( 79 | typeof options.traceId === "object" && 80 | typeof options.traceId.spanId === "string" 81 | ); 82 | } 83 | _getTraceId(options) { 84 | // construct from give traceId 85 | if (this._constructedFromOutside(options)) { 86 | const { traceId, parentId, spanId, sampled } = options.traceId; 87 | return new TraceId({ 88 | traceId: makeOptional(traceId), 89 | parentId: makeOptional(parentId), 90 | spanId: spanId, 91 | sampled: makeOptional(sampled) 92 | }); 93 | } 94 | 95 | // construct with parent 96 | if (options.childOf !== null && typeof options.childOf === "object") { 97 | const parent = options.childOf; 98 | 99 | return new TraceId({ 100 | traceId: makeOptional(parent.id.traceId), 101 | parentId: makeOptional(parent.id.spanId), 102 | spanId: randomTraceId(), 103 | sampled: parent.id.sampled 104 | }); 105 | } 106 | 107 | // construct from give traceId 108 | return tracer.createRootId(); 109 | } 110 | 111 | constructor(spanName, options) { 112 | const id = this._getTraceId(options); 113 | this.id = id; 114 | 115 | if (!this._constructedFromOutside(options)) { 116 | tracer.scoped(() => { 117 | tracer.setId(id); 118 | if (spanName) { 119 | tracer.recordAnnotation(new Annotation.Rpc(spanName)); 120 | } 121 | 122 | tracer.recordServiceName(serviceName); 123 | tracer.recordAnnotation(new startSpanAnnotation[kind]()); 124 | }); 125 | } 126 | } 127 | 128 | log(obj) { 129 | tracer.scoped(() => { 130 | // make sure correct id is set 131 | if (obj) { 132 | tracer.setId(this.id); 133 | tracer.recordMessage(obj); 134 | } 135 | }); 136 | } 137 | setTag(key, value) { 138 | tracer.scoped(() => { 139 | // make sure correct id is set 140 | tracer.setId(this.id); 141 | 142 | // some tags are treated specially by Zipkin 143 | switch (key) { 144 | case availableTags.PEER_ADDRESS: 145 | if (typeof value !== "string") { 146 | throw new Error( 147 | `Tag ${availableTags.PEER_ADDRESS} needs a string` 148 | ); 149 | } 150 | 151 | const host = new InetAddress(value.split(":")[0]); 152 | const port = value.split(":")[1] 153 | ? parseInt(value.split(":")[1], 10) 154 | : 80; 155 | 156 | const address = { 157 | serviceName, 158 | host: host, 159 | port: port 160 | }; 161 | 162 | tracer.recordAnnotation(new addressAnnotation[kind](address)); 163 | break; 164 | 165 | // Otherwise, set arbitrary key/value tags using Zipkin binary annotations 166 | default: 167 | tracer.recordAnnotation( 168 | new Annotation.BinaryAnnotation(key, value) 169 | ); 170 | } 171 | }); 172 | } 173 | 174 | context() { 175 | const { spanId, traceId } = this.id; 176 | return { 177 | toSpanId() { 178 | return spanId; 179 | }, 180 | toTraceId() { 181 | return traceId; 182 | } 183 | }; 184 | } 185 | 186 | finish() { 187 | tracer.scoped(() => { 188 | // make sure correct id is set 189 | tracer.setId(this.id); 190 | tracer.recordAnnotation(new finishSpanAnnotation[kind]()); 191 | }); 192 | } 193 | }; 194 | } 195 | 196 | class Tracing { 197 | constructor(options = {}) { 198 | // serviceName: the name of the service monitored with this tracer 199 | if (typeof options.serviceName !== "string") { 200 | throw new Error("serviceName option needs to be provided"); 201 | } 202 | 203 | if (typeof options.recorder !== "object") { 204 | if (typeof options.endpoint !== "string") { 205 | throw new Error("recorder or endpoint option needs to be provided"); 206 | } 207 | 208 | if (options.endpoint.indexOf("http") === -1) { 209 | throw new Error( 210 | "endpoint value needs to start with http:// or https://" 211 | ); 212 | } 213 | 214 | options.recorder = new BatchRecorder({ 215 | logger: new HttpLogger({ 216 | endpoint: options.endpoint + "/api/v2/spans", 217 | jsonEncoder: JSON_V2 218 | }) 219 | }); 220 | } 221 | 222 | if ( 223 | options.kind !== "client" && 224 | options.kind !== "server" && 225 | options.kind !== "local" 226 | ) { 227 | throw new Error( 228 | 'kind option needs to be provided as either "local", "client" or "server"' 229 | ); 230 | } 231 | 232 | options.sampler = 233 | options.sampler || new sampler.Sampler(sampler.alwaysSample); 234 | 235 | this._serviceName = options.serviceName; 236 | 237 | this._zipkinTracer = new Tracer({ 238 | ctxImpl: new ExplicitContext(), 239 | recorder: options.recorder, 240 | sampler: options.sampler 241 | }); 242 | this._Span = SpanCreator({ 243 | tracer: this._zipkinTracer, 244 | serviceName: this._serviceName, 245 | kind: options.kind 246 | }); 247 | } 248 | 249 | startSpan(name, options = {}) { 250 | if (typeof name !== "string") { 251 | throw new Error( 252 | `startSpan needs an operation name as string as first argument. 253 | For more details, please see https://github.com/opentracing/specification/blob/master/specification.md#start-a-new-span` 254 | ); 255 | } 256 | 257 | return new this._Span(name, options); 258 | } 259 | 260 | inject(span, format, carrier) { 261 | if (typeof span !== "object") { 262 | throw new Error("inject called without a span"); 263 | } 264 | 265 | if (format !== Tracing.FORMAT_HTTP_HEADERS) { 266 | throw new Error("inject called with unsupported format"); 267 | } 268 | 269 | if (typeof carrier !== "object") { 270 | throw new Error("inject called without a carrier object"); 271 | } 272 | 273 | carrier[HttpHeaders.TraceId] = span.id.traceId; 274 | carrier[HttpHeaders.SpanId] = span.id.spanId; 275 | carrier[HttpHeaders.ParentSpanId] = span.id.parentId; 276 | carrier[HttpHeaders.Sampled] = span.id.sampled.getOrElse("0"); 277 | } 278 | 279 | extract(format, carrier) { 280 | if (format !== Tracing.FORMAT_HTTP_HEADERS) { 281 | throw new Error("extract called with unsupported format"); 282 | } 283 | 284 | if (typeof carrier !== "object") { 285 | throw new Error("extract called without a carrier"); 286 | } 287 | 288 | if (!carrier[HttpHeaders.TraceId]) { 289 | return null; 290 | } 291 | 292 | // XXX: no empty string here v 293 | // We should send the span name too 294 | // TODO: take a look for span name here: https://github.com/openzipkin/zipkin-go-opentracing/blob/594640b9ef7e5c994e8d9499359d693c032d738c/propagation_ot.go#L26 295 | const span = new this._Span("", { 296 | traceId: { 297 | traceId: carrier[HttpHeaders.TraceId], 298 | parentId: carrier[HttpHeaders.ParentSpanId], 299 | spanId: carrier[HttpHeaders.SpanId], 300 | sampled: carrier[HttpHeaders.Sampled] 301 | } 302 | }); 303 | 304 | return span; 305 | } 306 | } 307 | 308 | // These values should match https://github.com/opentracing/opentracing-javascript/blob/master/src/constants.ts 309 | Tracing.FORMAT_TEXT_MAP = FORMAT_TEXT_MAP; 310 | Tracing.FORMAT_HTTP_HEADERS = FORMAT_HTTP_HEADERS; 311 | Tracing.FORMAT_BINARY = FORMAT_BINARY; 312 | 313 | // For testing purposes 314 | Tracing.makeOptional = makeOptional; 315 | 316 | module.exports = Tracing; 317 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | lib/core/metadata.js 4 | lib/core/MetadataBlog.js 5 | website/translated_docs 6 | website/build/ 7 | website/yarn.lock 8 | website/node_modules 9 | 10 | website/i18n/* 11 | !website/i18n/en.json 12 | -------------------------------------------------------------------------------- /website/core/Footer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | const React = require("react"); 9 | 10 | class Footer extends React.Component { 11 | render() { 12 | const currentYear = new Date().getFullYear(); 13 | return ( 14 |
15 |
16 |
17 |
More
18 | Blog 19 | GitHub 20 | 29 | Star 30 | 31 |
32 |
33 | 34 |
35 | Copyright © {currentYear} Daniel Schmidt 36 |
37 |
38 | ); 39 | } 40 | } 41 | 42 | module.exports = Footer; 43 | -------------------------------------------------------------------------------- /website/i18n/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "_comment": "This file is auto-generated by write-translations.js", 3 | "localized-strings": { 4 | "next": "Next", 5 | "previous": "Previous", 6 | "tagline": "Opentracing on the streets, Zipkin in the sheets", 7 | "examples": "Examples", 8 | "Examples": "Examples", 9 | "Docusaurus": "Docusaurus", 10 | "First Category": "First Category", 11 | "Second Category": "Second Category" 12 | }, 13 | "pages-strings": { 14 | "Help Translate|recruit community translators for your project": 15 | "Help Translate", 16 | "Edit this Doc|recruitment message asking to edit the doc source": "Edit", 17 | "Translate this Doc|recruitment message asking to translate the docs": 18 | "Translate" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "examples": "docusaurus-examples", 4 | "start": "docusaurus-start", 5 | "build": "docusaurus-build", 6 | "publish-gh-pages": "docusaurus-publish", 7 | "write-translations": "docusaurus-write-translations", 8 | "version": "docusaurus-version", 9 | "rename-version": "docusaurus-rename-version" 10 | }, 11 | "devDependencies": { 12 | "docusaurus": "1.0.10" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /website/pages/en/help.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | const React = require("react"); 9 | 10 | const CompLibrary = require("../../core/CompLibrary.js"); 11 | const Container = CompLibrary.Container; 12 | const GridBlock = CompLibrary.GridBlock; 13 | 14 | const siteConfig = require(process.cwd() + "/siteConfig.js"); 15 | 16 | class Help extends React.Component { 17 | render() { 18 | const supportLinks = [ 19 | { 20 | content: 21 | "Learn more using the [documentation on this site.](/test-site/docs/en/doc1.html)", 22 | title: "Browse Docs" 23 | }, 24 | { 25 | content: "Ask questions about the documentation and project", 26 | title: "Join the community" 27 | }, 28 | { 29 | content: "Find out what's new with this project", 30 | title: "Stay up to date" 31 | } 32 | ]; 33 | 34 | return ( 35 |
36 | 37 |
38 |
39 |

Need help?

40 |
41 |

This project is maintained by a dedicated group of people.

42 | 43 |
44 |
45 |
46 | ); 47 | } 48 | } 49 | 50 | module.exports = Help; 51 | -------------------------------------------------------------------------------- /website/pages/en/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | const React = require("react"); 9 | 10 | const CompLibrary = require("../../core/CompLibrary.js"); 11 | const MarkdownBlock = CompLibrary.MarkdownBlock; /* Used to read markdown */ 12 | const Container = CompLibrary.Container; 13 | const GridBlock = CompLibrary.GridBlock; 14 | 15 | const siteConfig = require(process.cwd() + "/siteConfig.js"); 16 | 17 | class Button extends React.Component { 18 | render() { 19 | return ( 20 |
21 | 22 | {this.props.children} 23 | 24 |
25 | ); 26 | } 27 | } 28 | 29 | Button.defaultProps = { 30 | target: "_self" 31 | }; 32 | 33 | class HomeSplash extends React.Component { 34 | render() { 35 | return ( 36 |
37 |
38 |
39 |
40 |
41 |

42 | {siteConfig.title} 43 | {siteConfig.tagline} 44 |

45 |
46 |
47 |
48 | 49 | 52 | 55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | ); 63 | } 64 | } 65 | 66 | class Index extends React.Component { 67 | render() { 68 | let language = this.props.language || "en"; 69 | const showcase = siteConfig.users 70 | .filter(user => { 71 | return user.pinned; 72 | }) 73 | .map(user => { 74 | return ( 75 | 76 | 77 | 78 | ); 79 | }); 80 | 81 | return ( 82 |
83 | 84 |
85 | 86 | 107 | 108 | 109 | 110 | 124 | 125 | 126 |
127 |

{"Who's Using This?"}

128 |

This project is used by all these people

129 |
{showcase}
130 |
131 | Do you want your project to be listed, too? Send in an{" "} 132 | 133 | issue 134 | . 135 |
136 |
137 |
138 |
139 | ); 140 | } 141 | } 142 | 143 | module.exports = Index; 144 | -------------------------------------------------------------------------------- /website/pages/en/users.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | const React = require("react"); 9 | 10 | const CompLibrary = require("../../core/CompLibrary.js"); 11 | const Container = CompLibrary.Container; 12 | 13 | const siteConfig = require(process.cwd() + "/siteConfig.js"); 14 | 15 | class Users extends React.Component { 16 | render() { 17 | const showcase = siteConfig.users.map((user, i) => { 18 | return ( 19 | 20 | 21 | 22 | ); 23 | }); 24 | 25 | return ( 26 |
27 | 28 |
29 |
30 |

Who's Using This?

31 |

This project is used by many folks

32 |
33 |
{showcase}
34 |

Are you using this project?

35 | 39 | Add your company 40 | 41 |
42 |
43 |
44 | ); 45 | } 46 | } 47 | 48 | module.exports = Users; 49 | -------------------------------------------------------------------------------- /website/sidebars.json: -------------------------------------------------------------------------------- 1 | { 2 | "docs": { 3 | "Docusaurus": ["doc1"], 4 | "First Category": ["doc2"], 5 | "Second Category": ["doc3"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /website/siteConfig.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | /* List of projects/orgs using your project for the users page */ 9 | const users = [ 10 | { 11 | caption: "AIDA", 12 | image: 13 | "https://www.aida.de/typo3conf/ext/aid_distribution_aida/Resources/Public/v8/Images/AIDA-Logo.svg", 14 | infoLink: "https://www.aida.de", 15 | pinned: true 16 | } 17 | ]; 18 | 19 | const siteConfig = { 20 | title: "zipkin-javascript-opentracing" /* title for your website */, 21 | tagline: "Opentracing on the streets, Zipkin in the sheets", 22 | url: "https://danielmschmidt.github.io" /* your website url */, 23 | baseUrl: "/zipkin-javascript-opentracing/" /* base url for your project */, 24 | projectName: "zipkin-javascript-opentracing", 25 | headerLinks: [{ doc: "examples", label: "Examples" }], 26 | users, 27 | /* path to images for header/footer */ 28 | headerIcon: "img/opentracing.svg", 29 | footerIcon: "img/opentracing.svg", 30 | favicon: "img/favicon.png", 31 | /* colors for website */ 32 | colors: { 33 | primaryColor: "#68bfdb", 34 | secondaryColor: "#FFF" 35 | }, 36 | // This copyright info is used in /core/Footer.js and blog rss/atom feeds. 37 | copyright: "Copyright © " + new Date().getFullYear() + " Daniel Schmidt", 38 | organizationName: "DanielMSchmidt", // or set an env variable ORGANIZATION_NAME 39 | projectName: "zipkin-javascript-opentracing", // or set an env variable PROJECT_NAME 40 | highlight: { 41 | // Highlight.js theme to use for syntax highlighting in code blocks 42 | theme: "default" 43 | }, 44 | scripts: ["https://buttons.github.io/buttons.js"], 45 | // You may provide arbitrary config keys to be used as needed by your template. 46 | repoUrl: "https://github.com/danielmschmidt/zipkin-javascript-opentracing" 47 | }; 48 | 49 | module.exports = siteConfig; 50 | -------------------------------------------------------------------------------- /website/static/css/custom.css: -------------------------------------------------------------------------------- 1 | /* your custom css */ 2 | 3 | @media only screen and (min-device-width: 360px) and (max-device-width: 736px) { 4 | } 5 | 6 | @media only screen and (min-width: 1024px) { 7 | } 8 | 9 | @media only screen and (max-width: 1023px) { 10 | } 11 | 12 | @media only screen and (min-width: 1400px) { 13 | } 14 | 15 | @media only screen and (min-width: 1500px) { 16 | } 17 | -------------------------------------------------------------------------------- /website/static/img/docusaurus.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /website/static/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielMSchmidt/zipkin-javascript-opentracing/62bcc833a5f94ac0bc64af19cd2264a2c92cdef1/website/static/img/favicon.png -------------------------------------------------------------------------------- /website/static/img/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielMSchmidt/zipkin-javascript-opentracing/62bcc833a5f94ac0bc64af19cd2264a2c92cdef1/website/static/img/favicon/favicon.ico -------------------------------------------------------------------------------- /website/static/img/opentracing.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 21 | 24 | 27 | 29 | 31 | 35 | 37 | 41 | 42 | 44 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /website/static/img/openzipkin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielMSchmidt/zipkin-javascript-opentracing/62bcc833a5f94ac0bc64af19cd2264a2c92cdef1/website/static/img/openzipkin.jpg -------------------------------------------------------------------------------- /website/static/img/oss_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielMSchmidt/zipkin-javascript-opentracing/62bcc833a5f94ac0bc64af19cd2264a2c92cdef1/website/static/img/oss_logo.png --------------------------------------------------------------------------------