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(plugin-http): add plugin hooks before processing req and res #963

Merged
merged 8 commits into from May 13, 2020
Merged

feat(plugin-http): add plugin hooks before processing req and res #963

merged 8 commits into from May 13, 2020

Conversation

blumamir
Copy link
Member

Enable users of the http plugin to register hooks on http request and response. The hooks are called before the stream begins to handle data, allowing the plugin user to register to events.

Which problem is this PR solving?

  • Access to the request and response stream, which enable to capture the http body in the plugin.
    Add custom span attributes to the span ASAP.
  • In case of error or exception in the request handling, the existing applyCustomAttributesOnSpan function is never called, and it is impossible to add custom attributes to span.

Short description of the changes

  • Add two new options to the HttpPluginConfig: requestHook, responseHook, similar to the existing applyCustomAttributesOnSpan option. If set, these callbacks are called by the http plugin, allowing the plugin user to add custom span attributes and execute logic right after the stream creation and before data is sent on it.

@codecov-io
Copy link

codecov-io commented Apr 13, 2020

Codecov Report

Merging #963 into master will increase coverage by 0.01%.
The diff coverage is 100.00%.

@@            Coverage Diff             @@
##           master     #963      +/-   ##
==========================================
+ Coverage   95.10%   95.12%   +0.01%     
==========================================
  Files         213      213              
  Lines        8914     8944      +30     
  Branches      800      804       +4     
==========================================
+ Hits         8478     8508      +30     
  Misses        436      436              
Impacted Files Coverage Δ
packages/opentelemetry-plugin-http/src/http.ts 97.87% <100.00%> (+0.17%) ⬆️
...y-plugin-http/test/functionals/http-enable.test.ts 97.02% <100.00%> (+0.12%) ⬆️

@vmarchaud
Copy link
Member

Overall it seems fine but i don't see the point of keeping applyCustomAttributesOnSpan if we have this system. Should we just drop it and use those hooks ? Paging @OlivierAlbertini since if i remember correctly you had implement that because you needed it

@blumamir
Copy link
Member Author

Overall it seems fine but i don't see the point of keeping applyCustomAttributesOnSpan if we have this system. Should we just drop it and use those hooks ? Paging @OlivierAlbertini since if i remember correctly you had implement that because you needed it

Thanks for the comment. The applyCustomAttributesOnSpan callback is still needed since it is called after the response ended and all the data is available on the request and response objects (like the response http headers for example).

@dyladan
Copy link
Member

dyladan commented Apr 21, 2020

In general, I'm worried about the performance impact of another function which is called on every request and response on both the client and the server side, and I'm not sure I see the value.

@blumamir
Copy link
Member Author

blumamir commented Apr 21, 2020

In general, I'm worried about the performance impact of another function which is called on every request and response on both the client and the server side, and I'm not sure I see the value.

Thanks for taking a look
Same as the current applyCustomAttributesOnSpan, these hooks are called only if configured in the HttpPluginConfig. If not set, no extra function will be called.
Regarding the value:

  • I need to sometimes also capture the request and response body, which requires to register to the stream events: on('data', ...), and to patch the write function of the ClientRequest before it's called.
  • The existing function applyCustomAttributesOnSpan is called only if the request ended successfully. In case of error \ exception, the plugin user has no option to add custom attributes with the current hook.

@@ -200,6 +201,7 @@ export class HttpPlugin extends BasePlugin<Http> {
{ hostname }
);
span.setAttributes(attributes);
this._onNewResponse(span, response);
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 that probably it would be better if you modify this whole concept a bit because of performance issues.

First Approach
During a plugin initialisation check if user defined a hooks in config if that is the case then add simply a different event listener (request.on) with your modifications plugin._onNewRequest etc. otherwise use the one without your modifications.
This will have some code duplication but in fact 0 performance issue if user doesn't define any hooks.

Second Approach
Also during initialisation check if user defined a hooks in config but this time add separate event listener and then call plugin._onNewRequest.
This could be achieved by adding extra parameter to _traceClientRequest.
This would be basically a callback for setting up the desired events, but if user doesn't define the hooks this callback will be simply something like NoopHookCallback which will look like function () {} and can be called before returning the request inside function _traceClientRequest

WDYT ?

Copy link
Member Author

Choose a reason for hiding this comment

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

Thank you for the review,

Regarding the performance impact, when these function are not set, the only performance issue is function call to _onNewRequest \ _onNewResponse, and executing an if: if (this._config.requestHook). Is it something to worry about?

On plugin initialisation I still have no request`responseobjects on which I can add events. I call the function to register events as soon as therequest` object is available (in incoming or outgoing functions).

What I can do is to register on the http.Server request Event, but then I'll get a callback without the relevant span, and it will be called every-time, also when the request is ignored in open telemetry.

Copy link
Member

Choose a reason for hiding this comment

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

Regarding the performance impact, when these function are not set, the only performance issue is function call to _onNewRequest \ _onNewResponse, and executing an if: if (this._config.requestHook). Is it something to worry about?

Yes. When this is done on every single request and response on both client and server it is something to worry about. Function call overhead is non-negligible. Right now you have a function which is called every time and an if-check that happens inside the function. Moving the if-check outside of the function and only calling it if there is something configured would be drastically more performant.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks @dyladan
I wasn't aware of the impact of the function call. Will perform the if-check before calling the function and push a new commit

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 the change you did will have exactly the same number of checks then previously

request.on('some event', a);

function a() {
  b();
  console.log('bar');
}

function b() {
  if (config.c) {
    console.log('foo');
  }
}

is exactly the same as

request.on('some event', a);

function a() {
  if (config.c) {
    b();
  }
  console.log('bar');
}

function b() {
  console.log('foo');
}

What I tried to say is to refactor into something like this

if (config.c) {
  request.on('some event', a1);
} else {
  request.on('some event', a2);
}

function a1() {
  b();
  console.log('bar');
}

function a2() {
  console.log('bar');
}

function b() {
  console.log('foo');
}

this way you will have some duplication in code, but when it is about performance, you will check only once for config.c rather then every time on some event

Copy link
Member Author

Choose a reason for hiding this comment

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

Sorry for the delay, missed the comment.
I like the idea of registering to the EventEmitter interface.
In this case, the hook is exposing the request object (which we get as the patched function parameter) so the user is able to do function calls like request.on('some event', a1);

Copy link
Member Author

Choose a reason for hiding this comment

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

To be more precise, the above code still execute the if (config.c) per request which is the same as what current implementation is doing

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.

LGTM after latest changes

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.

LGTM please fix the build

@mayurkale22
Copy link
Member

@OlivierAlbertini would be nice if you can review this one.

@dyladan dyladan added the enhancement New feature or request label May 4, 2020
@mayurkale22 mayurkale22 requested a review from obecny May 4, 2020 21:37
@mayurkale22 mayurkale22 added the Merge:LGTM This PR is ready to be merged by a Maintainer (has enough valid approvals, successful build, etc.) label May 13, 2020
@dyladan
Copy link
Member

dyladan commented May 13, 2020

@blumamir are you going to make the changes suggested by @obecny ?

@blumamir
Copy link
Member Author

@blumamir are you going to make the changes suggested by @obecny ?

I like the concept of using the event emitter, but specifically in this case the request and response objects are received via patched function parameters:

        return function incomingRequest(this: {}, event: string, ...args: unknown[]): boolean {
            ...
            const request = args[0] as IncomingMessage;
            const response = args[1] as ServerResponse & { socket: Socket };

Or return value from patched function

                const request: ClientRequest = plugin._safeExecute(
                    span,
                    () => original.apply(this, [optionsParsed, ...args]),
                    true
                );

So there is no place where I can register once for an event and get the request and response objects when they are are created.
I think the current implementation (adding two extra if's per request to check the config), is the right way to go here.

@dyladan
Copy link
Member

dyladan commented May 13, 2020

@obecny I was going to merge this but would like your go/no-go.

Copy link
Member

@obecny obecny left a comment

Choose a reason for hiding this comment

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

@dyladan I don't want to block it anymore, we both wanted this to be done in a way I described - split this into 2 events instead of one, but if that won't happen then we can have it as it is :), thx

@dyladan
Copy link
Member

dyladan commented May 13, 2020

I'm going to merge it, we can address performance concerns if they come up.

@mayurkale22 mayurkale22 merged commit 6b2a9b7 into open-telemetry:master May 13, 2020
pichlermarc pushed a commit to dynatrace-oss-contrib/opentelemetry-js that referenced this pull request Dec 15, 2023
* feat: update webpack outside of examples

* chore: replace istanbul-instrumenter-loader with @jsdevtools/coverage-istanbul-loader

Co-authored-by: Valentin Marchaud <contact@vmarchaud.fr>
martinkuba pushed a commit to martinkuba/opentelemetry-js that referenced this pull request Mar 13, 2024
* feat: update webpack outside of examples

* chore: replace istanbul-instrumenter-loader with @jsdevtools/coverage-istanbul-loader

Co-authored-by: Valentin Marchaud <contact@vmarchaud.fr>
martinkuba pushed a commit to martinkuba/opentelemetry-js that referenced this pull request Mar 16, 2024
* feat: update webpack outside of examples

* chore: replace istanbul-instrumenter-loader with @jsdevtools/coverage-istanbul-loader

Co-authored-by: Valentin Marchaud <contact@vmarchaud.fr>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request Merge:LGTM This PR is ready to be merged by a Maintainer (has enough valid approvals, successful build, etc.)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants