Skip to content

Commit

Permalink
feat(honeycomb-opentelemetry-web): Add support for differing subdomai…
Browse files Browse the repository at this point in the history
…ns for debug link exporter. (#406)

## Which problem is this PR solving?
This will enable this to work with EU deployments. 

## Short description of the changes
Infers the ui and auth URL roots from the supplied `endpoint`.

## How to verify that this has the expected result
Pointing an example app to `https://api.eu1.honeycomb.io/v1/traces` as
suggested in the sample apps should link to EU traces.

---------

Co-authored-by: Mustafa Haddara <[email protected]>
  • Loading branch information
wolfgangcodes and MustafaHaddara authored Dec 10, 2024
1 parent 8bab45c commit d70cef7
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from './util';
import {
FAILED_AUTH_FOR_LOCAL_VISUALIZATIONS,
MISSING_FIELDS_FOR_GENERATING_LINKS,
MISSING_FIELDS_FOR_LOCAL_VISUALIZATIONS,
} from './validate-options';
import { DiagLogLevel } from '@opentelemetry/api';
Expand All @@ -23,13 +24,45 @@ export function configureConsoleTraceLinkExporter(
options?: HoneycombOptions,
): SpanExporter {
const apiKey = getTracesApiKey(options);
const { authRoot, uiRoot } = getUrlRoots(options?.endpoint);
return new ConsoleTraceLinkExporter(
options?.serviceName,
apiKey,
options?.logLevel,
authRoot,
uiRoot,
);
}

export const getUrlRoots = (endpoint = '') => {
const url = new URL(endpoint);
const subdomainRegex = /(api)([.|-])?(.*?)(\.?)(honeycomb\.io)(.*)/;
const matches = subdomainRegex.exec(url.host);
if (matches === null) {
return {
authRoot: undefined,
uiRoot: undefined,
};
}
const isDashSubdomain = matches[2] === '-';
let apiSubdomain;
let uiSubdomain;
if (isDashSubdomain) {
apiSubdomain = `api-${matches[3]}`;
uiSubdomain = `ui-${matches[3]}`;
} else {
apiSubdomain = matches[3] ? `api.${matches[3]}` : 'api';
uiSubdomain = matches[3] ? `ui.${matches[3]}` : 'ui';
}

const authRoot = `${url.protocol}//${apiSubdomain}.honeycomb.io/1/auth`;
const uiRoot = `${url.protocol}//${uiSubdomain}.honeycomb.io`;

return {
authRoot,
uiRoot,
};
};
/**
* A custom {@link SpanExporter} that logs Honeycomb URLs for completed traces.
*
Expand All @@ -39,7 +72,13 @@ class ConsoleTraceLinkExporter implements SpanExporter {
private _traceUrl = '';
private _logLevel: DiagLogLevel = DiagLogLevel.DEBUG;

constructor(serviceName?: string, apikey?: string, logLevel?: DiagLogLevel) {
constructor(
serviceName?: string,
apikey?: string,
logLevel?: DiagLogLevel,
authRoot?: string,
uiRoot?: string,
) {
if (logLevel) {
this._logLevel = logLevel;
}
Expand All @@ -51,12 +90,19 @@ class ConsoleTraceLinkExporter implements SpanExporter {
return;
}

if (!authRoot || !uiRoot) {
if (this._logLevel >= DiagLogLevel.DEBUG) {
console.debug(MISSING_FIELDS_FOR_GENERATING_LINKS);
}
return;
}

const options = {
headers: {
'x-honeycomb-team': apikey,
},
};
fetch('https://api.honeycomb.io/1/auth', options)
fetch(authRoot, options)
.then((resp) => {
if (resp.ok) {
return resp.json();
Expand All @@ -71,6 +117,7 @@ class ConsoleTraceLinkExporter implements SpanExporter {
serviceName,
respData.team?.slug,
respData.environment?.slug,
uiRoot,
);
} else {
throw new Error();
Expand Down Expand Up @@ -121,8 +168,9 @@ export function buildTraceUrl(
serviceName: string,
team: string,
environment?: string,
uiRoot?: string,
): string {
let url = `https://ui.honeycomb.io/${team}`;
let url = `${uiRoot}/${team}`;
if (!isClassic(apikey) && environment) {
url += `/environments/${environment}`;
}
Expand Down
3 changes: 3 additions & 0 deletions packages/honeycomb-opentelemetry-web/src/validate-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ export const MISSING_FIELDS_FOR_LOCAL_VISUALIZATIONS =
createHoneycombSDKLogMessage(
'🔕 Disabling local visualizations - must have both service name and API key configured.',
);
export const MISSING_FIELDS_FOR_GENERATING_LINKS = createHoneycombSDKLogMessage(
'🔕 Disabling local visualizations - cannot infer auth and ui url roots from endpoint url.',
);
export const FAILED_AUTH_FOR_LOCAL_VISUALIZATIONS =
createHoneycombSDKLogMessage(
'🔕 Failed to get proper auth response from Honeycomb. No local visualization available.',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ReadableSpan } from '@opentelemetry/sdk-trace-base';
import {
buildTraceUrl,
configureConsoleTraceLinkExporter,
getUrlRoots,
} from '../src/console-trace-link-exporter';

const apikey = '000000000000000000000000';
Expand All @@ -14,6 +15,7 @@ describe('buildTraceUrl', () => {
'my-service',
'my-team',
'my-environment',
'https://ui.honeycomb.io',
);
expect(url).toBe(
'https://ui.honeycomb.io/my-team/environments/my-environment/datasets/my-service/trace?trace_id',
Expand All @@ -26,6 +28,7 @@ describe('buildTraceUrl', () => {
'my-service',
'my-team',
'my-environment',
'https://ui.honeycomb.io',
);
expect(url).toBe(
'https://ui.honeycomb.io/my-team/datasets/my-service/trace?trace_id',
Expand Down Expand Up @@ -64,6 +67,7 @@ describe('ConsoleTraceLinkExporter', () => {
const exporter = configureConsoleTraceLinkExporter({
apiKey: apikey,
serviceName: 'test-service',
endpoint: 'https://api.honeycomb.io/v1/traces',
});

expect(global.fetch).toHaveBeenCalledTimes(1);
Expand Down Expand Up @@ -105,6 +109,7 @@ describe('ConsoleTraceLinkExporter', () => {
const exporter = configureConsoleTraceLinkExporter({
apiKey: apikey,
serviceName: 'test-service',
endpoint: 'https://api.honeycomb.io/v1/traces',
});

expect(global.fetch).toHaveBeenCalledTimes(1);
Expand Down Expand Up @@ -144,6 +149,7 @@ describe('ConsoleTraceLinkExporter', () => {
const exporter = configureConsoleTraceLinkExporter({
apiKey: apikey,
serviceName: 'test-service',
endpoint: 'https://api.honeycomb.io/v1/traces',
});

expect(global.fetch).toHaveBeenCalledTimes(1);
Expand Down Expand Up @@ -177,3 +183,33 @@ describe('ConsoleTraceLinkExporter', () => {
);
});
});

describe('getUrlRoots', () => {
it('it should generate correct auth and ui url roots for domains (api.)', () => {
const endpoint = 'https://api.honeycomb.io/v1/traces';
const { authRoot, uiRoot } = getUrlRoots(endpoint);
expect(authRoot).toEqual('https://api.honeycomb.io/1/auth');
expect(uiRoot).toEqual('https://ui.honeycomb.io');
});
it('it should generate correct auth and ui url roots for dash-separated domains domains (api-other_env.)', () => {
const endpoint = 'https://api-other_env.honeycomb.io/v1/traces';
const { authRoot, uiRoot } = getUrlRoots(endpoint);
expect(authRoot).toEqual('https://api-other_env.honeycomb.io/1/auth');
expect(uiRoot).toEqual('https://ui-other_env.honeycomb.io');
});
it('it should generate correct auth and ui url roots for dot-separated domains (api.other_region)', () => {
const endpoint = 'https://api.other_region.honeycomb.io/v1/traces';
const { authRoot, uiRoot } = getUrlRoots(endpoint);
expect(authRoot).toEqual('https://api.other_region.honeycomb.io/1/auth');
expect(uiRoot).toEqual('https://ui.other_region.honeycomb.io');
});
it('it should generate correct auth and ui url roots for dot-dash-separated domains (api.other_env-other_region.)', () => {
const endpoint =
'https://api.other_env-other_region.honeycomb.io/v1/traces';
const { authRoot, uiRoot } = getUrlRoots(endpoint);
expect(authRoot).toEqual(
'https://api.other_env-other_region.honeycomb.io/1/auth',
);
expect(uiRoot).toEqual('https://ui.other_env-other_region.honeycomb.io');
});
});

0 comments on commit d70cef7

Please sign in to comment.