Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add ability to filter grpc methods #1353

Merged

Conversation

reggiemcdonald
Copy link
Contributor

Which problem is this PR solving?

In brief, the current gRPC plugins have no ability to filter which methods are traced. This could lead to an infinite cycle if an exporter was to use gRPC. This PR addresses this problem.

Short description of the changes

The following changes were made for both plugin-grpc and plugin-grpc-js:

  • Outgoing requests with x-opentelemetry-outgoing-request metadata are now filtered
  • Added the ability to filter methods using the ignoreMethods plugin config

Testing

  • Unit tests
  • Tested @opentelemetry/plugin-grpc on a live instance exported to GCP
  • Tested @opentelemetry/plugin-grpc-js on a live instance exported to GCP

@codecov
Copy link

codecov bot commented Jul 27, 2020

Codecov Report

Merging #1353 into master will decrease coverage by 0.17%.
The diff coverage is 94.05%.

@@            Coverage Diff             @@
##           master    #1353      +/-   ##
==========================================
- Coverage   93.90%   93.72%   -0.18%     
==========================================
  Files         148      148              
  Lines        4396     4481      +85     
  Branches      895      929      +34     
==========================================
+ Hits         4128     4200      +72     
- Misses        268      281      +13     
Impacted Files Coverage Δ
...plugin-grpc-js/src/client/loadPackageDefinition.ts 100.00% <ø> (ø)
...telemetry-plugin-grpc-js/src/client/patchClient.ts 100.00% <ø> (ø)
...ackages/opentelemetry-plugin-grpc-js/src/grpcJs.ts 100.00% <ø> (ø)
...telemetry-plugin-grpc-js/src/server/patchServer.ts 93.54% <90.90%> (-1.46%) ⬇️
packages/opentelemetry-plugin-grpc/src/grpc.ts 94.31% <92.59%> (-2.57%) ⬇️
packages/opentelemetry-plugin-grpc-js/src/utils.ts 93.10% <95.00%> (+4.21%) ⬆️
packages/opentelemetry-plugin-grpc/src/utils.ts 91.89% <95.23%> (-1.86%) ⬇️
...s/opentelemetry-plugin-grpc-js/src/client/utils.ts 93.40% <100.00%> (-3.19%) ⬇️
... and 2 more

@reggiemcdonald
Copy link
Contributor Author

/cc @nlehrer @danherr

Copy link
Member

@markwolff markwolff left a comment

Choose a reason for hiding this comment

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

Overall LGTM. Added few comments

Comment on lines 65 to 105
export const methodIsIgnored = (
methodName: string,
ignoredMethods?: string[]
): boolean => {
if (!ignoredMethods) {
return false;
}
for (const pattern of ignoredMethods) {
if (new RegExp(`^${pattern}$`, 'i').test(methodName)) {
return true;
}
}
return false;
};
Copy link
Member

Choose a reason for hiding this comment

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

Could this be made like http plugin and support both partial regex and exact string matching?

export const satisfiesPattern = <T>(
constant: string,
pattern: IgnoreMatcher
): boolean => {
if (typeof pattern === 'string') {
return pattern === constant;
} else if (pattern instanceof RegExp) {
return pattern.test(constant);
} else if (typeof pattern === 'function') {
return pattern(constant);
} else {
throw new TypeError('Pattern is in unsupported datatype');
}
};

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep! I'll just use the exact same approach as in the http plugin

/**
* Metadata key used to denote an outgoing opentelemetry request.
*/
const _otRequestHeader = 'x-opentelemetry-outgoing-request';
Copy link
Member

Choose a reason for hiding this comment

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

nit: This seems to be getting defined in a lot of separate places. @open-telemetry/javascript-approvers Should it be moved to somewhere common like /core or /tracing or /semantic-conventions?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Once I get consensus here I can make the change thats required

Copy link
Member

Choose a reason for hiding this comment

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

I think just keeping it here is fine for now, because a better solution in the form of #1344 is coming.

