Skip to content
This repository was archived by the owner on Oct 3, 2023. It is now read-only.

Commit 5b8dced

Browse files
authored
Use binary format for grpc plugin (#354)
* Use binary format for grpc plugin * fix indentations * add tests for metadata headers
1 parent fc5f4d1 commit 5b8dced

File tree

5 files changed

+165
-59
lines changed

5 files changed

+165
-59
lines changed

packages/opencensus-instrumentation-grpc/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
"access": "public"
4141
},
4242
"devDependencies": {
43-
"@opencensus/propagation-b3": "^0.0.9",
43+
"@opencensus/propagation-binaryformat": "^0.0.1",
4444
"@types/end-of-stream": "^1.4.0",
4545
"@types/lodash": "^4.14.109",
4646
"@types/mocha": "^5.2.5",

packages/opencensus-instrumentation-grpc/src/grpc.ts

+68-51
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,15 @@
1414
* limitations under the License.
1515
*/
1616

17-
import {BasePlugin, CanonicalCode, HeaderGetter, HeaderSetter, PluginInternalFiles, RootSpan, Span, SpanKind} from '@opencensus/core';
17+
import {BasePlugin, CanonicalCode, PluginInternalFiles, RootSpan, Span, SpanContext, SpanKind, TraceOptions} from '@opencensus/core';
18+
import {deserializeSpanContext, serializeSpanContext} from '@opencensus/propagation-binaryformat';
1819
import {EventEmitter} from 'events';
1920
import * as grpcTypes from 'grpc';
2021
import * as lodash from 'lodash';
21-
import * as path from 'path';
22-
import * as semver from 'semver';
2322
import * as shimmer from 'shimmer';
2423

24+
/** The metadata key under which span context is stored as a binary value. */
25+
export const GRPC_TRACE_KEY = 'grpc-trace-bin';
2526
const findIndex = lodash.findIndex;
2627

2728
//
@@ -48,7 +49,7 @@ type ServerCallWithMeta = ServerCall&{
4849
&EventEmitter;
4950

5051
export type SendUnaryDataCallback =
51-
(error: grpcTypes.ServiceError,
52+
(error: grpcTypes.ServiceError|null,
5253
// tslint:disable-next-line:no-any
5354
value?: any, trailer?: grpcTypes.Metadata, flags?: grpcTypes.writeFlags) =>
5455
void;
@@ -59,17 +60,6 @@ type GrpcClientFunc = typeof Function&{
5960
responseStream: boolean;
6061
};
6162

62-
63-
type HandlerSet = {
64-
// tslint:disable-next-line:no-any
65-
func: grpcTypes.handleCall<any, any>;
66-
// tslint:disable-next-line:no-any
67-
serialize: grpcTypes.serialize<any>;
68-
// tslint:disable-next-line:no-any
69-
deserialize: grpcTypes.deserialize<any>;
70-
type: string;
71-
};
72-
7363
// tslint:disable:variable-name
7464
// tslint:disable-next-line:no-any
7565
let Metadata: any;
@@ -162,29 +152,29 @@ export class GrpcPlugin extends BasePlugin {
162152
this: typeof handlerSet, call: ServerCallWithMeta,
163153
callback: SendUnaryDataCallback) {
164154
const self = this;
165-
const propagation = plugin.tracer.propagation;
166-
const headers = call.metadata.getMap();
167-
const getter: HeaderGetter = {
168-
getHeader(name: string) {
169-
return headers[name] as string;
170-
}
171-
};
172155

173-
const traceOptions = {
156+
const traceOptions: TraceOptions = {
174157
name: `grpc.${name.replace('/', '')}`,
175-
kind: SpanKind.SERVER,
176-
spanContext: propagation ? propagation.extract(getter) : null
158+
kind: SpanKind.SERVER
177159
};
178-
plugin.logger.debug('path func: %s', traceOptions.name);
160+
161+
const spanContext = GrpcPlugin.getSpanContext(call.metadata);
162+
if (spanContext) {
163+
traceOptions.spanContext = spanContext;
164+
}
165+
plugin.logger.debug(
166+
'path func: %s', JSON.stringify(traceOptions));
179167

180168
return plugin.tracer.startRootSpan(traceOptions, rootSpan => {
181169
if (!rootSpan) {
182170
return originalFunc.call(self, call, callback);
183171
}
184172

185173
rootSpan.addAttribute(GrpcPlugin.ATTRIBUTE_GRPC_METHOD, name);
186-
rootSpan.addAttribute(
187-
GrpcPlugin.ATTRIBUTE_GRPC_KIND, traceOptions.kind);
174+
if (traceOptions.kind) {
175+
rootSpan.addAttribute(
176+
GrpcPlugin.ATTRIBUTE_GRPC_KIND, traceOptions.kind);
177+
}
188178

189179
switch (type) {
190180
case 'unary':
@@ -206,7 +196,6 @@ export class GrpcPlugin extends BasePlugin {
206196
};
207197
}
208198

209-
210199
/**
211200
* Handler Unary and Client Stream Calls
212201
*/
@@ -219,10 +208,12 @@ export class GrpcPlugin extends BasePlugin {
219208
// tslint:disable-next-line:no-any
220209
value: any, trailer: grpcTypes.Metadata, flags: grpcTypes.writeFlags) {
221210
if (err) {
222-
rootSpan.setStatus(
223-
GrpcPlugin.convertGrpcStatusToSpanStatus(err.code), err.message);
224-
rootSpan.addAttribute(
225-
GrpcPlugin.ATTRIBUTE_GRPC_STATUS_CODE, err.code.toString());
211+
if (err.code) {
212+
rootSpan.setStatus(
213+
GrpcPlugin.convertGrpcStatusToSpanStatus(err.code), err.message);
214+
rootSpan.addAttribute(
215+
GrpcPlugin.ATTRIBUTE_GRPC_STATUS_CODE, err.code.toString());
216+
}
226217
rootSpan.addAttribute(GrpcPlugin.ATTRIBUTE_GRPC_ERROR_NAME, err.name);
227218
rootSpan.addAttribute(
228219
GrpcPlugin.ATTRIBUTE_GRPC_ERROR_MESSAGE, err.message);
@@ -351,10 +342,13 @@ export class GrpcPlugin extends BasePlugin {
351342
// tslint:disable-next-line:no-any
352343
const wrappedFn = (err: grpcTypes.ServiceError, res: any) => {
353344
if (err) {
354-
span.setStatus(
355-
GrpcPlugin.convertGrpcStatusToSpanStatus(err.code), err.message);
356-
span.addAttribute(
357-
GrpcPlugin.ATTRIBUTE_GRPC_STATUS_CODE, err.code.toString());
345+
if (err.code) {
346+
span.setStatus(
347+
GrpcPlugin.convertGrpcStatusToSpanStatus(err.code),
348+
err.message);
349+
span.addAttribute(
350+
GrpcPlugin.ATTRIBUTE_GRPC_STATUS_CODE, err.code.toString());
351+
}
358352
span.addAttribute(GrpcPlugin.ATTRIBUTE_GRPC_ERROR_NAME, err.name);
359353
span.addAttribute(
360354
GrpcPlugin.ATTRIBUTE_GRPC_ERROR_MESSAGE, err.message);
@@ -386,24 +380,13 @@ export class GrpcPlugin extends BasePlugin {
386380
}
387381
}
388382

389-
const metadata = this.getMetadata(original, args, span);
390-
391-
const setter: HeaderSetter = {
392-
setHeader(name: string, value: string) {
393-
metadata.set(name, value);
394-
}
395-
};
396-
397-
const propagation = plugin.tracer.propagation;
398-
if (propagation) {
399-
propagation.inject(setter, span.spanContext);
400-
}
383+
const metadata = this.getMetadata(original, args);
384+
GrpcPlugin.setSpanContext(metadata, span.spanContext);
401385

402386
span.addAttribute(GrpcPlugin.ATTRIBUTE_GRPC_METHOD, original.path);
403387
span.addAttribute(GrpcPlugin.ATTRIBUTE_GRPC_KIND, span.kind);
404388

405389
const call = original.apply(self, args);
406-
407390
plugin.tracer.wrapEmitter(call);
408391

409392
// if server stream or bidi
@@ -444,7 +427,7 @@ export class GrpcPlugin extends BasePlugin {
444427
* https://github.com/GoogleCloudPlatform/cloud-trace-nodejs/blob/src/plugins/plugin-grpc.ts#L96)
445428
*/
446429
// tslint:disable-next-line:no-any
447-
private getMetadata(original: GrpcClientFunc, args: any[], span: Span):
430+
private getMetadata(original: GrpcClientFunc, args: any[]):
448431
grpcTypes.Metadata {
449432
let metadata: grpcTypes.Metadata;
450433

@@ -490,6 +473,40 @@ export class GrpcPlugin extends BasePlugin {
490473
static convertGrpcStatusToSpanStatus(statusCode: grpcTypes.status): number {
491474
return statusCode;
492475
}
476+
477+
/**
478+
* Returns a span context on a Metadata object if it exists and is
479+
* well-formed, or null otherwise.
480+
* @param metadata The Metadata object from which span context should be
481+
* retrieved.
482+
*/
483+
static getSpanContext(metadata: grpcTypes.Metadata): SpanContext|null {
484+
const metadataValue = metadata.getMap()[GRPC_TRACE_KEY] as Buffer;
485+
// Entry doesn't exist.
486+
if (!metadataValue) {
487+
return null;
488+
}
489+
const spanContext = deserializeSpanContext(metadataValue);
490+
// Value is malformed.
491+
if (!spanContext) {
492+
return null;
493+
}
494+
return spanContext;
495+
}
496+
497+
/**
498+
* Set span context on a Metadata object if it exists.
499+
* @param metadata The Metadata object to which a span context should be
500+
* added.
501+
* @param spanContext The span context.
502+
*/
503+
static setSpanContext(metadata: grpcTypes.Metadata, spanContext: SpanContext):
504+
void {
505+
const serializedSpanContext = serializeSpanContext(spanContext);
506+
if (serializedSpanContext) {
507+
metadata.set(GRPC_TRACE_KEY, serializedSpanContext);
508+
}
509+
}
493510
}
494511

495512
const plugin = new GrpcPlugin();

packages/opencensus-instrumentation-grpc/test/test-grpc.ts

+92-5
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,17 @@
1616

1717
import {CoreTracer, RootSpan, Span, SpanEventListener, SpanKind} from '@opencensus/core';
1818
import {logger} from '@opencensus/core';
19-
import {B3Format} from '@opencensus/propagation-b3';
2019
import * as assert from 'assert';
2120
import * as grpcModule from 'grpc';
2221
import * as path from 'path';
2322

24-
import {GrpcModule, GrpcPlugin, plugin, SendUnaryDataCallback} from '../src/';
25-
23+
import {GRPC_TRACE_KEY, GrpcModule, GrpcPlugin, plugin, SendUnaryDataCallback} from '../src/';
2624

2725
const PROTO_PATH = __dirname + '/fixtures/grpc-instrumentation-test.proto';
2826
const grpcPort = 50051;
2927
const MAX_ERROR_STATUS = grpcModule.status.UNAUTHENTICATED;
3028
const log = logger.logger();
3129

32-
3330
const replicate = (request: TestRequestResponse) => {
3431
const result: TestRequestResponse[] = [];
3532
for (let i = 0; i < request.num; i++) {
@@ -244,7 +241,7 @@ describe('GrpcPlugin() ', function() {
244241
let client: TestGrpcClient;
245242
const tracer = new CoreTracer();
246243
const rootSpanVerifier = new RootSpanVerifier();
247-
tracer.start({samplingRate: 1, propagation: new B3Format(), logger: log});
244+
tracer.start({samplingRate: 1, logger: log});
248245

249246
it('should return a plugin', () => {
250247
assert.ok(plugin instanceof GrpcPlugin);
@@ -495,4 +492,94 @@ describe('GrpcPlugin() ', function() {
495492
});
496493
});
497494
});
495+
describe('setSpanContext', () => {
496+
const metadata = new grpcModule.Metadata();
497+
const spanContext = {
498+
traceId: '3ad17e665f514aabb896341f670179ed',
499+
spanId: '3aaeb440a89d9e82',
500+
options: 0x1
501+
};
502+
503+
it('should set span context', () => {
504+
GrpcPlugin.setSpanContext(metadata, spanContext);
505+
const actualSpanContext = GrpcPlugin.getSpanContext(metadata);
506+
assert.deepEqual(actualSpanContext, spanContext);
507+
});
508+
});
509+
510+
describe('getSpanContext', () => {
511+
const metadata = new grpcModule.Metadata();
512+
it('should return null when span context is not set', () => {
513+
const actualSpanContext = GrpcPlugin.getSpanContext(metadata);
514+
assert.equal(actualSpanContext, null);
515+
});
516+
517+
it('should return valid span context', () => {
518+
const buffer = new Buffer([
519+
0x00, 0x00, 0xdf, 0x6a, 0x20, 0x38, 0xfa, 0x78, 0xc4, 0xcd,
520+
0x42, 0x20, 0x91, 0x26, 0x24, 0x9c, 0x31, 0xc7, 0x01, 0xc2,
521+
0xb7, 0xce, 0x7a, 0x57, 0x2a, 0x37, 0xc6, 0x02, 0x01
522+
]);
523+
const expectedSpanContext = {
524+
traceId: 'df6a2038fa78c4cd42209126249c31c7',
525+
spanId: 'c2b7ce7a572a37c6',
526+
options: 1
527+
};
528+
metadata.set(GRPC_TRACE_KEY, buffer);
529+
const actualSpanContext = GrpcPlugin.getSpanContext(metadata);
530+
assert.deepEqual(actualSpanContext, expectedSpanContext);
531+
});
532+
533+
it('should return null for unsupported version', () => {
534+
const buffer = new Buffer([
535+
0x66, 0x64, 0xdf, 0x6a, 0x20, 0x38, 0xfa, 0x78, 0xc4, 0xcd,
536+
0x42, 0x20, 0x91, 0x26, 0x24, 0x9c, 0x31, 0xc7, 0x01, 0xc2,
537+
0xb7, 0xce, 0x7a, 0x57, 0x2a, 0x37, 0xc6, 0x02, 0x01
538+
]);
539+
metadata.set(GRPC_TRACE_KEY, buffer);
540+
const actualSpanContext = GrpcPlugin.getSpanContext(metadata);
541+
assert.deepEqual(actualSpanContext, null);
542+
});
543+
544+
it('should return null when unexpected trace ID offset', () => {
545+
const buffer = new Buffer([
546+
0x00, 0x04, 0xdf, 0x6a, 0x20, 0x38, 0xfa, 0x78, 0xc4, 0xcd,
547+
0x42, 0x20, 0x91, 0x26, 0x24, 0x9c, 0x31, 0xc7, 0x01, 0xc2,
548+
0xb7, 0xce, 0x7a, 0x57, 0x2a, 0x37, 0xc6, 0x02, 0x01
549+
]);
550+
metadata.set(GRPC_TRACE_KEY, buffer);
551+
const actualSpanContext = GrpcPlugin.getSpanContext(metadata);
552+
assert.deepEqual(actualSpanContext, null);
553+
});
554+
555+
it('should return null when unexpected span ID offset', () => {
556+
const buffer = new Buffer([
557+
0x00, 0x00, 0xdf, 0x6a, 0x20, 0x38, 0xfa, 0x78, 0xc4, 0xcd,
558+
0x42, 0x20, 0x91, 0x26, 0x24, 0x9c, 0x31, 0xc7, 0x03, 0xc2,
559+
0xb7, 0xce, 0x7a, 0x57, 0x2a, 0x37, 0xc6, 0x02, 0x01
560+
]);
561+
metadata.set(GRPC_TRACE_KEY, buffer);
562+
const actualSpanContext = GrpcPlugin.getSpanContext(metadata);
563+
assert.deepEqual(actualSpanContext, null);
564+
});
565+
566+
it('should return null when unexpected options offset', () => {
567+
const buffer = new Buffer([
568+
0x00, 0x00, 0xdf, 0x6a, 0x20, 0x38, 0xfa, 0x78, 0xc4, 0xcd,
569+
0x42, 0x20, 0x91, 0x26, 0x24, 0x9c, 0x31, 0xc7, 0x03, 0xc2,
570+
0xb7, 0xce, 0x7a, 0x57, 0x2a, 0x37, 0xc6, 0x00, 0x01
571+
]);
572+
metadata.set(GRPC_TRACE_KEY, buffer);
573+
const actualSpanContext = GrpcPlugin.getSpanContext(metadata);
574+
assert.deepEqual(actualSpanContext, null);
575+
});
576+
577+
it('should return null when invalid input i.e. truncated', () => {
578+
const buffer =
579+
new Buffer([0x00, 0x00, 0xdf, 0x6a, 0x20, 0x38, 0xfa, 0x78, 0xc4]);
580+
metadata.set(GRPC_TRACE_KEY, buffer);
581+
const actualSpanContext = GrpcPlugin.getSpanContext(metadata);
582+
assert.deepEqual(actualSpanContext, null);
583+
});
584+
});
498585
});

packages/opencensus-instrumentation-grpc/tsconfig.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"pretty": true,
88
"module": "commonjs",
99
"target": "es6",
10-
"strictNullChecks": false
10+
"strictNullChecks": true,
11+
"noUnusedLocals": true
1112
},
1213
"include": [
1314
"src/**/*.ts",
@@ -17,4 +18,3 @@
1718
"node_modules"
1819
]
1920
}
20-

packages/opencensus-propagation-binaryformat/src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,5 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16+
17+
export * from './binary-format';

0 commit comments

Comments
 (0)