Skip to content

Commit

Permalink
Zipkin Exporter (#192)
Browse files Browse the repository at this point in the history
* feat(zipkin-exporter): implement

* fix(zipkin): use time fns

* refactor(exporter-zipkin): linter fix
  • Loading branch information
Peter Marton authored and mayurkale22 committed Sep 19, 2019
1 parent 7fc72dc commit b58965c
Show file tree
Hide file tree
Showing 8 changed files with 1,069 additions and 0 deletions.
6 changes: 6 additions & 0 deletions packages/opentelemetry-exporter-zipkin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"tdd": "yarn test -- --watch-extensions ts --watch",
"clean": "rimraf build/*",
"check": "gts check",
"codecov": "nyc report --reporter=json && codecov -f coverage/*.json -p ../../",
"compile": "tsc -p .",
"fix": "gts fix"
},
Expand All @@ -35,10 +36,12 @@
},
"devDependencies": {
"@types/mocha": "^5.2.7",
"@types/nock": "^10.0.3",
"@types/node": "^12.6.9",
"codecov": "^3.5.0",
"gts": "^1.1.0",
"mocha": "^6.2.0",
"nock": "^10.0.6",
"nyc": "^14.1.1",
"tslint-microsoft-contrib": "^6.2.0",
"tslint-consistent-codestyle":"^1.15.1",
Expand All @@ -47,5 +50,8 @@
"typescript": "^3.5.3"
},
"dependencies": {
"@opentelemetry/core": "^0.0.1",
"@opentelemetry/basic-tracer": "^0.0.1",
"@opentelemetry/types": "^0.0.1"
}
}
2 changes: 2 additions & 0 deletions packages/opentelemetry-exporter-zipkin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

export * from './zipkin';
95 changes: 95 additions & 0 deletions packages/opentelemetry-exporter-zipkin/src/transform.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*!
* Copyright 2019, OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as types from '@opentelemetry/types';
import { ReadableSpan } from '@opentelemetry/basic-tracer';
import { hrTimeToMilliseconds } from '@opentelemetry/core';
import * as zipkinTypes from './types';

const ZIPKIN_SPAN_KIND_MAPPING = {
[types.SpanKind.CLIENT]: zipkinTypes.SpanKind.CLIENT,
[types.SpanKind.SERVER]: zipkinTypes.SpanKind.SERVER,
[types.SpanKind.CONSUMER]: zipkinTypes.SpanKind.CONSUMER,
[types.SpanKind.PRODUCER]: zipkinTypes.SpanKind.PRODUCER,
// When absent, the span is local.
[types.SpanKind.INTERNAL]: undefined,
};

export const statusCodeTagName = 'ot.status_code';
export const statusDescriptionTagName = 'ot.status_description';

/**
* Translate OpenTelemetry ReadableSpan to ZipkinSpan format
* @param span Span to be translated
*/
export function toZipkinSpan(
span: ReadableSpan,
serviceName: string,
statusCodeTagName: string,
statusDescriptionTagName: string
): zipkinTypes.Span {
const zipkinSpan: zipkinTypes.Span = {
traceId: span.spanContext.traceId,
parentId: span.parentSpanId,
name: span.name,
id: span.spanContext.spanId,
kind: ZIPKIN_SPAN_KIND_MAPPING[span.kind],
timestamp: hrTimeToMilliseconds(span.startTime),
duration: hrTimeToMilliseconds(span.duration),
localEndpoint: { serviceName },
tags: _toZipkinTags(
span.attributes,
span.status,
statusCodeTagName,
statusDescriptionTagName
),
annotations: span.events.length
? _toZipkinAnnotations(span.events)
: undefined,
};

return zipkinSpan;
}

/** Converts OpenTelemetry Attributes and Status to Zipkin Tags format. */
export function _toZipkinTags(
attributes: types.Attributes,
status: types.Status,
statusCodeTagName: string,
statusDescriptionTagName: string
): zipkinTypes.Tags {
const tags: { [key: string]: string } = {};
for (const key of Object.keys(attributes)) {
tags[key] = String(attributes[key]);
}
tags[statusCodeTagName] = String(types.CanonicalCode[status.code]);
if (status.message) {
tags[statusDescriptionTagName] = status.message;
}
return tags;
}

/**
* Converts OpenTelemetry Events to Zipkin Annotations format.
*/
export function _toZipkinAnnotations(
events: types.TimedEvent[]
): zipkinTypes.Annotation[] {
return events.map(event => ({
timestamp: hrTimeToMilliseconds(event.time),
value: event.name,
}));
}
180 changes: 180 additions & 0 deletions packages/opentelemetry-exporter-zipkin/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/*!
* Copyright 2019, OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as types from '@opentelemetry/types';

/**
* Exporter config
*/
export interface ExporterConfig {
logger?: types.Logger;
serviceName: string;
url?: string;
// Initiates a request with spans in memory to the backend.
forceFlush?: boolean;
// Optional mapping overrides for OpenTelemetry status code and description.
statusCodeTagName?: string;
statusDescriptionTagName?: string;
}

/**
* Zipkin Span
* @see https://github.com/openzipkin/zipkin-api/blob/master/zipkin2-api.yaml
*/
export interface Span {
/**
* Trace identifier, set on all spans within it.
*/
traceId: string;
/**
* The logical operation this span represents in lowercase (e.g. rpc method).
* Leave absent if unknown.
*/
name: string;
/**
* The parent span ID or absent if this the root span in a trace.
*/
parentId?: string;
/**
* Unique 64bit identifier for this operation within the trace.
*/
id: string;
/**
* When present, kind clarifies timestamp, duration and remoteEndpoint.
* When absent, the span is local or incomplete.
*/
kind?: SpanKind;
/**
* Epoch microseconds of the start of this span, possibly absent if
* incomplete.
*/
timestamp: number;
/**
* Duration in microseconds of the critical path, if known.
*/
duration: number;
/**
* True is a request to store this span even if it overrides sampling policy.
* This is true when the `X-B3-Flags` header has a value of 1.
*/
debug?: boolean;
/**
* True if we are contributing to a span started by another tracer (ex on a
* different host).
*/
shared?: boolean;
/**
* The host that recorded this span, primarily for query by service name.
*/
localEndpoint: Endpoint;
/**
* Associates events that explain latency with the time they happened.
*/
annotations?: Annotation[];
/**
* Tags give your span context for search, viewing and analysis.
*/
tags: Tags;
/**
* TODO: `remoteEndpoint`, do we need to support it?
* When an RPC (or messaging) span, indicates the other side of the
* connection.
*/
}

/**
* Associates an event that explains latency with a timestamp.
* Unlike log statements, annotations are often codes. Ex. "ws" for WireSend
* Zipkin v1 core annotations such as "cs" and "sr" have been replaced with
* Span.Kind, which interprets timestamp and duration.
*/
export interface Annotation {
/**
* Epoch microseconds of this event.
* For example, 1502787600000000 corresponds to 2017-08-15 09:00 UTC
*/
timestamp: number;
/**
* Usually a short tag indicating an event, like "error"
* While possible to add larger data, such as garbage collection details, low
* cardinality event names both keep the size of spans down and also are easy
* to search against.
*/
value: string;
}

/**
* The network context of a node in the service graph.
*/
export interface Endpoint {
/**
* Lower-case label of this node in the service graph, such as "favstar".
* Leave absent if unknown.
* This is a primary label for trace lookup and aggregation, so it should be
* intuitive and consistent. Many use a name from service discovery.
*/
serviceName?: string;
/**
* The text representation of the primary IPv4 address associated with this
* connection. Ex. 192.168.99.100 Absent if unknown.
*/
ipv4?: string;
/**
* The text representation of the primary IPv6 address associated with a
* connection. Ex. 2001:db8::c001 Absent if unknown.
* Prefer using the ipv4 field for mapped addresses.
*/
port?: number;
}

/**
* Adds context to a span, for search, viewing and analysis.
* For example, a key "your_app.version" would let you lookup traces by version.
* A tag "sql.query" isn't searchable, but it can help in debugging when viewing
* a trace.
*/
export interface Tags {
[tagKey: string]: unknown;
}

/**
* When present, kind clarifies timestamp, duration and remoteEndpoint. When
* absent, the span is local or incomplete. Unlike client and server, there
* is no direct critical path latency relationship between producer and
* consumer spans.
* `CLIENT`
* timestamp is the moment a request was sent to the server.
* duration is the delay until a response or an error was received.
* remoteEndpoint is the server.
* `SERVER`
* timestamp is the moment a client request was received.
* duration is the delay until a response was sent or an error.
* remoteEndpoint is the client.
* `PRODUCER`
* timestamp is the moment a message was sent to a destination.
* duration is the delay sending the message, such as batching.
* remoteEndpoint is the broker.
* `CONSUMER`
* timestamp is the moment a message was received from an origin.
* duration is the delay consuming the message, such as from backlog.
* remoteEndpoint - Represents the broker. Leave serviceName absent if unknown.
*/
export enum SpanKind {
CLIENT = 'CLIENT',
SERVER = 'SERVER',
CONSUMER = 'CONSUMER',
PRODUCER = 'PRODUCER',
}
Loading

0 comments on commit b58965c

Please sign in to comment.