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

Using ResolveConfigFn does not reevaluate exporter URL for every request #187

Open
jmaroeder opened this issue Jan 31, 2025 · 3 comments
Open

Comments

@jmaroeder
Copy link

This tripped me up - we are trying to support sending metrics to different URLs based on request criteria. It appears that regardless of what we return from our ResolveConfigFn, though, the exporter that was constructed for the very first request is reused.

Here's a simple example:

const config: ResolveConfigFn = (__env, trigger: Trigger) => {
  const request = trigger as Request;
  return {
    exporter: {
      url: (request.url.includes('foo')) ? 'https://api.foo.com' : 'https://api.bar.com',
    },
    service: { name: 'foo-or-bar' },
  };
};

Regardless of whether or not this function returns https://api.foo.com or https://api.bar.com, when it comes time to export, this library will always use the exporter that the first call to this config function returns.

@jmaroeder
Copy link
Author

It appears that this is because exporting uses the global tracer named export:

export async function exportSpans(tracker?: PromiseTracker) {
const tracer = trace.getTracer('export')
if (tracer instanceof WorkerTracer) {
await scheduler.wait(1)
if (tracker) {
await tracker.wait()
}
const promises = tracer.spanProcessors.map(async (spanProcessor) => {
await spanProcessor.forceFlush()
})
await Promise.allSettled(promises)
} else {
console.error('The global tracer is not of type WorkerTracer and can not export spans')
}
}

@jmaroeder
Copy link
Author

jmaroeder commented Feb 6, 2025

I was able to work around this issue by making a new class that implements SpanExporter, largely copied from the existing OTLPExporter, that looks for an attribute in the spans and sets the URL based on that.

A little bit like this (not copied/pasted exactly):

export default class CustomOtlpExporter implements SpanExporter {
  // ...
  private getUrl(items: any[]): string | undefined {
    for (const item of items) {
      if (isReadableSpan(item)) {
        const maybeUrl = item.attributes['custom.exporter.url'];
        if (maybeUrl) {
          return maybeUrl as string;
        }
      }
    }
    return undefined;
  }
  // ...
  send(items: any[], onSuccess: () => void, onError: (error: OTLPExporterError) => void): void {
    // ...
    const url = this.getUrl(items);
    if (!url) {
      onError(new OTLPExporterError("Can't determine URL"));
      return;
    }
    unwrap(fetch)(url, params)
    // ...

Note that in order for this to work, you have to ensure there is a span with that attribute set. I set that early in the request handler

@evanderkoogh
Copy link
Owner

Ahh.. yeah that is certainly the case indeed. I guess you are the first person to want to export different info to different places based on dynamic data. I'll have a think about how we can solve this easier going forward :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants