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

Use binary format for grpc plugin #354

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/opencensus-instrumentation-grpc/package.json
Expand Up @@ -40,7 +40,7 @@
"access": "public"
},
"devDependencies": {
"@opencensus/propagation-b3": "^0.0.9",
"@opencensus/propagation-binaryformat": "^0.0.1",
"@types/end-of-stream": "^1.4.0",
"@types/lodash": "^4.14.109",
"@types/mocha": "^5.2.5",
Expand Down
119 changes: 68 additions & 51 deletions packages/opencensus-instrumentation-grpc/src/grpc.ts
Expand Up @@ -14,14 +14,15 @@
* limitations under the License.
*/

import {BasePlugin, CanonicalCode, HeaderGetter, HeaderSetter, PluginInternalFiles, RootSpan, Span, SpanKind} from '@opencensus/core';
import {BasePlugin, CanonicalCode, PluginInternalFiles, RootSpan, Span, SpanContext, SpanKind, TraceOptions} from '@opencensus/core';
import {deserializeSpanContext, serializeSpanContext} from '@opencensus/propagation-binaryformat';
import {EventEmitter} from 'events';
import * as grpcTypes from 'grpc';
import * as lodash from 'lodash';
import * as path from 'path';
import * as semver from 'semver';
import * as shimmer from 'shimmer';

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

//
Expand All @@ -48,7 +49,7 @@ type ServerCallWithMeta = ServerCall&{
&EventEmitter;

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


type HandlerSet = {
// tslint:disable-next-line:no-any
func: grpcTypes.handleCall<any, any>;
// tslint:disable-next-line:no-any
serialize: grpcTypes.serialize<any>;
// tslint:disable-next-line:no-any
deserialize: grpcTypes.deserialize<any>;
type: string;
};

// tslint:disable:variable-name
// tslint:disable-next-line:no-any
let Metadata: any;
Expand Down Expand Up @@ -162,29 +152,29 @@ export class GrpcPlugin extends BasePlugin {
this: typeof handlerSet, call: ServerCallWithMeta,
callback: SendUnaryDataCallback) {
const self = this;
const propagation = plugin.tracer.propagation;
const headers = call.metadata.getMap();
const getter: HeaderGetter = {
getHeader(name: string) {
return headers[name] as string;
}
};

const traceOptions = {
const traceOptions: TraceOptions = {
name: `grpc.${name.replace('/', '')}`,
kind: SpanKind.SERVER,
spanContext: propagation ? propagation.extract(getter) : null
kind: SpanKind.SERVER
};
plugin.logger.debug('path func: %s', traceOptions.name);

const spanContext = GrpcPlugin.getSpanContext(call.metadata);
if (spanContext) {
traceOptions.spanContext = spanContext;
}
plugin.logger.debug(
'path func: %s', JSON.stringify(traceOptions));

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

rootSpan.addAttribute(GrpcPlugin.ATTRIBUTE_GRPC_METHOD, name);
rootSpan.addAttribute(
GrpcPlugin.ATTRIBUTE_GRPC_KIND, traceOptions.kind);
if (traceOptions.kind) {
rootSpan.addAttribute(
GrpcPlugin.ATTRIBUTE_GRPC_KIND, traceOptions.kind);
}

switch (type) {
case 'unary':
Expand All @@ -206,7 +196,6 @@ export class GrpcPlugin extends BasePlugin {
};
}


/**
* Handler Unary and Client Stream Calls
*/
Expand All @@ -219,10 +208,12 @@ export class GrpcPlugin extends BasePlugin {
// tslint:disable-next-line:no-any
value: any, trailer: grpcTypes.Metadata, flags: grpcTypes.writeFlags) {
if (err) {
rootSpan.setStatus(
GrpcPlugin.convertGrpcStatusToSpanStatus(err.code), err.message);
rootSpan.addAttribute(
GrpcPlugin.ATTRIBUTE_GRPC_STATUS_CODE, err.code.toString());
if (err.code) {
rootSpan.setStatus(
GrpcPlugin.convertGrpcStatusToSpanStatus(err.code), err.message);
rootSpan.addAttribute(
GrpcPlugin.ATTRIBUTE_GRPC_STATUS_CODE, err.code.toString());
}
rootSpan.addAttribute(GrpcPlugin.ATTRIBUTE_GRPC_ERROR_NAME, err.name);
rootSpan.addAttribute(
GrpcPlugin.ATTRIBUTE_GRPC_ERROR_MESSAGE, err.message);
Expand Down Expand Up @@ -351,10 +342,13 @@ export class GrpcPlugin extends BasePlugin {
// tslint:disable-next-line:no-any
const wrappedFn = (err: grpcTypes.ServiceError, res: any) => {
if (err) {
span.setStatus(
GrpcPlugin.convertGrpcStatusToSpanStatus(err.code), err.message);
span.addAttribute(
GrpcPlugin.ATTRIBUTE_GRPC_STATUS_CODE, err.code.toString());
if (err.code) {
span.setStatus(
GrpcPlugin.convertGrpcStatusToSpanStatus(err.code),
err.message);
span.addAttribute(
GrpcPlugin.ATTRIBUTE_GRPC_STATUS_CODE, err.code.toString());
}
span.addAttribute(GrpcPlugin.ATTRIBUTE_GRPC_ERROR_NAME, err.name);
span.addAttribute(
GrpcPlugin.ATTRIBUTE_GRPC_ERROR_MESSAGE, err.message);
Expand Down Expand Up @@ -386,24 +380,13 @@ export class GrpcPlugin extends BasePlugin {
}
}

const metadata = this.getMetadata(original, args, span);

const setter: HeaderSetter = {
setHeader(name: string, value: string) {
metadata.set(name, value);
}
};

const propagation = plugin.tracer.propagation;
if (propagation) {
propagation.inject(setter, span.spanContext);
}
const metadata = this.getMetadata(original, args);
GrpcPlugin.setSpanContext(metadata, span.spanContext);

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

const call = original.apply(self, args);

plugin.tracer.wrapEmitter(call);

// if server stream or bidi
Expand Down Expand Up @@ -444,7 +427,7 @@ export class GrpcPlugin extends BasePlugin {
* https://github.com/GoogleCloudPlatform/cloud-trace-nodejs/blob/src/plugins/plugin-grpc.ts#L96)
*/
// tslint:disable-next-line:no-any
private getMetadata(original: GrpcClientFunc, args: any[], span: Span):
private getMetadata(original: GrpcClientFunc, args: any[]):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than using any here, could this just be an empty object {}?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ping on this: @mayurkale22 is it possible to use {} here instead of any and remove the tslint disable above the line? (This was already here so if you don't want to do it now that's OK, but figure it's nice to clean up if we can while we're in here).

grpcTypes.Metadata {
let metadata: grpcTypes.Metadata;

Expand Down Expand Up @@ -490,6 +473,40 @@ export class GrpcPlugin extends BasePlugin {
static convertGrpcStatusToSpanStatus(statusCode: grpcTypes.status): number {
return statusCode;
}

/**
* Returns a span context on a Metadata object if it exists and is
* well-formed, or null otherwise.
* @param metadata The Metadata object from which span context should be
* retrieved.
*/
static getSpanContext(metadata: grpcTypes.Metadata): SpanContext|null {
const metadataValue = metadata.getMap()[GRPC_TRACE_KEY] as Buffer;
// Entry doesn't exist.
if (!metadataValue) {
return null;
}
const spanContext = deserializeSpanContext(metadataValue);
// Value is malformed.
if (!spanContext) {
return null;
}
return spanContext;
}

/**
* Set span context on a Metadata object if it exists.
* @param metadata The Metadata object to which a span context should be
* added.
* @param spanContext The span context.
*/
static setSpanContext(metadata: grpcTypes.Metadata, spanContext: SpanContext):
void {
const serializedSpanContext = serializeSpanContext(spanContext);
if (serializedSpanContext) {
metadata.set(GRPC_TRACE_KEY, serializedSpanContext);
}
}
}

const plugin = new GrpcPlugin();
Expand Down
97 changes: 92 additions & 5 deletions packages/opencensus-instrumentation-grpc/test/test-grpc.ts
Expand Up @@ -16,20 +16,17 @@

import {CoreTracer, RootSpan, Span, SpanEventListener, SpanKind} from '@opencensus/core';
import {logger} from '@opencensus/core';
import {B3Format} from '@opencensus/propagation-b3';
import * as assert from 'assert';
import * as grpcModule from 'grpc';
import * as path from 'path';

import {GrpcModule, GrpcPlugin, plugin, SendUnaryDataCallback} from '../src/';

import {GRPC_TRACE_KEY, GrpcModule, GrpcPlugin, plugin, SendUnaryDataCallback} from '../src/';

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


const replicate = (request: TestRequestResponse) => {
const result: TestRequestResponse[] = [];
for (let i = 0; i < request.num; i++) {
Expand Down Expand Up @@ -244,7 +241,7 @@ describe('GrpcPlugin() ', function() {
let client: TestGrpcClient;
const tracer = new CoreTracer();
const rootSpanVerifier = new RootSpanVerifier();
tracer.start({samplingRate: 1, propagation: new B3Format(), logger: log});
tracer.start({samplingRate: 1, logger: log});

it('should return a plugin', () => {
assert.ok(plugin instanceof GrpcPlugin);
Expand Down Expand Up @@ -495,4 +492,94 @@ describe('GrpcPlugin() ', function() {
});
});
});
describe('setSpanContext', () => {
const metadata = new grpcModule.Metadata();
const spanContext = {
traceId: '3ad17e665f514aabb896341f670179ed',
spanId: '3aaeb440a89d9e82',
options: 0x1
};

it('should set span context', () => {
GrpcPlugin.setSpanContext(metadata, spanContext);
const actualSpanContext = GrpcPlugin.getSpanContext(metadata);
assert.deepEqual(actualSpanContext, spanContext);
});
});

describe('getSpanContext', () => {
const metadata = new grpcModule.Metadata();
it('should return null when span context is not set', () => {
const actualSpanContext = GrpcPlugin.getSpanContext(metadata);
assert.equal(actualSpanContext, null);
});

it('should return valid span context', () => {
const buffer = new Buffer([
0x00, 0x00, 0xdf, 0x6a, 0x20, 0x38, 0xfa, 0x78, 0xc4, 0xcd,
0x42, 0x20, 0x91, 0x26, 0x24, 0x9c, 0x31, 0xc7, 0x01, 0xc2,
0xb7, 0xce, 0x7a, 0x57, 0x2a, 0x37, 0xc6, 0x02, 0x01
]);
const expectedSpanContext = {
traceId: 'df6a2038fa78c4cd42209126249c31c7',
spanId: 'c2b7ce7a572a37c6',
options: 1
};
metadata.set(GRPC_TRACE_KEY, buffer);
const actualSpanContext = GrpcPlugin.getSpanContext(metadata);
assert.deepEqual(actualSpanContext, expectedSpanContext);
});

it('should return null for unsupported version', () => {
const buffer = new Buffer([
0x66, 0x64, 0xdf, 0x6a, 0x20, 0x38, 0xfa, 0x78, 0xc4, 0xcd,
0x42, 0x20, 0x91, 0x26, 0x24, 0x9c, 0x31, 0xc7, 0x01, 0xc2,
0xb7, 0xce, 0x7a, 0x57, 0x2a, 0x37, 0xc6, 0x02, 0x01
]);
metadata.set(GRPC_TRACE_KEY, buffer);
const actualSpanContext = GrpcPlugin.getSpanContext(metadata);
assert.deepEqual(actualSpanContext, null);
});

it('should return null when unexpected trace ID offset', () => {
const buffer = new Buffer([
0x00, 0x04, 0xdf, 0x6a, 0x20, 0x38, 0xfa, 0x78, 0xc4, 0xcd,
0x42, 0x20, 0x91, 0x26, 0x24, 0x9c, 0x31, 0xc7, 0x01, 0xc2,
0xb7, 0xce, 0x7a, 0x57, 0x2a, 0x37, 0xc6, 0x02, 0x01
]);
metadata.set(GRPC_TRACE_KEY, buffer);
const actualSpanContext = GrpcPlugin.getSpanContext(metadata);
assert.deepEqual(actualSpanContext, null);
});

it('should return null when unexpected span ID offset', () => {
const buffer = new Buffer([
0x00, 0x00, 0xdf, 0x6a, 0x20, 0x38, 0xfa, 0x78, 0xc4, 0xcd,
0x42, 0x20, 0x91, 0x26, 0x24, 0x9c, 0x31, 0xc7, 0x03, 0xc2,
0xb7, 0xce, 0x7a, 0x57, 0x2a, 0x37, 0xc6, 0x02, 0x01
]);
metadata.set(GRPC_TRACE_KEY, buffer);
const actualSpanContext = GrpcPlugin.getSpanContext(metadata);
assert.deepEqual(actualSpanContext, null);
});

it('should return null when unexpected options offset', () => {
const buffer = new Buffer([
0x00, 0x00, 0xdf, 0x6a, 0x20, 0x38, 0xfa, 0x78, 0xc4, 0xcd,
0x42, 0x20, 0x91, 0x26, 0x24, 0x9c, 0x31, 0xc7, 0x03, 0xc2,
0xb7, 0xce, 0x7a, 0x57, 0x2a, 0x37, 0xc6, 0x00, 0x01
]);
metadata.set(GRPC_TRACE_KEY, buffer);
const actualSpanContext = GrpcPlugin.getSpanContext(metadata);
assert.deepEqual(actualSpanContext, null);
});

it('should return null when invalid input i.e. truncated', () => {
const buffer =
new Buffer([0x00, 0x00, 0xdf, 0x6a, 0x20, 0x38, 0xfa, 0x78, 0xc4]);
metadata.set(GRPC_TRACE_KEY, buffer);
const actualSpanContext = GrpcPlugin.getSpanContext(metadata);
assert.deepEqual(actualSpanContext, null);
});
});
});
4 changes: 2 additions & 2 deletions packages/opencensus-instrumentation-grpc/tsconfig.json
Expand Up @@ -7,7 +7,8 @@
"pretty": true,
"module": "commonjs",
"target": "es6",
"strictNullChecks": false
"strictNullChecks": true,
"noUnusedLocals": true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!! 👍

},
"include": [
"src/**/*.ts",
Expand All @@ -17,4 +18,3 @@
"node_modules"
]
}

2 changes: 2 additions & 0 deletions packages/opencensus-propagation-binaryformat/src/index.ts
Expand Up @@ -13,3 +13,5 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

export * from './binary-format';