Skip to content

Commit 13da2a7

Browse files
sethmaxwlyoshi-automationsofislBenjamin E. Coefeywind
authored
feat: Opentelemetry integration (#1078)
* Add opentelemetry tracing * build: rename _toc to toc (#1066) * changes without context autosynth cannot find the source of changes triggered by earlier changes in this repository, or by version upgrades to tools such as linters. * fix: rename _toc to toc Source-Author: F. Hinkelmann <[email protected]> Source-Date: Tue Jul 21 10:53:20 2020 -0400 Source-Repo: googleapis/synthtool Source-Sha: 99c93fe09f8c1dca09dfc0301c8668e3a70dd796 Source-Link: googleapis/synthtool@99c93fe Co-authored-by: sofisl <[email protected]> * build: move gitattributes files to node templates (#1070) Source-Author: F. Hinkelmann <[email protected]> Source-Date: Thu Jul 23 01:45:04 2020 -0400 Source-Repo: googleapis/synthtool Source-Sha: 3a00b7fea8c4c83eaff8eb207f530a2e3e8e1de3 Source-Link: googleapis/synthtool@3a00b7f * Add opentelemetry instrumentation * Add create span test * Refactor tracing * Add publisher key test * Fix linting issues * Add docs * Add example for opentelemetry * Add tracing example * Update headers * Add microsoft api documenter * Fix linting in samples/package.json * Add optional tracing * Fix linting issues * Re-add api-documenter * Update package.json * Update package.json * Update package.json * Fix docs * Add more unit tests * Fix linting * Add disable tracing tests * Update opentelemetryTracing sample Co-authored-by: Yoshi Automation Bot <[email protected]> Co-authored-by: sofisl <[email protected]> Co-authored-by: Benjamin E. Coe <[email protected]> Co-authored-by: Megan Potter <[email protected]>
1 parent 523f0a7 commit 13da2a7

7 files changed

Lines changed: 399 additions & 11 deletions

File tree

handwritten/pubsub/package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"presystem-test": "npm run compile",
3131
"system-test": "mocha build/system-test --timeout 600000",
3232
"samples-test": "cd samples/ && npm link ../ && npm install && npm test && cd ../",
33-
"test": "c8 mocha build/test",
33+
"test": "c8 mocha build/test --recursive",
3434
"lint": "gts check",
3535
"predocs": "npm run compile",
3636
"docs": "jsdoc -c .jsdoc.js",
@@ -44,15 +44,17 @@
4444
"predocs-test": "npm run docs",
4545
"benchwrapper": "node bin/benchwrapper.js",
4646
"prelint": "cd samples; npm link ../; npm install",
47-
"precompile": "gts clean",
4847
"api-extractor": "api-extractor run --local",
49-
"api-documenter": "api-documenter yaml --input-folder=temp"
48+
"api-documenter": "api-documenter yaml --input-folder=temp",
49+
"precompile": "gts clean"
5050
},
5151
"dependencies": {
5252
"@google-cloud/paginator": "^3.0.0",
5353
"@google-cloud/precise-date": "^2.0.0",
5454
"@google-cloud/projectify": "^2.0.0",
5555
"@google-cloud/promisify": "^2.0.0",
56+
"@opentelemetry/api": "^0.9.0",
57+
"@opentelemetry/tracing": "^0.9.0",
5658
"@types/duplexify": "^3.6.0",
5759
"@types/long": "^4.0.0",
5860
"arrify": "^2.0.0",
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*!
2+
* Copyright 2020 Google LLC
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
import {Attributes, SpanContext, Span, trace} from '@opentelemetry/api';
17+
import {Tracer} from '@opentelemetry/tracing';
18+
19+
/**
20+
* Wrapper for creating OpenTelemetry Spans
21+
*
22+
* @class
23+
*/
24+
export class OpenTelemetryTracer {
25+
/**
26+
* Creates a new span with the given properties
27+
*
28+
* @param {string} spanName the name for the span
29+
* @param {Attributes?} attributes an object containing the attributes to be set for the span
30+
* @param {SpanContext?} parent the context of the parent span to link to the span
31+
*/
32+
createSpan(
33+
spanName: string,
34+
attributes?: Attributes,
35+
parent?: SpanContext
36+
): Span {
37+
const tracerProvider: Tracer = trace.getTracer('default') as Tracer;
38+
return tracerProvider.startSpan(spanName, {
39+
parent: parent,
40+
attributes: attributes,
41+
});
42+
}
43+
}

handwritten/pubsub/src/publisher/index.ts

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@
1717
import {promisify, promisifyAll} from '@google-cloud/promisify';
1818
import * as extend from 'extend';
1919
import {CallOptions} from 'google-gax';
20+
import {Span} from '@opentelemetry/api';
2021

2122
import {BatchPublishOptions} from './message-batch';
2223
import {Queue, OrderedQueue} from './message-queues';
2324
import {Topic} from '../topic';
2425
import {RequestCallback, EmptyCallback} from '../pubsub';
2526
import {google} from '../../protos/protos';
2627
import {defaultOptions} from '../default-options';
28+
import {OpenTelemetryTracer} from '../opentelemetry-tracing';
2729

2830
export type PubsubMessage = google.pubsub.v1.IPubsubMessage;
2931

@@ -37,6 +39,7 @@ export interface PublishOptions {
3739
batching?: BatchPublishOptions;
3840
gaxOpts?: CallOptions;
3941
messageOrdering?: boolean;
42+
enableOpenTelemetryTracing?: boolean;
4043
}
4144

4245
/**
@@ -72,11 +75,16 @@ export class Publisher {
7275
settings!: PublishOptions;
7376
queue: Queue;
7477
orderedQueues: Map<string, OrderedQueue>;
78+
tracing: OpenTelemetryTracer | undefined;
7579
constructor(topic: Topic, options?: PublishOptions) {
7680
this.setOptions(options);
7781
this.topic = topic;
7882
this.queue = new Queue(this);
7983
this.orderedQueues = new Map();
84+
this.tracing =
85+
this.settings && this.settings.enableOpenTelemetryTracing
86+
? new OpenTelemetryTracer()
87+
: undefined;
8088
}
8189

8290
flush(): Promise<void>;
@@ -162,8 +170,13 @@ export class Publisher {
162170
}
163171
}
164172

173+
const span: Span | undefined = this.constructSpan(message);
174+
165175
if (!message.orderingKey) {
166176
this.queue.add(message, callback);
177+
if (span) {
178+
span.end();
179+
}
167180
return;
168181
}
169182

@@ -177,6 +190,10 @@ export class Publisher {
177190

178191
const queue = this.orderedQueues.get(key)!;
179192
queue.add(message, callback);
193+
194+
if (span) {
195+
span.end();
196+
}
180197
}
181198
/**
182199
* Indicates to the publisher that it is safe to continue publishing for the
@@ -211,13 +228,19 @@ export class Publisher {
211228
gaxOpts: {
212229
isBundling: false,
213230
},
231+
enableOpenTelemetryTracing: false,
214232
};
215233

216-
const {batching, gaxOpts, messageOrdering} = extend(
217-
true,
218-
defaults,
219-
options
220-
);
234+
const {
235+
batching,
236+
gaxOpts,
237+
messageOrdering,
238+
enableOpenTelemetryTracing,
239+
} = extend(true, defaults, options);
240+
241+
this.tracing = enableOpenTelemetryTracing
242+
? new OpenTelemetryTracer()
243+
: undefined;
221244

222245
this.settings = {
223246
batching: {
@@ -227,11 +250,45 @@ export class Publisher {
227250
},
228251
gaxOpts,
229252
messageOrdering,
253+
enableOpenTelemetryTracing,
230254
};
231255
}
256+
257+
/**
258+
* Constructs an OpenTelemetry span
259+
*
260+
* @private
261+
*
262+
* @param {PubsubMessage} message The message to create a span for
263+
*/
264+
constructSpan(message: PubsubMessage): Span | undefined {
265+
const spanAttributes = {
266+
data: message.data,
267+
};
268+
const span: Span | undefined = this.tracing
269+
? this.tracing.createSpan(`${this.topic.name} publisher`, spanAttributes)
270+
: undefined;
271+
if (span) {
272+
if (
273+
message.attributes &&
274+
message.attributes['googclient_OpenTelemetrySpanContext']
275+
) {
276+
console.warn(
277+
'googclient_OpenTelemetrySpanContext key set as message attribute, but will be overridden.'
278+
);
279+
}
280+
if (!message.attributes) {
281+
message.attributes = {};
282+
}
283+
message.attributes[
284+
'googclient_OpenTelemetrySpanContext'
285+
] = JSON.stringify(span.context());
286+
}
287+
return span;
288+
}
232289
}
233290

234291
promisifyAll(Publisher, {
235292
singular: true,
236-
exclude: ['publish', 'setOptions'],
293+
exclude: ['publish', 'setOptions', 'constructSpan'],
237294
});

handwritten/pubsub/src/subscriber.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {DateStruct, PreciseDate} from '@google-cloud/precise-date';
1818
import {replaceProjectIdToken} from '@google-cloud/projectify';
1919
import {promisify} from '@google-cloud/promisify';
2020
import {EventEmitter} from 'events';
21+
import {SpanContext, Span} from '@opentelemetry/api';
2122

2223
import {google} from '../protos/protos';
2324
import {Histogram} from './histogram';
@@ -27,6 +28,7 @@ import {MessageStream, MessageStreamOptions} from './message-stream';
2728
import {Subscription} from './subscription';
2829
import {defaultOptions} from './default-options';
2930
import {SubscriberClient} from './v1';
31+
import {OpenTelemetryTracer} from './opentelemetry-tracing';
3032

3133
export type PullResponse = google.pubsub.v1.IPullResponse;
3234

@@ -202,6 +204,7 @@ export interface SubscriberOptions {
202204
batching?: BatchOptions;
203205
flowControl?: FlowControlOptions;
204206
streamingOptions?: MessageStreamOptions;
207+
enableOpenTelemetryTracing?: boolean;
205208
}
206209

207210
/**
@@ -237,6 +240,7 @@ export class Subscriber extends EventEmitter {
237240
private _options!: SubscriberOptions;
238241
private _stream!: MessageStream;
239242
private _subscription: Subscription;
243+
private _tracing: OpenTelemetryTracer | undefined;
240244
constructor(subscription: Subscription, options = {}) {
241245
super();
242246

@@ -248,7 +252,6 @@ export class Subscriber extends EventEmitter {
248252
this._histogram = new Histogram({min: 10, max: 600});
249253
this._latencies = new Histogram();
250254
this._subscription = subscription;
251-
252255
this.setOptions(options);
253256
}
254257
/**
@@ -423,7 +426,42 @@ export class Subscriber extends EventEmitter {
423426
this.maxMessages
424427
);
425428
}
429+
this._tracing = options.enableOpenTelemetryTracing
430+
? new OpenTelemetryTracer()
431+
: undefined;
432+
}
433+
434+
/**
435+
* Constructs an OpenTelemetry span from the incoming message.
436+
*
437+
* @param {Message} message One of the received messages
438+
* @private
439+
*/
440+
private _constructSpan(message: Message): Span | undefined {
441+
// Handle cases where OpenTelemetry is disabled or no span context was sent through message
442+
if (
443+
!this._tracing ||
444+
!message.attributes ||
445+
!message.attributes['googclient_OpenTelemetrySpanContext']
446+
) {
447+
return undefined;
448+
}
449+
const spanValue = message.attributes['googclient_OpenTelemetrySpanContext'];
450+
const parentSpanContext: SpanContext | undefined = spanValue
451+
? JSON.parse(spanValue)
452+
: undefined;
453+
const spanAttributes = {
454+
ackId: message.ackId,
455+
deliveryAttempt: message.deliveryAttempt,
456+
};
457+
// Subscriber spans should always have a publisher span as a parent.
458+
// Return undefined if no parent is provided
459+
const span = parentSpanContext
460+
? this._tracing.createSpan(this._name, spanAttributes, parentSpanContext)
461+
: undefined;
462+
return span;
426463
}
464+
427465
/**
428466
* Callback to be invoked when a new message is available.
429467
*
@@ -445,12 +483,16 @@ export class Subscriber extends EventEmitter {
445483
for (const data of receivedMessages!) {
446484
const message = new Message(this, data);
447485

486+
const span: Span | undefined = this._constructSpan(message);
448487
if (this.isOpen) {
449488
message.modAck(this.ackDeadline);
450489
this._inventory.add(message);
451490
} else {
452491
message.nack();
453492
}
493+
if (span) {
494+
span.end();
495+
}
454496
}
455497
}
456498

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*!
2+
* Copyright 2020 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import * as assert from 'assert';
18+
import {describe, it, before, beforeEach, afterEach} from 'mocha';
19+
20+
import * as api from '@opentelemetry/api';
21+
import * as trace from '@opentelemetry/tracing';
22+
import {OpenTelemetryTracer} from '../src/opentelemetry-tracing';
23+
import {SimpleSpanProcessor} from '@opentelemetry/tracing';
24+
25+
describe('OpenTelemetryTracer', () => {
26+
let tracing: OpenTelemetryTracer;
27+
let span: trace.Span;
28+
const spanName = 'test-span';
29+
const spanContext: api.SpanContext = {
30+
traceId: 'd4cda95b652f4a1592b449d5929fda1b',
31+
spanId: '6e0c63257de34c92',
32+
traceFlags: api.TraceFlags.SAMPLED,
33+
};
34+
const spanAttributes: api.Attributes = {
35+
foo: 'bar',
36+
};
37+
38+
before(() => {
39+
const provider = new trace.BasicTracerProvider();
40+
const exporter = new trace.InMemorySpanExporter();
41+
provider.addSpanProcessor(new SimpleSpanProcessor(exporter));
42+
api.trace.setGlobalTracerProvider(provider);
43+
});
44+
45+
beforeEach(() => {
46+
tracing = new OpenTelemetryTracer();
47+
});
48+
49+
afterEach(() => {
50+
span.end();
51+
});
52+
53+
it('creates a span', () => {
54+
span = tracing.createSpan(
55+
spanName,
56+
spanAttributes,
57+
spanContext
58+
) as trace.Span;
59+
assert.strictEqual(span.name, spanName);
60+
assert.deepStrictEqual(span.attributes, spanAttributes);
61+
assert.strictEqual(span.parentSpanId, spanContext.spanId);
62+
});
63+
});

0 commit comments

Comments
 (0)