return false;
}
for (const pattern of ignoredMethods) {
if (new RegExp(`^${pattern}$`, 'i').test(methodName)) {
Copy link
Member

Choose a reason for hiding this comment

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

Is there a benefit to newing up a regex here each time instead of doing a standard inequality check?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's a case insensitive match which I believe it needs to be for grpc since the method names may be either lower or upper camel case. I could switch to using toLowerCase() instead

@reggiemcdonald
Copy link
Contributor Author

reggiemcdonald commented Jul 29, 2020

Not sure whats up with the CI? The same commands run locally, and the thrown error doesn't look related to the changes in the last 2 commits

@dyladan
Copy link
Member

dyladan commented Jul 29, 2020

Not sure whats up with the CI? The same commands run locally, and the thrown error doesn't look related to the changes in the last 2 commits

looks like npm returned a 404. I'm able to download the tarball it missed. I'll restart the build and see if that helps.

@reggiemcdonald
Copy link
Contributor Author

Not sure whats up with the CI? The same commands run locally, and the thrown error doesn't look related to the changes in the last 2 commits

looks like npm returned a 404. I'm able to download the tarball it missed. I'll restart the build and see if that helps.

Looks like that solved the issue. Thanks!

* Returns true if the server call should not be traced.
*/
function shouldNotTraceServerCall(
this: GrpcJsPlugin,
Copy link
Member

Choose a reason for hiding this comment

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

can you please change this into grpcJsPlugin, I find it a bit misleading later in code

Copy link
Contributor Author

@reggiemcdonald reggiemcdonald Aug 6, 2020

Choose a reason for hiding this comment

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

If I change this to grpcJsPlugin then I cant access the _config of the plugin because it is a protected field. Do you have any suggestions around this?

Copy link
Member

Choose a reason for hiding this comment

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

then function shouldNotTraceServerCall should not be a standalone function but a private of GrpcJsPlugin

Copy link
Contributor Author

@reggiemcdonald reggiemcdonald Aug 6, 2020

Choose a reason for hiding this comment

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

Because the function needs to be called in patchServer(grpcJsPlugin) I cant actually make the function a private member of the class.

One alternative would be to remove the this keyword from shouldNotTraceServerCall and pass in the list of ignored gRPC methods in as parameter:

/**
 * Returns true if the server call should not be traced.
 */
function shouldNotTraceServerCall(
  metadata: grpcJs.Metadata,
  methodName: string,
  ignoreGrpcMethods?: IgnoreMatcher[]
): boolean {
  const parsedName = methodName.split('/');
  return (
    containsOtelMetadata(metadata) ||
    methodIsIgnored(
      parsedName[parsedName.length - 1] || methodName,
      ignoreGrpcMethods
    )
  );
}

That being said, the original implementation is consistent with the rest of the package

Copy link
Member

Choose a reason for hiding this comment

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

When the first parameter is named this in typescript it carries special meaning. It only carries typing info and is compiled away. Example Playground Link to illustrate.

Copy link
Member

Choose a reason for hiding this comment

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

Please make this function a private member of the class.

Copy link
Contributor Author

@reggiemcdonald reggiemcdonald Aug 6, 2020

Choose a reason for hiding this comment

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

I think maybe I still don't fully understand how to make this work. By moving the function over to class GrpcJsPlugin I get a compile error:

src/grpcJs.ts:107:11 - error TS6133: 'shouldNotTraceServerCall' is declared but its value is never read.

107   private shouldNotTraceServerCall(
              ~~~~~~~~~~~~~~~~~~~~~~~~

src/server/patchServer.ts:75:24 - error TS2341: Property 'shouldNotTraceServerCall' is private and only accessible within class 'GrpcJsPlugin'.

75             if (plugin.shouldNotTraceServerCall(call.metadata, name)) {
                          ~~~~~~~~~~~~~~~~~~~~~~~~


Found 2 errors.

Do I need to change anything else in patchServer to make this work?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just checking in to see if there's any advice on this issue?

Copy link
Member

Choose a reason for hiding this comment

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

Ah I see it is called from patchserver not from within the same class. You can make it a util function, but please do not use call to call it. It would be much better if you could pass any required configs or properties as arguments to the function.

originalFunc: HandleCall<RequestType, ResponseType>,
call: ServerCallWithMeta<RequestType, ResponseType>,
callback: SendUnaryDataCallback<unknown>
): void {
Copy link
Member

Choose a reason for hiding this comment

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

Are you sure the return type of originalFunc will always be void ?

Copy link
Contributor Author

@reggiemcdonald reggiemcdonald Aug 6, 2020

Choose a reason for hiding this comment

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

According to the grpc-js types, HandleCall is always going to be a void function

export type IgnoreMatcher = string | RegExp | ((str: string) => boolean);

export interface GrpcPluginOptions extends PluginConfig {
/* Omits tracing on any RPC methods that match any of
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/* Omits tracing on any RPC methods that match any of
/* Omits tracing on any GRPC methods that match any of

/* Omits tracing on any RPC methods that match any of
* the IgnoreMatchers in the ignoreRpcMethods list
*/
ignoreRpcMethods?: IgnoreMatcher[];
Copy link
Member

Choose a reason for hiding this comment

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

should this be called ignoreGrpcMethods ?

return false;
}

try {
Copy link
Member

Choose a reason for hiding this comment

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

please remove try catch, then function satisfiesPattern should simply return false instead of throwing an error

* @param ignoredMethods a list of matching patterns
* @param onException an error handler for matching exceptions
*/
export const _methodIsIgnored = (
Copy link
Member

Choose a reason for hiding this comment

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

this method should not throw anything, you can simply return boolean and make the same logic

@@ -72,6 +73,15 @@ export function patchServer(
) {
const self = this;

if (shouldNotTraceServerCall.call(plugin, call.metadata, name)) {
Copy link
Member

Choose a reason for hiding this comment

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

Use of call here is unnecessary and confusing. Making the function a private method of the class would be much better.

Suggested change
if (shouldNotTraceServerCall.call(plugin, call.metadata, name)) {
if (plugin.shouldNotTraceServerCall(call.metadata, name)) {

* Returns true if the server call should not be traced.
*/
function shouldNotTraceServerCall(
this: GrpcJsPlugin,
Copy link
Member

Choose a reason for hiding this comment

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

Please make this function a private member of the class.

reggiemcdonald added 2 commits August 12, 2020 15:38
Signed-off-by: reggiemcdonald <[email protected]>
Signed-off-by: reggiemcdonald <[email protected]>
@reggiemcdonald reggiemcdonald force-pushed the 1327-filter-exporter-requests branch from 91d23df to d2c67ec Compare August 12, 2020 17:00
@reggiemcdonald
Copy link
Contributor Author

Are there any more changes needed for this PR?

Copy link
Member

@dyladan dyladan left a comment

Choose a reason for hiding this comment

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

Sorry this fell too far down my backlog and got a little lost. Looks good to me after the changes thanks

@dyladan
Copy link
Member

dyladan commented Aug 19, 2020

@obecny and @vmarchaud have your concerns been addressed?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants