opentelemetry-js: Unable to rely on AsyncHooksScopeManager when wrapping async functions

Please answer these questions before submitting a bug report.

What version of OpenTelemetry are you using?

0.3.3

What version of Node are you using?

13.6.0

What did you do?

I’m using the AsyncHooksScopeManager to keep track of my current scope. I’m expecting to be able to call tracer.withSpan(...) and from within call tracer.getCurrentSpan() and have it provide me with the span I am executing inside of. I’ve provided a failing unit test

import { AsyncHooksScopeManager } from '@opentelemetry/scope-async-hooks'
import { InMemorySpanExporter } from '@opentelemetry/tracing'
import {
  BasicTracerRegistry,
  SimpleSpanProcessor,
} from '@opentelemetry/tracing'

function makeTracer(exporter: InMemorySpanExporter) {
  const config = {
    scopeManager: new AsyncHooksScopeManager(),
  }
  const registry = new BasicTracerRegistry()
  registry.addSpanProcessor(new SimpleSpanProcessor(exporter))
  const tracer = registry.getTracer('tracer', undefined, config)
  return tracer
}

describe('async handling should fail', () => {
  const exporter = new InMemorySpanExporter()

  async function demo(ms): Promise<string> {
    const tracer = makeTracer(exporter)

    const span = tracer.startSpan('my span')

    return tracer.withSpan(span, async () => {
      await new Promise(resolve => setTimeout(resolve, ms))
      tracer.getCurrentSpan().addEvent('root-event', {})

      return 'ok'
    })
  }

  it('will not be able to retrieve the current span from within the `withSpan` block', async () => {
    const result = await demo(0)
    expect(result).toBe('ok')
    expect(exporter.getFinishedSpans().length).toBe(1)
    expect(exporter.getFinishedSpans()[0].events.length).toBe(1)
  })
})

What did you see instead?

Instead of giving me the current span, the ScopeManager has continued executing (because it doesn’t await) and has deleted the scope for that span. So calling getCurrentSpan() is returning undefined

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 52 (30 by maintainers)

Commits related to this issue

Most upvoted comments

@vmarchaud, thank you for the feedback. I’ll wait for the implementation. Thanks again for the great repo! Take care 😃

@satazor It has specific issue but depending on how you want to use it, it may have no impact. However the method is still not implemented on the tracer so its quite hard to use it for now

@yosiat I found a fix that involves the way values are stored. Checkout my PR but i believe a similar fix would be possible in cls-hooked

No I’m not offended at all 😃 please take a look and see if you can catch what i’m missing

This would have to be propagated through to the Tracer level too. The tracers will need to offer a withSpanAsync function which uses the withAsync otherwise the change would be inaccessible at the application level

https://davidwalsh.name/javascript-detect-async-function

const isAsync = myFunction.constructor.name === "AsyncFunction";

This apparently works?

async function getPerson(id) { .. }

function getRecord(id) {
   return getPerson(id)
      .then(function(person){ return { data: person }; });
}

const isAsync = getRecord.constructor.name === "AsyncFunction"; // false

It won’t work Perhaps, you could always do Promise.resolve(...) but I’m not sure that is a solution. Also I’m agree that async function should be supported.