diff --git a/packages/honeycomb-opentelemetry-web/src/console-trace-link-exporter.ts b/packages/honeycomb-opentelemetry-web/src/console-trace-link-exporter.ts index 160c587..c415a7b 100644 --- a/packages/honeycomb-opentelemetry-web/src/console-trace-link-exporter.ts +++ b/packages/honeycomb-opentelemetry-web/src/console-trace-link-exporter.ts @@ -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'; @@ -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. * @@ -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; } @@ -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(); @@ -71,6 +117,7 @@ class ConsoleTraceLinkExporter implements SpanExporter { serviceName, respData.team?.slug, respData.environment?.slug, + uiRoot, ); } else { throw new Error(); @@ -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}`; } diff --git a/packages/honeycomb-opentelemetry-web/src/validate-options.ts b/packages/honeycomb-opentelemetry-web/src/validate-options.ts index 110b77d..7974e7d 100644 --- a/packages/honeycomb-opentelemetry-web/src/validate-options.ts +++ b/packages/honeycomb-opentelemetry-web/src/validate-options.ts @@ -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.', diff --git a/packages/honeycomb-opentelemetry-web/test/console-trace-link-exporter.test.ts b/packages/honeycomb-opentelemetry-web/test/console-trace-link-exporter.test.ts index 4f266aa..af9844d 100644 --- a/packages/honeycomb-opentelemetry-web/test/console-trace-link-exporter.test.ts +++ b/packages/honeycomb-opentelemetry-web/test/console-trace-link-exporter.test.ts @@ -2,6 +2,7 @@ import { ReadableSpan } from '@opentelemetry/sdk-trace-base'; import { buildTraceUrl, configureConsoleTraceLinkExporter, + getUrlRoots, } from '../src/console-trace-link-exporter'; const apikey = '000000000000000000000000'; @@ -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', @@ -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', @@ -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); @@ -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); @@ -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); @@ -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'); + }); +});