opentelemetry-js icon indicating copy to clipboard operation
opentelemetry-js copied to clipboard

Deno Support

Open alexander-bzikadze opened this issue 3 years ago • 9 comments

Are there any plans for supporting Deno runtime? Perhaps it is a bit early to speak of it, however it seems rather possible due to low number of dependencies for opentelemetry implementation. Deno tries to support browser APIs so providing support might be rather easy.

It's already possible to import some opentelemetry-js libs via skypack, however using require (for instance, for require('lodash.merge') in opentelemetry/tracing and opentelemetry/metrics, opentelemetry/metrics 2 makes it impossible.

Overall seems to be not so many requires:

> grep -r 'require(' packages/**/src/*
packages/opentelemetry-exporter-collector-grpc/src/CollectorExporterNodeBase.ts:      const { onInit } = require('./util');
packages/opentelemetry-exporter-collector-grpc/src/CollectorExporterNodeBase.ts:        const { send } = require('./util');
packages/opentelemetry-exporter-collector-proto/src/CollectorExporterNodeBase.ts:      const { onInit } = require('./util');
packages/opentelemetry-exporter-collector-proto/src/CollectorExporterNodeBase.ts:        const { send } = require('./util');
packages/opentelemetry-exporter-jaeger/src/types.ts:export const UDPSender = require('jaeger-client/dist/src/reporters/udp_sender')
packages/opentelemetry-exporter-jaeger/src/types.ts:export const Utils = require('jaeger-client/dist/src/util').default;
packages/opentelemetry-exporter-jaeger/src/types.ts:export const ThriftUtils = require('jaeger-client/dist/src/thrift').default;
packages/opentelemetry-exporter-jaeger/src/types.ts:export const HTTPSender = require('jaeger-client/dist/src/reporters/http_sender')
packages/opentelemetry-instrumentation/src/platform/node/instrumentation.ts:    const version = require(path.join(baseDir, 'package.json')).version;
packages/opentelemetry-metrics/src/Meter.ts:const merge = require('lodash.merge');
packages/opentelemetry-metrics/src/MeterProvider.ts:const merge = require('lodash.merge');
packages/opentelemetry-semantic-conventions/build/src/index.js:__exportStar(require("./trace"), exports);
packages/opentelemetry-semantic-conventions/build/src/index.js:__exportStar(require("./resource"), exports);
packages/opentelemetry-semantic-conventions/build/src/resource/index.js:__exportStar(require("./ResourceAttributes"), exports);
packages/opentelemetry-semantic-conventions/build/src/trace/index.js:__exportStar(require("./SemanticAttributes"), exports);

However, it demands more effort due to testing in Deno runtime and validating not using any Node-specific APIs.

Do you have any plans or thoughts at the moment?

Thanks!

alexander-bzikadze avatar Jun 20 '21 06:06 alexander-bzikadze

Another thing to be aware of for deno support is the use of XHR. Since deno does not support XHR, when trying to use opentelemetry/metrics via jspm, an error is thrown when creating the request

error: Uncaught (in promise) ReferenceError: XMLHttpRequest is not defined
  const xhr = new XMLHttpRequest();

treethought avatar Jul 06 '21 21:07 treethought

This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 14 days.

github-actions[bot] avatar Jan 10 '22 06:01 github-actions[bot]

Assigning myself as I'm working on non-main-frame-context support on browsers.

legendecas avatar Jan 10 '22 07:01 legendecas

Fwiw I tried taking a stab at adapting one of the browser console exporter examples for Deno Deploy to see how far I could get, and I was actually able to make it through to the end with a minimum of monkey patching.

Here's the Deno Deploy code I used. All of the hacks are just below the imports (the last one was also to get around this Deno Deploy issue)

import { serve } from "https://deno.land/[email protected]/http/server.ts";

import { context, trace } from 'https://cdn.skypack.dev/@opentelemetry/api';
import { ConsoleSpanExporter, SimpleSpanProcessor } from 'https://cdn.skypack.dev/@opentelemetry/sdk-trace-base';
import { OTLPTraceExporter } from 'https://cdn.skypack.dev/@opentelemetry/exporter-trace-otlp-http';
import { WebTracerProvider } from 'https://cdn.skypack.dev/@opentelemetry/sdk-trace-web';
import { FetchInstrumentation } from 'https://cdn.skypack.dev/@opentelemetry/instrumentation-fetch';
import { ZoneContextManager } from 'https://cdn.skypack.dev/@opentelemetry/context-zone';
import { B3Propagator } from 'https://cdn.skypack.dev/@opentelemetry/propagator-b3';
import { registerInstrumentations } from 'https://cdn.skypack.dev/@opentelemetry/instrumentation';

//Hacks - start
globalThis.document = {
  createElement: function() { //For this line - https://github.com/open-telemetry/opentelemetry-js/blob/bdb61f7e56b7fbe7d281262e69e5bc8683a52014/packages/opentelemetry-sdk-trace-web/src/utils.ts#L33
    return {
      protocol: ":", //For this line - https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-xml-http-request/src/xhr.ts#L170
    };
  }
};
globalThis.location = {}; //For this line - https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/opentelemetry-sdk-trace-web/src/utils.ts#L424

performance.clearResourceTimings = function() {}; //For this line and Deno Deploy bug - https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-fetch/src/fetch.ts#L181
//Hacks - end

const provider = new WebTracerProvider();

// Note: For production consider using the "BatchSpanProcessor" to reduce the number of requests
// to your exporter. Using the SimpleSpanProcessor here as it sends the spans immediately to the
// exporter without delay
provider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter()));
provider.addSpanProcessor(new SimpleSpanProcessor(new OTLPTraceExporter()));
provider.register({
  contextManager: new ZoneContextManager(),
  propagator: new B3Propagator(),
});

registerInstrumentations({
  instrumentations: [
    new FetchInstrumentation({
      ignoreUrls: [/localhost:8090\/sockjs-node/],
      propagateTraceHeaderCorsUrls: [
        'https://cors-test.appspot.com/test',
        'https://httpbin.org/get',
      ],
      clearTimingResources: true,
    }),
  ],
});

const webTracerWithZone = provider.getTracer('example-tracer-web');

async function handler(req: Request): Response {

  const singleSpan = webTracerWithZone.startSpan('files-series-info');
    context.with(trace.setSpan(context.active(), singleSpan), () => {
      fetch("https://httpbin.org/get").then((_data) => {
        trace.getSpan(context.active()).addEvent('fetching-single-span-completed');
        singleSpan.end();
      });
    });

  // await fetch("https://httpbin.org/get");

  return new Response("Hello world");
}

console.log("Listening on http://localhost:8000");
await serve(handler);

And here was the console output

{
  traceId: "f720f78470dbc13cd78b90187533bbec",
  parentId: "9244d1764e81faf1",
  name: "HTTP GET",
  id: "5649f9695859d613",
  kind: 2,
  timestamp: NaN,
  duration: NaN,
  attributes: {
    component: "fetch",
    "http.method": "GET",
    "http.url": "https://httpbin.org/get",
    "http.status_code": 200,
    "http.status_text": "OK",
    "http.scheme": ""
  },
  status: { code: 0 },
  events: []
}

And I haven't tried this (yet), but I wonder if a polyfill for XmlHttpRequest for Deno (like this one from deno.land) could be enough to support a normal exporter before standard support arrives.

Grunet avatar Feb 06 '22 04:02 Grunet

I think the main thing that will be missing for correct deno support is a correct context manager, the zone start to fail as soon as you use async/await (in your example you use promises so it works fine). For this someone will need to take a shot at https://github.com/denoland/deno/issues/7010

vmarchaud avatar Feb 06 '22 10:02 vmarchaud

I think the main thing that will be missing for correct deno support is a correct context manager, the zone start to fail as soon as you use async/await (in your example you use promises so it works fine). For this someone will need to take a shot at denoland/deno#7010

(Been trying to read around and unpack your insight after confirming it didn't work with async/await...trying to summarize my understanding below)

Am I correct in understanding that browsers today (and maybe for the foreseeable future) don't offer a way to maintain context across native async/await, and so there's no way to update zone.js (or any userland approximation of zones/other constructs that can keep track of timings/contexts across async operations/ticks) to take them into account, so no usage of otel-js (or any similar tooling) will be able to work with them?

So the workaround for browsers and Deno for now is to avoid native async/await (either by not using it or transpilation). Is that accurate? (or is there some nuance for browsers I'm maybe missing?)

(Fwiw it was this github issue for zone.js that helped crystallize things for me a bit more)

Grunet avatar Feb 06 '22 18:02 Grunet

Am I correct in understanding that browsers today (and maybe for the foreseeable future) don't offer a way to maintain context across native async/await, and so there's no way to update zone.js (or any userland approximation of zones/other constructs that can keep track of timings/contexts across async operations/ticks) to take them into account, so no usage of otel-js (or any similar tooling) will be able to work with them?

Exactly, this is a known issue and i believe the general agreement is to have a system added into EMCAScript but no one had the time/resource to push it through. The last one was actually @legendecas (https://github.com/legendecas/proposal-async-context)

So the workaround for browsers and Deno for now is to avoid native async/await (either by not using it or transpilation). Is that accurate? (or is there some nuance for browsers I'm maybe missing?)

Currently yes, but from the few discussions i have seen in Deno there is interested from the core team for diagostics tooling and context management is the most important thing in event loop runtimes (there is more info in a PR that someone from MS pulled up there: https://github.com/denoland/deno/pull/8209)

vmarchaud avatar Feb 06 '22 18:02 vmarchaud

It doesn't address the main context management blocker, but in case it's helpful I made a small polyfill for navigator.sendBeacon in Deno, as I noticed that was missing and it was either going to be polyfilling that or XmlHttpRequest in order to get the browser HTTP/JSON exporter to work there.

With that I was able to get a full flow through Deno working, but in a very simple case avoiding the native async/await and zone issues discussed above.

Grunet avatar Apr 17 '22 18:04 Grunet

Worth noting that a second attempt at promise hooks might be landing soon: https://github.com/denoland/deno/pull/15475

RichiCoder1 avatar Aug 16 '22 04:08 RichiCoder1

promise hooks did land in deno 1.26

nicolasgere avatar Oct 06 '22 22:10 nicolasgere