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

Add Plugin interface and BasePlugin class #110

Merged
merged 8 commits into from
Jul 26, 2019

Conversation

mayurkale22
Copy link
Member

Idea: The automatic-tracer would use this interface to automatically patch list of configured/provided modules i.e. Every instrumentation plugin would implement this interface.

Something like this.

// @example
export class RedisPlugin extends Plugin {
    constructor() {}
    enable<T>(moduleExports: T, tracer: Tracer): T {}
    disable(): void {}
}

Let me know if this makes sense or suggest an alternative.

@codecov-io
Copy link

codecov-io commented Jul 19, 2019

Codecov Report

Merging #110 into master will not change coverage.
The diff coverage is n/a.

@@           Coverage Diff           @@
##           master     #110   +/-   ##
=======================================
  Coverage   98.74%   98.74%           
=======================================
  Files          29       29           
  Lines        1915     1915           
  Branches      221      221           
=======================================
  Hits         1891     1891           
  Misses         24       24

export interface Plugin {
/**
* Method that enables the instrumentation patch.
* @param moduleExports nodejs module exports from the module to patch
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you give an example of this?

Copy link
Member Author

Choose a reason for hiding this comment

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

High level stuff, something like this:

const DEFAULT_INSTRUMENTATION_MODULES = ['http', 'https', ...];
export class AutomaticTracer implements types.Tracer {
  constructor(){
    this.loadPlugins(DEFAULT_INSTRUMENTATION_MODULES);
  }

  loadPlugins(modulesToPatch: string[]) {
    // load plugin using its instrumentation plugin package name ie. opentelemetry-instrumentation-http.
    // Each plugin module should implement the Plugin interface and export an instance named as "plugin".
    const plugin: Plugin = require(moduleName as string).plugin;

    // Hook into the modules
    Hook(modulesToPatch, function (exports, name, basedir) {
      return plugin.enable(exports, tracer);
    }
  }
}

* Name can be either AutomaticTracer or just Tracer or something else.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks - I actually just meant, what would be an example value for "moduleExports"? And would it make sense to add that to the JSDoc comment for the param?

Copy link
Member

Choose a reason for hiding this comment

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

I don't think it can be typed since it's what the patched module export as object

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, makes sense on not giving it a strict type - I mostly just wanted to understand how the parameter is used and the examples clarify that!

* normally be exposed by the required module. ex: `http`, `https` etc.
* @param tracer a tracer instance
*/
enable<T>(moduleExports: T, tracer: Tracer): T;
Copy link
Member

Choose a reason for hiding this comment

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

Since all plugins will be called in the same way by some sort of instrumenter, should there be a method/parameter to configure the plugin? Having that in the constructor would mean we can't validate it from the interface.

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure. Can we add another optional param (config)? What are the possible attributes for config? ex: version, default attributes like these?

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 config is basically just an object and the possible properties depend on each plugin.

Copy link
Member

Choose a reason for hiding this comment

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

One other thing that seems to be missing too is a way to define the supported versions/modules on every plugin in a standard way.

Copy link
Member Author

Choose a reason for hiding this comment

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

enable(moduleExports: T, tracer: Tracer, 
       version: string, config?: {[key: unknown]: unknown}): T;

WDYT?

Copy link
Member

Choose a reason for hiding this comment

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

config?: {[key: unknown]: unknown}

I think it's safe to assume keys will be strings, especially if it's an object since other types are not possible.

version: string

I think maybe the module name and version filters should be protected properties on the base tracer that the implementations can set. I don't think these should be set by the caller.

Copy link
Member Author

Choose a reason for hiding this comment

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

Added the config object. ptal

@mayurkale22 mayurkale22 changed the title Add Plugin interface Add Plugin interface and BasePlugin class Jul 25, 2019
@mayurkale22 mayurkale22 added this to the API complete milestone Jul 25, 2019
Copy link
Member

@rochdev rochdev left a comment

Choose a reason for hiding this comment

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

Small nit, but otherwise LGTM. I also thought a bit more about the config and maybe it could be passed to the constructor instead, but that can be reevaluated later.

import { Tracer, Plugin } from '@opentelemetry/types';

/** This class represent the base to patch plugin. */
export abstract class BasePlugin<T> implements Plugin<T> {
Copy link
Contributor

Choose a reason for hiding this comment

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

I asked this question on anther PR, but asking it here again for completeness: does it make sense for BasePlugin to be part of opentelemetry-core given that it's a Node platform specific concept?

Copy link
Member Author

Choose a reason for hiding this comment

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

I am just afraid about circular dependency if we move BasePlugin in Node-Tracer package because any instrumented plugin (ex: package/plugin-http) needs to extends BasePlugin from Node-Tracer and add same package in Node-Tracer for enabling the patching.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, makes sense! Yeah, I think it's fine then.

Copy link
Member

Choose a reason for hiding this comment

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

If it's Node specific, then IMO it definitely shouldn't be in core. Maybe we need a new package to store this? Otherwise, any way we can make sure that the base class is not Node specific?

Copy link
Contributor

Choose a reason for hiding this comment

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

Well, the whole concept of a Plugin as a runtime thing is somewhat Node specific. A couple options:

  • Have a package like node-core that contains Node-specific shared functionality. There could also be a web-core package that has web specific utilities in the future.
  • Just make Plugin an interface and move it to the types package - we lose the little bit of reusable code that it provides, but this isn't hard to duplicate for each plugin.

Copy link
Member

Choose a reason for hiding this comment

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

My only worry with a compile-time approach is that there are a lot of different bundlers out there, so we can't assume everyone uses Webpack. There are also a lot of apps and libraries out there that do not require compilation at all.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, I agree. The challenge with the runtime approach for patching something like a framework is that the build process will have renamed properties in the framework and/or inlined functions (or even whole classes!). So you can't really know how to monkey-patch a framework at runtime.

Our current approach in OpenCensus Web is to only patch the core web APIs, which always have defined names at runtime, and as a plus side will work no matter how you compile your JS and no matter what web framework you use :)

Copy link
Contributor

Choose a reason for hiding this comment

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

But to come back to why I wonder if the Plugin class as defined may fit well for the web, those core browser APIs don't have any notion of require or modules, they are just globals. (There is some module work in Chrome and new browsers for some new things, but most are still globals)

We could still have Plugin represent the patch/unpatch methods but they would be patching globals not a module object.

Copy link
Member

Choose a reason for hiding this comment

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

the build process will have renamed properties in the framework

True, but many frameworks expose some sort of global object that is not renamed (otherwise it couldn't be interacted with). I think maybe a good compromise would be having compile-time requirements that will allow the plugins to work at runtime. Then it would be possible to handle the requirements automatically with a Webpack loader, but if the user is not using Webpack they can still do it manually. So basically at compile time, anything needed by the plugin would simply get exposed, and then available at runtime for the plugin to do its magic. That approach would make it a lot easier for users to do the compile-time step manually if needed.

We could still have Plugin represent the patch/unpatch methods but they would be patching globals not a module object.

I think that's a fair assumption.

Copy link
Contributor

Choose a reason for hiding this comment

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

Those are cool ideas! Definitely worth exploring more once we get deeper into the browser side of this.

dyladan added a commit to dyladan/opentelemetry-js that referenced this pull request Sep 9, 2022
* chore: release 1.0.2 proposal

* chore: changelog
dyladan added a commit to dyladan/opentelemetry-js that referenced this pull request Sep 9, 2022
* chore: release 1.0.2 proposal

* chore: changelog
pichlermarc pushed a commit to dynatrace-oss-contrib/opentelemetry-js that referenced this pull request Dec 15, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
No open projects
Development

Successfully merging this pull request may close these issues.

None yet

5 participants