Automatically detect method invocation and manage spans

269 Views Asked by At

I'm Trying to instrument my nodejs application such that whenever a method is invoked at runtime it'll create a span to trace the method automatically and would require minimal code changes.

So I'm instrumenting my nodejs application with opentelemetry. I'm implementing manual instrumentation to trace the flow by starting the span when a method starts and ending the span before it finishes however I find this way very crude and primitive, in this way I'll have to make changes throughout my application to add instrumentation. Is there some better way to achieve this ? I tried using the decorator. however, decorators are not allowed for functions which do not belong to some class.

So for example I have program and currently I'm doing something like this to trace the application.

const { NodeTracerProvider, SimpleSpanProcessor, ConsoleSpanExporter, SpanKind } = require('@opentelemetry/node');
const { registerInstrumentations } = require('@opentelemetry/instrumentation');
const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http');
const { Span } = require('@opentelemetry/api');

const provider = new NodeTracerProvider();
provider.register();

registerInstrumentations({
  instrumentations: [
    new HttpInstrumentation(),
  ],
});

const consoleExporter = new ConsoleSpanExporter();
const spanProcessor = new SimpleSpanProcessor(consoleExporter);
provider.addSpanProcessor(spanProcessor);

function a() {
  const span = provider.getTracer('example-tracer').startSpan('a', { kind: SpanKind.INTERNAL });

  // Do something

  span.end();
}

function b() {
  const span = provider.getTracer('example-tracer').startSpan('b', { kind: SpanKind.INTERNAL });

  // Do something

  span.end();
}

function c() {
  const span = provider.getTracer('example-tracer').startSpan('c', { kind: SpanKind.INTERNAL });

  // Do something

  span.end();
}

function d() {
  const span = provider.getTracer('example-tracer').startSpan('d', { kind: SpanKind.INTERNAL });

  // Do something

  span.end();
}

function e() {
  const span = provider.getTracer('example-tracer').startSpan('e', { kind: SpanKind.INTERNAL });

  // Do something

  span.end();
}

function main() {
  const span = provider.getTracer('example-tracer').startSpan('main', { kind: SpanKind.INTERNAL });

  // Do something

  if (condition1) {
    a();
  } else if (condition2) {
    b();
  } else if (condition3) {
    c();
  } else if (condition4) {
    d();
  } else if (condition5) {
    e();
  }

  span.end();
}

main();

Then when some http request invokes the main method it should produce trace like


main() span (Root span)
|
|--- a() span
|    |
|    |--- b() span
|
|--- b() span
|
|--- c() span
|    |
|    |--- d() span
|         |
|         |--- a() span (looping back to 'a' from 'd')
|
|--- d() span
|    |
|    |--- a() span (looping back to 'a' from 'd')
|
|--- e() span
|    |
|    |--- b() span


1

There are 1 best solutions below

0
On

The lack of function decorators can be remedied with simple wrappers. For example, this function:

function wrap<Fun extends (..._: never[]) => unknown>(f: Fun) {
  return function (...args: Parameters<Fun>) {
    const span = provider
      .getTracer("example-tracer")
      .startSpan(f.name ?? "<unknown>", { kind: SpanKind.INTERNAL });
    const result = f(...args);
    span.end();
    return result;
  };
}

Can be used to wrap any non-async non-method function:

const a = wrap(function a(){
  // Do something
})