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

feature(trace): adds named tracer factory #420

Closed
wants to merge 24 commits into from

Conversation

bg451
Copy link
Member

@bg451 bg451 commented Oct 10, 2019

Which problem is this PR solving?

Short description of the changes

This PR adds in the TracerFactory type and updates the core's global tracer to use a tracerfactory instead of a tracer. Also modifies the TracerDelegate to be for a factory. Adds in the respective implementations for each tracer implementation.

Should name be a mandatory field in TS def? According to the specification an empty or null string can be passed as the name.

@bg451 bg451 marked this pull request as ready for review October 10, 2019 21:54
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.

My biggest question is that there are 3 methods of getting a tracer factory:

  1. new them directly
  2. call static .instance method
  3. call exported getTracerFactory function

It is not clear to me which is the best or recommended way to get a tracer factory. If the answer is not 1, then should we disallow it by making the constructor private?

@@ -17,14 +17,14 @@ if (EXPORTER.toLowerCase().startsWith('z')) {
exporter = new JaegerExporter(options);
}

const tracer = new BasicTracer();
// Initialize the OpenTelemetry APIs to use the BasicTracer bindings
opentelemetry.initGlobalTracerFactory(new BasicTracerFactory());
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 use BasicTracerFactory.instance() instead of directly calling new?

Copy link
Member

Choose a reason for hiding this comment

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

IMO this (initialization of global tracer factory and BasicTracerFactory) seems a little burdensome for the end users. I was thinking something like this:

// In case of BasicTracer
const opentelemetry = require('@opentelemetry/tracing');
opentelemetry.getTracerFactory().getTracer(); // should return the default BasicTracer.
opentelemetry.getTracerFactory().getTracer('mongodb'); // with name
opentelemetry.getTracerFactory().getTracer('redis', '1.0.0'); // with name and version
opentelemetry.getTracerFactory().addSpanProcessor(...);

// In case of NodeTracer
const opentelemetry = require('@opentelemetry/node');
opentelemetry.getTracerFactory().getTracer(); // should return the default NodeTracer.

// In case of WebTracer
const opentelemetry = require('@opentelemetry/web');
opentelemetry.getTracerFactory().getTracer(); // should return the default WebTracer.

The getTracerFactory function is responsible for returning the corresponding TracerFactory and we should create (and assign) the TracerFactory instance statically once you load the library (maybe in index.ts). WDYT?

const { SimpleSpanProcessor } = require('@opentelemetry/tracing');
const { JaegerExporter } = require('@opentelemetry/exporter-jaeger');
const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin');
const EXPORTER = process.env.EXPORTER || '';

