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: pino instrumentation #432

Merged
merged 11 commits into from
Apr 21, 2021
Merged

feat: pino instrumentation #432

merged 11 commits into from
Apr 21, 2021

Conversation

seemk
Copy link
Contributor

@seemk seemk commented Apr 19, 2021

Which problem is this PR solving?

Adds automatic injection of trace context to pino (5.x and 6.x) similarly to bunyan and winston instrumentations.

@seemk seemk requested a review from a team April 19, 2021 10:56
@codecov
Copy link

codecov bot commented Apr 19, 2021

Codecov Report

Merging #432 (7bad699) into main (230ce49) will not change coverage.
The diff coverage is n/a.

❗ Current head 7bad699 differs from pull request most recent head cb9ecb4. Consider uploading reports for the commit cb9ecb4 to get more accurate results

@@           Coverage Diff           @@
##             main     #432   +/-   ##
=======================================
  Coverage   95.83%   95.83%           
=======================================
  Files          15       15           
  Lines         624      624           
  Branches       94       94           
=======================================
  Hits          598      598           
  Misses         26       26           

@naseemkullah
Copy link
Member

cc @mcollina @davidmarkclements if you'd like to take a look

@vmarchaud vmarchaud requested a review from a team April 19, 2021 12:52
}

if (semver.satisfies(version, pino5Versions)) {
const proto = Object.getPrototypeOf(pinoModule());
Copy link
Member

Choose a reason for hiding this comment

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

do the constructor have a side effect ? I mean we are creating a new instance here which can be problematic

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Afaik it does not, it's just a factory function: https://github.com/pinojs/pino/blob/v5.17.0/pino.js#L63 so no global state gets set.

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 what would make more sense is to retrieve the lib/proto file to patch it, WDYT ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The thing with this is that the file hook gets called first, but in order to patch the prototype I need the value of writeSym. lib/proto gets loaded before lib/symbols, not sure if there is a non-hackish way to do it. Will give it some thought 🙂

Copy link
Member

Choose a reason for hiding this comment

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

Indeed, i don't think its possible then. Might be worth checking though

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed it to mixins patching now, should be simpler.

@mcollina
Copy link

I would like to ask why would you need to monkeypatch pino? Pino is inherently sync in how it logs, so there should not be no need to monkeypatch anything here.

(streams are async, but those are separate).

@mcollina
Copy link

could this be turned off anyway?

@naseemkullah
Copy link
Member

naseemkullah commented Apr 20, 2021

I would like to ask why would you need to monkeypatch pino? Pino is inherently sync in how it logs, so there should not be no need to monkeypatch anything here.

(streams are async, but those are separate).

The end goal is to 💯% offload the logic of adding span context fields (when available) to the logs. The span context consists of trace_id, span_id and trace_flags fields as per https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/overview.md#json-formats (though trace_flags seems to be missing in that example). Monkey patching via this instrumentation package is the means to that end.

could this be turned off anyway?

Yes, it will never be turned on unless the user has installed the opentelemetry-pino-instrumentation package and configured it in the instrumentations option of the registerInstrumentations() function, unless it becomes a turned on by default instrumentation (examples of turned on by default include http(s) , grpc, redis, ioredis, mysql, express, hapi, koa[would love to see fastify there as well ;) ]). In the event that it does become turned on by default, it could be overridden with enabled: false passed into the instrumentation config.

@dyladan
Copy link
Member

dyladan commented Apr 20, 2021

I wonder if "Instrumentation" is a bad name for this, since it isn't actually instrumenting anything? Maybe we should use some other name like LogInjection or something like that?

@dyladan
Copy link
Member

dyladan commented Apr 20, 2021

Also, pino supports "mixins" as a first party API. Maybe we should just release an official opentelemetry pino mixin that injects the span data. It would likely be a much more stable interface than patching internal methods.

@dyladan dyladan self-requested a review April 20, 2021 02:48
@dyladan
Copy link
Member

dyladan commented Apr 20, 2021

For now I'm retracting my approval until we are sure this is the direction we want to go.

@seemk
Copy link
Contributor Author

seemk commented Apr 20, 2021

I don't get it, it's meant for automatic instrumentation and is completely optional. If you want to use a mixin and have separate configuration for this, fine.

Besides, the spec even has a section for auto instrumentation about this exact same use case: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/overview.md#auto-instrumenting-existing-logging

Other languages already have similar mechanisms, e.g. Java

@naseemkullah is completely correct on this one.

I would like to ask why would you need to monkeypatch pino? Pino is inherently sync in how it logs, so there should not be no need to monkeypatch anything here.

(streams are async, but those are separate).

On a side note, what does it being sync or async have to do with this PR? It's about achieving log correlation.

@Flarna
Copy link
Member

Flarna commented Apr 20, 2021

To avoid monkey patching a pino API to register global mixins would be needed.

@vmarchaud
Copy link
Member

I wonder if "Instrumentation" is a bad name for this, since it isn't actually instrumenting anything? Maybe we should use some other name like LogInjection or something like that?

i would prefer to keep them as instrumentation, someone could decide to add metrics for how much logs you are emitting etc.

@mcollina
Copy link

The feature we added for supporting this use case was the mixin as @Flarna said! Can you please check if it's sufficient for the use case? In theory you can use it to add the relevant data to all the log lines: it was added specifically to support the APM use case.

If mixin is enough, I would recommend either:

  1. you just wrap the pino factory and add a mixin() option. A bit of care must be taken so that user-provided mixins will still be supported.
  2. add the concept of a global mixin to pino itself and you just use that in this library.

I do not have much time to work on this right now but happy to review. I'm ok with both approach with a slight preference to 1.

@seemk
Copy link
Contributor Author

seemk commented Apr 20, 2021

The feature we added for supporting this use case was the mixin as @Flarna said! Can you please check if it's sufficient for the use case? In theory you can use it to add the relevant data to all the log lines: it was added specifically to support the APM use case.

If mixin is enough, I would recommend either:

  1. you just wrap the pino factory and add a mixin() option. A bit of care must be taken so that user-provided mixins will still be supported.
  2. add the concept of a global mixin to pino itself and you just use that in this library.

I do not have much time to work on this right now but happy to review. I'm ok with both approach with a slight preference to 1.

I'm all for option 1, but with our current instrumentation API how can we achieve this? I am perhaps missing something here.

Edit: Can return the wrapper inside patch I guess. Reworking this a bit

@naseemkullah
Copy link
Member

naseemkullah commented Apr 20, 2021

The feature we added for supporting this use case was the mixin as @Flarna said! Can you please check if it's sufficient for the use case? In theory you can use it to add the relevant data to all the log lines: it was added specifically to support the APM use case.
If mixin is enough, I would recommend either:

  1. you just wrap the pino factory and add a mixin() option. A bit of care must be taken so that user-provided mixins will still be supported.
  2. add the concept of a global mixin to pino itself and you just use that in this library.

I do not have much time to work on this right now but happy to review. I'm ok with both approach with a slight preference to 1.

I'm all for option 1, but with our current instrumentation API how can we achieve this? I am perhaps missing something here.

Edit: Can return the wrapper inside patch I guess. Reworking this a bit

You probably know this but I can confirm that mixin works great with the following option passed in to pino :

      mixin: () => (getSpan(context.active())?.context() ?? {}),

@Flarna
Copy link
Member

Flarna commented Apr 20, 2021

Personally I'm in favor of option 2 as it requires no monkey patching of pino. Noone knows if we will be ever able to reliable monkey patch ESM modules therefore getting public APIs is future proof.

But clearly this can't happen within this PR.

@dyladan
Copy link
Member

dyladan commented Apr 20, 2021

I don't get it, it's meant for automatic instrumentation and is completely optional. If you want to use a mixin and have separate configuration for this, fine.

Instrument (verb): equip (something) with measuring instruments.

Since we are not measuring anything, we are not instrumenting the module.

Besides, the spec even has a section for auto instrumentation about this exact same use case: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/overview.md#auto-instrumenting-existing-logging

If spec says this it's probably fine for now. I would also prefer to take advantage of public, supported APIs. As @Flarna says we will not be able to monkeypatch once ESM is widely supported.

I generally support option 2, but since it relies on options which don't yet exist 1 is probably a better solution at the moment.

@dyladan
Copy link
Member

dyladan commented Apr 20, 2021

I'm going to approve anyways as this can always be reworked later.

@dyladan dyladan added the enhancement New feature or request label Apr 20, 2021
@seemk
Copy link
Contributor Author

seemk commented Apr 20, 2021

Changing it to mixin patching, don't merge yet. This will bring the min version to 5.14, but should be fine

@dyladan
Copy link
Member

dyladan commented Apr 20, 2021

Adding blocked label until you finish

@seemk
Copy link
Contributor Author

seemk commented Apr 20, 2021

Adding blocked label until you finish

@dyladan Done. Added additional tests regarding options and mixins handling. Upped the required version to 5.14.0 and ran both 5.14.0 and 6.x through an example app.

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.

Looks good

Comment on lines +62 to +65
args.splice(0, 0, {
mixin: instrumentation._getMixinFunction(),
});
return pinoModule(...(args as Parameters<Pino>));
Copy link
Member

Choose a reason for hiding this comment

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

This can be done in a single step I think

Suggested change
args.splice(0, 0, {
mixin: instrumentation._getMixinFunction(),
});
return pinoModule(...(args as Parameters<Pino>));
return pinoModule({ mixin: instrumentation._getMixinFunction() }, ...args);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, although this won't compile due to pino taking 0-2 args and ...(args as Parameters<Pino>) already expands to 2 args and it can't be untyped 😦

Copy link
Member

@blumamir blumamir Apr 21, 2021

Choose a reason for hiding this comment

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

We are under the case of args.length == 1, so it can also be written as return pinoModule({ mixin: instrumentation._getMixinFunction() }, args[0] as pino.DestinationStream) maybe?

@dyladan dyladan removed the blocked label Apr 20, 2021
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.

lgtm, thx for contribution, one comment


disable() {
super.disable();
this._isEnabled = false;
Copy link
Member

Choose a reason for hiding this comment

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

hmm we already have such functionality, can you add todo here to remove it once the base class will be changed. I created an issue for that -> open-telemetry/opentelemetry-js#2131

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a TODO, found it kinda awkward as well 😄

Copy link

@mcollina mcollina left a comment

Choose a reason for hiding this comment

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

lgtm, really good work!

@dyladan dyladan merged commit 60dd7d1 into open-telemetry:main Apr 21, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants