export resource to exporters (#843)

* export resource to exporters

* update Zipkin and Stackdriver exporter to use Resource

* chore: remove redundant dependency

* update Collector exporter to use Resource

* rebase with #846

* minor

* fix collector resource

* fix build
This commit is contained in:
Mayur Kale 2020-03-12 16:03:03 -07:00 committed by GitHub
parent 02c1d66b76
commit 4627892c4a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 183 additions and 44 deletions

View File

@ -50,7 +50,6 @@
},
"devDependencies": {
"@babel/core": "^7.6.0",
"@opentelemetry/resources": "^0.4.0",
"@types/mocha": "^5.2.5",
"@types/node": "^12.6.8",
"@types/sinon": "^7.0.13",
@ -83,6 +82,7 @@
"@opentelemetry/api": "^0.4.0",
"@opentelemetry/base": "^0.4.0",
"@opentelemetry/core": "^0.4.0",
"@opentelemetry/resources": "^0.4.0",
"@opentelemetry/tracing": "^0.4.0"
}
}

View File

@ -19,8 +19,9 @@ import { NoopLogger } from '@opentelemetry/core';
import { ReadableSpan, SpanExporter } from '@opentelemetry/tracing';
import { Attributes, Logger } from '@opentelemetry/api';
import * as collectorTypes from './types';
import { toCollectorSpan } from './transform';
import { toCollectorSpan, toCollectorResource } from './transform';
import { onInit, onShutdown, sendSpans } from './platform/index';
import { Resource } from '@opentelemetry/resources';
/**
* Collector Exporter Config
@ -100,10 +101,13 @@ export class CollectorExporter implements SpanExporter {
toCollectorSpan(span)
);
this.logger.debug('spans to be sent', spansToBeSent);
const resource = toCollectorResource(
spansToBeSent.length > 0 ? spans[0].resource : Resource.empty()
);
// Send spans to [opentelemetry collector]{@link https://github.com/open-telemetry/opentelemetry-collector}
// it will use the appropriate transport layer automatically depends on platform
sendSpans(spansToBeSent, resolve, reject, this);
sendSpans(spansToBeSent, resolve, reject, this, resource);
} catch (e) {
reject(e);
}

View File

@ -43,12 +43,14 @@ export function onShutdown(shutdownF: EventListener) {
* @param onSuccess
* @param onError
* @param collectorExporter
* @param resource
*/
export function sendSpans(
spans: collectorTypes.Span[],
onSuccess: () => void,
onError: (status?: number) => void,
collectorExporter: CollectorExporter
collectorExporter: CollectorExporter,
resource: collectorTypes.Resource
) {
const exportTraceServiceRequest: collectorTypes.ExportTraceServiceRequest = {
node: {
@ -66,7 +68,7 @@ export function sendSpans(
},
attributes: collectorExporter.attributes,
},
// resource: '', not implemented
resource,
spans,
};

View File

@ -47,32 +47,20 @@ export function onShutdown(shutdownF: Function) {}
* @param onSuccess
* @param onError
* @param collectorExporter
* @param resource
*/
export function sendSpans(
spans: collectorTypes.Span[],
onSuccess: () => void,
onError: (status?: number) => void,
collectorExporter: CollectorExporter
collectorExporter: CollectorExporter,
resource: collectorTypes.Resource
) {
const exportTraceServiceRequest: collectorTypes.ExportTraceServiceRequest = {
node: {
identifier: {
hostName: collectorExporter.hostName,
startTimestamp: core.hrTimeToTimeStamp(core.hrTime()),
},
libraryInfo: {
language: collectorTypes.LibraryInfoLanguage.NODE_JS,
coreLibraryVersion: core.VERSION,
exporterVersion: VERSION,
},
serviceInfo: {
name: collectorExporter.serviceName,
},
attributes: collectorExporter.attributes,
},
// resource: '', not implemented
const exportTraceServiceRequest = toCollectorTraceServiceRequest(
spans,
};
collectorExporter,
resource
);
const body = JSON.stringify(exportTraceServiceRequest);
const parsedUrl = url.parse(collectorExporter.url);
@ -105,3 +93,29 @@ export function sendSpans(
req.write(body);
req.end();
}
export function toCollectorTraceServiceRequest(
spans: collectorTypes.Span[],
collectorExporter: CollectorExporter,
resource: collectorTypes.Resource
): collectorTypes.ExportTraceServiceRequest {
return {
node: {
identifier: {
hostName: collectorExporter.hostName,
startTimestamp: core.hrTimeToTimeStamp(core.hrTime()),
},
libraryInfo: {
language: collectorTypes.LibraryInfoLanguage.NODE_JS,
coreLibraryVersion: core.VERSION,
exporterVersion: VERSION,
},
serviceInfo: {
name: collectorExporter.serviceName,
},
attributes: collectorExporter.attributes,
},
resource,
spans,
};
}

View File

@ -18,6 +18,7 @@ import { hexToBase64, hrTimeToTimeStamp } from '@opentelemetry/core';
import { ReadableSpan } from '@opentelemetry/tracing';
import { Attributes, Link, TimedEvent, TraceState } from '@opentelemetry/api';
import * as collectorTypes from './types';
import { Resource } from '@opentelemetry/resources';
const OT_MAX_STRING_LENGTH = 128;
@ -201,6 +202,21 @@ export function toCollectorSpan(span: ReadableSpan): collectorTypes.Span {
};
}
/**
* converts span resource
* @param resource
*/
export function toCollectorResource(
resource: Resource
): collectorTypes.Resource {
const labels: { [key: string]: string } = {};
Object.keys(resource.labels).forEach(
name => (labels[name] = String(resource.labels[name]))
);
// @TODO: add type support
return { labels };
}
/**
* @param traceState
*/

View File

@ -18,6 +18,7 @@ import { Attributes, TimedEvent } from '@opentelemetry/api';
import * as assert from 'assert';
import * as transform from '../../src/transform';
import { ensureSpanIsCorrect, mockedReadableSpan } from '../helper';
import { Resource } from '@opentelemetry/resources';
describe('transform', () => {
describe('toCollectorTruncatableString', () => {
@ -149,4 +150,23 @@ describe('transform', () => {
ensureSpanIsCorrect(transform.toCollectorSpan(mockedReadableSpan));
});
});
describe('toCollectorResource', () => {
it('should convert resource', () => {
const resource = transform.toCollectorResource(
new Resource({
service: 'ui',
version: 1.0,
success: true,
})
);
assert.deepStrictEqual(resource, {
labels: {
service: 'ui',
version: '1',
success: 'true',
},
});
});
});
});

View File

@ -69,7 +69,11 @@ export const mockedReadableSpan: ReadableSpan = {
},
],
duration: [0, 8885000],
resource: Resource.empty(),
resource: new Resource({
service: 'ui',
version: 1,
cost: 112.12,
}),
};
export function ensureSpanIsCorrect(span: collectorTypes.Span) {

View File

@ -64,6 +64,12 @@ export function spanToThrift(span: ReadableSpan): ThriftSpan {
if (span.kind !== undefined) {
tags.push({ key: 'span.kind', value: SpanKind[span.kind] });
}
Object.keys(span.resource.labels).forEach(name =>
tags.push({
key: name,
value: toTagValue(span.resource.labels[name]),
})
);
const spanTags: ThriftTag[] = ThriftUtils.getThriftTags(tags);

View File

@ -70,7 +70,11 @@ describe('transform', () => {
},
],
duration: [32, 800000000],
resource: Resource.empty(),
resource: new Resource({
service: 'ui',
version: 1,
cost: 112.12,
}),
};
const thriftSpan = spanToThrift(readableSpan);
@ -95,8 +99,18 @@ describe('transform', () => {
thriftSpan.startTime,
Utils.encodeInt64(hrTimeToMicroseconds(readableSpan.startTime))
);
assert.strictEqual(thriftSpan.tags.length, 6);
const [tag1, tag2, tag3, tag4, tag5, tag6] = thriftSpan.tags;
assert.strictEqual(thriftSpan.tags.length, 9);
const [
tag1,
tag2,
tag3,
tag4,
tag5,
tag6,
tag7,
tag8,
tag9,
] = thriftSpan.tags;
assert.strictEqual(tag1.key, 'testBool');
assert.strictEqual(tag1.vType, 'BOOL');
assert.strictEqual(tag1.vBool, true);
@ -115,6 +129,15 @@ describe('transform', () => {
assert.strictEqual(tag6.key, 'span.kind');
assert.strictEqual(tag6.vType, 'STRING');
assert.strictEqual(tag6.vStr, 'INTERNAL');
assert.strictEqual(tag7.key, 'service');
assert.strictEqual(tag7.vType, 'STRING');
assert.strictEqual(tag7.vStr, 'ui');
assert.strictEqual(tag8.key, 'version');
assert.strictEqual(tag8.vType, 'DOUBLE');
assert.strictEqual(tag8.vDouble, 1);
assert.strictEqual(tag9.key, 'cost');
assert.strictEqual(tag9.vType, 'DOUBLE');
assert.strictEqual(tag9.vDouble, 112.12);
assert.strictEqual(thriftSpan.references.length, 0);
assert.strictEqual(thriftSpan.logs.length, 1);

View File

@ -41,7 +41,6 @@
"access": "public"
},
"devDependencies": {
"@opentelemetry/resources": "^0.4.0",
"@types/mocha": "^5.2.7",
"@types/nock": "^11.1.0",
"@types/node": "^12.6.9",
@ -63,6 +62,7 @@
"@opentelemetry/api": "^0.4.0",
"@opentelemetry/base": "^0.4.0",
"@opentelemetry/core": "^0.4.0",
"@opentelemetry/resources": "^0.4.0",
"@opentelemetry/tracing": "^0.4.0",
"google-auth-library": "^5.7.0",
"googleapis": "^46.0.0"

View File

@ -30,6 +30,7 @@ import {
TruncatableString,
} from './types';
import { VERSION } from './version';
import { Resource } from '@opentelemetry/resources';
const AGENT_LABEL_KEY = 'g.co/agent';
const AGENT_LABEL_VALUE = `opentelemetry-js [${CORE_VERSION}]; stackdriver-trace-exporter [${VERSION}]`;
@ -38,10 +39,14 @@ export function getReadableSpanTransformer(
projectId: string
): (span: ReadableSpan) => Span {
return span => {
const attributes = transformAttributes(span.attributes, {
project_id: projectId,
[AGENT_LABEL_KEY]: AGENT_LABEL_VALUE,
});
const attributes = transformAttributes(
span.attributes,
{
project_id: projectId,
[AGENT_LABEL_KEY]: AGENT_LABEL_VALUE,
},
span.resource
);
const out: Span = {
attributes,
@ -85,9 +90,16 @@ function transformLink(link: ot.Link): Link {
function transformAttributes(
requestAttributes: ot.Attributes = {},
serviceAttributes: ot.Attributes = {}
serviceAttributes: ot.Attributes = {},
resource: Resource = Resource.empty()
): Attributes {
const attributes = Object.assign({}, requestAttributes, serviceAttributes);
const attributes = Object.assign(
{},
requestAttributes,
serviceAttributes,
resource.labels
);
const attributeMap = transformAttributeValues(attributes);
return {
attributeMap: attributeMap,

View File

@ -51,7 +51,11 @@ describe('transform', () => {
name: 'my-span',
spanContext,
status: { code: types.CanonicalCode.OK },
resource: Resource.empty(),
resource: new Resource({
service: 'ui',
version: 1,
cost: 112.12,
}),
};
});
@ -67,6 +71,9 @@ describe('transform', () => {
value: `opentelemetry-js [${CORE_VERSION}]; stackdriver-trace-exporter [${VERSION}]`,
},
},
cost: { intValue: '112' },
service: { stringValue: { value: 'ui' } },
version: { intValue: '1' },
},
droppedAttributesCount: 0,
},
@ -130,7 +137,7 @@ describe('transform', () => {
assert.deepStrictEqual(result.attributes!.droppedAttributesCount, 1);
assert.deepStrictEqual(
Object.keys(result.attributes!.attributeMap!).length,
2
5
);
});

View File

@ -39,7 +39,6 @@
"access": "public"
},
"devDependencies": {
"@opentelemetry/resources": "^0.4.0",
"@types/mocha": "^5.2.7",
"@types/nock": "^10.0.3",
"@types/node": "^12.6.9",
@ -59,6 +58,7 @@
"@opentelemetry/api": "^0.4.0",
"@opentelemetry/base": "^0.4.0",
"@opentelemetry/core": "^0.4.0",
"@opentelemetry/resources": "^0.4.0",
"@opentelemetry/tracing": "^0.4.0"
}
}

View File

@ -18,6 +18,7 @@ import * as types from '@opentelemetry/api';
import { ReadableSpan } from '@opentelemetry/tracing';
import { hrTimeToMicroseconds } from '@opentelemetry/core';
import * as zipkinTypes from './types';
import { Resource } from '@opentelemetry/resources';
const ZIPKIN_SPAN_KIND_MAPPING = {
[types.SpanKind.CLIENT]: zipkinTypes.SpanKind.CLIENT,
@ -54,7 +55,8 @@ export function toZipkinSpan(
span.attributes,
span.status,
statusCodeTagName,
statusDescriptionTagName
statusDescriptionTagName,
span.resource
),
annotations: span.events.length
? _toZipkinAnnotations(span.events)
@ -69,9 +71,10 @@ export function _toZipkinTags(
attributes: types.Attributes,
status: types.Status,
statusCodeTagName: string,
statusDescriptionTagName: string
statusDescriptionTagName: string,
resource: Resource
): zipkinTypes.Tags {
const tags: { [key: string]: string } = {};
const tags: { [key: string]: unknown } = {};
for (const key of Object.keys(attributes)) {
tags[key] = String(attributes[key]);
}
@ -79,6 +82,11 @@ export function _toZipkinTags(
if (status.message) {
tags[statusDescriptionTagName] = status.message;
}
Object.keys(resource.labels).forEach(
name => (tags[name] = resource.labels[name])
);
return tags;
}

View File

@ -21,6 +21,7 @@ import {
NoopLogger,
hrTimeToMicroseconds,
hrTimeDuration,
VERSION,
} from '@opentelemetry/core';
import {
toZipkinSpan,
@ -30,6 +31,7 @@ import {
statusDescriptionTagName,
} from '../src/transform';
import * as zipkinTypes from '../src/types';
import { Resource } from '@opentelemetry/resources';
const logger = new NoopLogger();
const tracer = new BasicTracerProvider({
@ -42,6 +44,12 @@ const spanContext: types.SpanContext = {
traceFlags: types.TraceFlags.SAMPLED,
};
const DUMMY_RESOUCE = new Resource({
service: 'ui',
version: 1,
cost: 112.12,
});
describe('transform', () => {
describe('toZipkinSpan', () => {
it('should convert an OpenTelemetry span to a Zipkin span', () => {
@ -86,6 +94,9 @@ describe('transform', () => {
key1: 'value1',
key2: 'value2',
[statusCodeTagName]: 'OK',
'telemetry.sdk.language': 'nodejs',
'telemetry.sdk.name': 'opentelemetry',
'telemetry.sdk.version': VERSION,
},
timestamp: hrTimeToMicroseconds(span.startTime),
traceId: span.spanContext.traceId,
@ -120,6 +131,9 @@ describe('transform', () => {
parentId: undefined,
tags: {
[statusCodeTagName]: 'OK',
'telemetry.sdk.language': 'nodejs',
'telemetry.sdk.name': 'opentelemetry',
'telemetry.sdk.version': VERSION,
},
timestamp: hrTimeToMicroseconds(span.startTime),
traceId: span.spanContext.traceId,
@ -159,6 +173,9 @@ describe('transform', () => {
parentId: undefined,
tags: {
[statusCodeTagName]: 'OK',
'telemetry.sdk.language': 'nodejs',
'telemetry.sdk.name': 'opentelemetry',
'telemetry.sdk.version': VERSION,
},
timestamp: hrTimeToMicroseconds(span.startTime),
traceId: span.spanContext.traceId,
@ -184,13 +201,17 @@ describe('transform', () => {
span.attributes,
span.status,
statusCodeTagName,
statusDescriptionTagName
statusDescriptionTagName,
DUMMY_RESOUCE
);
assert.deepStrictEqual(tags, {
key1: 'value1',
key2: 'value2',
[statusCodeTagName]: 'OK',
cost: 112.12,
service: 'ui',
version: 1,
});
});
it('should map OpenTelemetry Status.code to a Zipkin tag', () => {
@ -213,7 +234,8 @@ describe('transform', () => {
span.attributes,
span.status,
statusCodeTagName,
statusDescriptionTagName
statusDescriptionTagName,
Resource.empty()
);
assert.deepStrictEqual(tags, {
@ -243,7 +265,8 @@ describe('transform', () => {
span.attributes,
span.status,
statusCodeTagName,
statusDescriptionTagName
statusDescriptionTagName,
Resource.empty()
);
assert.deepStrictEqual(tags, {