function setupTracerAndExporters(service) {
const tracer = new NodeTracer({
const factory = new NodeTracerFactory({
Copy link
Member

Choose a reason for hiding this comment

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

instance?

packages/opentelemetry-node/README.md Show resolved Hide resolved
packages/opentelemetry-tracing/src/BasicTracerFactory.ts Outdated Show resolved Hide resolved
private _spanProcessors: SpanProcessor[] = [];
private _config?: BasicTracerConfig;

constructor(config?: BasicTracerConfig) {
Copy link
Member

Choose a reason for hiding this comment

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

constructor could be private, forcing users to use BasicTracerFactory.instance which would prevent creation of multiple NodeTracerFactories. Is there a use case where we would want to allow the creation of multiple or should it always be a singleton?

Copy link
Member Author

Choose a reason for hiding this comment

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

@dyladan
Copy link
Member

dyladan commented Oct 25, 2019

From #434

The duplication between BasicTracerFactory and NodeTracerFactory seems like a good candidate for inheritance

class AbstractTracerFactory<T extends types.Tracer> implements types.TracerFactory {
  private static _singletonInstance: types.TracerFactory;
  private readonly _tracers: Map<String, T> = new Map();

  protected abstract create(): T;

  getTracer(name: string = "", version?: string): T {
    const key = name + (version != undefined ? version : "");
    if (this._tracers.has(key)) return this._tracers.get(key)!;

    const tracer = this.create();
    this._tracers.set(key, tracer);
    return tracer;
  }

  static get instance(): types.TracerFactory {
    return this._singletonInstance || (this._singletonInstance = new this());
  }
}

class NodeTracerFactory extends AbstractTracerFactory<NodeTracer> {
  protected create() {
    return new NodeTracer();
  }
}

class BasicTracerFactory extends AbstractTracerFactory<BasicTracer> {
  protected create() {
    return new BasicTracer();
  }
}

@bg451 bg451 requested a review from markwolff as a code owner October 29, 2019 21:01
@bg451
Copy link
Member Author

bg451 commented Oct 30, 2019

I'm having second thoughts on the singleton approach for the factory. There is a use case for having multiple factories if your runtime contains different applications and needs to be provided different trace factory configurations. The specification mentions delegating to a different provider for this use case but honestly that's just another layer of abstraction, it would be simpler imo to just have users do new NodeTracerFactory(myConfig). @dyladan @mayurkale22 I'd like to propose removing the singleton factory for this PR and revisit the idea in the future if a use case arises. How does that sound?

@dyladan
Copy link
Member

dyladan commented Oct 30, 2019

imho we should be deviating from the spec as little as possible. If you believe the spec should be changed that is a separate issue. Maybe it could be written in a more language/implementation
agnostic manner? There is currently a working group to make the spec less language specific that might be interested in this.

Maybe the spec could, instead of specifying a static tracer factory with a delegate, say something along the lines of "support multiple tracer factory configurations for runtimes with multiple applications"

edit: I assume by "static" you are referring to the singleton pattern?

@bg451
Copy link
Member Author

bg451 commented Oct 30, 2019

Yeah meant singleton, still drinking my morning coffee :)

I don't think we'd be deviating much if at all from the specification as it doesn't call for a singleton. It does mention having a global tracer factory registry, but that is what initGlobalTracerFactory and getTracerFactory/getTracer covers.

@dyladan
Copy link
Member

dyladan commented Oct 30, 2019

Yea I'm just looking over it now. It looks like it allows for the creation of multiple tracer factories, and the delegate uses the wording "may" which to me just suggests an implementation rather than requiring it.

@austinlparker
Copy link
Member

FWIW I think it's worth remembering that a primary goal of the spec is, to quote,

This document defines common principles that will help designers create language libraries that are easy to use, are uniform across all supported languages, yet allow enough flexibility for language-specific expressiveness.

(from https://github.com/open-telemetry/opentelemetry-specification/blob/a131ae1ccabee8cc8f3b8cf109b81be378006fea/specification/library-guidelines.md)

I'm not quite sure what the most 'javascript-y' way to accomplish the named tracer factory is in this library, but I'd hew towards doing what feels natural for this language.

@bg451
Copy link
Member Author

bg451 commented Oct 31, 2019

@mayurkale22 @dyladan @OlivierAlbertini I've updated this to not use the singleton and did a bit of refactoring, ptal!

import { BasicTracer } from './BasicTracer';
import { SpanProcessor } from './SpanProcessor';

export abstract class AbstractBasicTracerFactory
Copy link
Member

Choose a reason for hiding this comment

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

Why is the abstract factory specific to basic tracer? Why not just AbstractTracerFactory and _newTracer would return types.Tracer?

BasicTracerFactory and NodeTracerFactory would inherit from it as NodeTracer and BasicTracer both implement types.Tracer

Copy link
Member Author

Choose a reason for hiding this comment

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

It's due to this line specifically, since span processors is not an API level concept. https://github.com/open-telemetry/opentelemetry-js/pull/420/files#diff-7de0ceee2be4a0e5c9c8ba090fcc1ce4R28

Copy link
Member

Choose a reason for hiding this comment

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

But the base class they inherit from should be more general. BasicTracer and WebTracer are both just specializations of types.Tracer. The abstract base class is just an abstract tracer factory which all other tracer factories inherit from. Those more specific tracer factories would then have more specific typings.

class AbstractTracerFactory {
  protected abstract _newTracer(): types.Tracer;
}

class BasicTracerFactory {
  protected _newTracer(): BasicTracer {
    return new BasicTracer();
  }
}

class WebTracerFactory {
  protected _newTracer(): WebTracer {
    return new WebTracer();
  }
}

Copy link
Member Author

Choose a reason for hiding this comment

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

Cool, updated the abstract factory to be more general.

return tracer;
}

protected abstract _newTracer(): BasicTracer;
Copy link
Member

Choose a reason for hiding this comment

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

Why is this BasicTracer and not types.Tracer?

export class NodeTracerFactory implements types.TracerFactory {
private readonly _tracers: Map<string, NodeTracer> = new Map();
private _spanProcessors: SpanProcessor[] = [];
export class NodeTracerFactory extends AbstractBasicTracerFactory {
Copy link
Member

Choose a reason for hiding this comment

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

It seems weird to me the NodeTracerFactory extends AbstractBasicTracerFactory when NodeTracer and BasicTracer are different

BasicTracerConfig,
} from '@opentelemetry/tracing';

export class WebTracerFactory extends AbstractBasicTracerFactory {
Copy link
Member

Choose a reason for hiding this comment

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

Same here, I think it should just inherit from a more general AbstractTracerFactory

this._config = config;
}

protected _newTracer(): BasicTracer {
Copy link
Member

Choose a reason for hiding this comment

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

Why is this BasicTracer return type not WebTracer?

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.

I have added few minor comments, typos or just upvoted for the existing issue, there are some places which are missing jsdoc too. I think the example for examples/tracer-web is not updated.

this._tracerFactory = tracerFactory || null;
this._fallbackTracerFactory =
fallbackTracerFactory || new NoopTracerFactory();
this._currentTracerFactory =
Copy link
Member

Choose a reason for hiding this comment

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

should we have here this.start(); instead?

packages/opentelemetry-tracing/README.md Outdated Show resolved Hide resolved
packages/opentelemetry-types/src/trace/TracerFactory.ts Outdated Show resolved Hide resolved
packages/opentelemetry-web/src/WebTracerFactory.ts Outdated Show resolved Hide resolved
@bg451 bg451 requested a review from mayurkale22 November 7, 2019 19:01
@@ -15,3 +15,4 @@
*/

export * from './NodeTracer';
Copy link
Member

Choose a reason for hiding this comment

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

Do you think we should remove this now? As NodeTracerFactory is responsible for tracer creation and all.

* @param name identifies the instrumentation library
* @param [version] is the semantic version of the library.
*/
getTracer(name: string, version?: string): types.Tracer {
Copy link
Member

Choose a reason for hiding this comment

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

should return type T

}
}

getTracer(name: string = '', version?: string): BasicTracer {
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 reason to override the default getTracer implementation?

* addSpanProcessor adds a {@link SpanProcessor} to the factory, applying it
* to all new and existing tracers.
*/
addSpanProcessor(spanProcessor: SpanProcessor): void {
Copy link
Member

Choose a reason for hiding this comment

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

Why is this not on AbstractTracerFactory?

Copy link
Member Author

@bg451 bg451 Nov 11, 2019

Choose a reason for hiding this comment

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

API vs SDK. Again, addSpanProcessor is a sdk concept and doesn't exist at the API level. If the AbstractTracerFactory is going to be in core and reusable by other implementations, it's should be at the level of the API.

/**
* WebTracerFactory produces named tracers.
*/
export class WebTracerFactory extends BasicTracerFactory {
Copy link
Member

Choose a reason for hiding this comment

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

Why does this inherit BasicTracerFactory rather than AbstractTracerFactory<WebTracer>>?

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 don't see the reason why we would reimplement getTracer to do the addSpanProcessor loops, the only thing that's different is _newTracer. That being said I don't really have a strong opinion here, can go ahead and make the change

import { NodeTracerConfig } from './config';
import { BasicTracerFactory } from '@opentelemetry/tracing';

export class NodeTracerFactory extends BasicTracerFactory {
Copy link
Member

Choose a reason for hiding this comment

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

Why does this inherit BasicTracerFactory rather than AbstractTracerFactory<NodeTracer>>?

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 don't see the reason why we would reimplement getTracer to do the addSpanProcessor loops, the only thing that's different is _newTracer. That being said I don't really have a strong opinion here, can go ahead and make the change

import { NoopTracer } from './NoopTracer';

export class NoopTracerFactory implements types.TracerFactory {
private readonly _tracer: types.Tracer;
Copy link
Member

Choose a reason for hiding this comment

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

Why not omit constructor entirely and do:

  private readonly _tracer = new NoopTracer();

@dyladan
Copy link
Member

dyladan commented Jan 22, 2020

Closing because it is not longer relevant

@dyladan dyladan closed this Jan 22, 2020
@mayurkale22 mayurkale22 self-assigned this Sep 26, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants