Skip to content

Instantly share code, notes, and snippets.

@fmhall
Created April 14, 2025 21:06
Show Gist options
  • Save fmhall/5c639adda56d29dd9eb8cf8e01984ad3 to your computer and use it in GitHub Desktop.
Save fmhall/5c639adda56d29dd9eb8cf8e01984ad3 to your computer and use it in GitHub Desktop.
import {
ConsoleSpanExporter,
SimpleSpanProcessor,
WebTracerProvider,
} from '@opentelemetry/sdk-trace-web';
import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch';
import { ZoneContextManager } from '@opentelemetry/context-zone';
import { registerInstrumentations } from '@opentelemetry/instrumentation';
import { context, trace } from '@opentelemetry/api';
// Initialize state for tracing
let tracingEnabled = true;
let provider: WebTracerProvider;
let fetchInstrumentation: FetchInstrumentation;
let tracer: any;
// Initialize the tracer provider
function initTracing() {
provider = new WebTracerProvider({
spanProcessors: [new SimpleSpanProcessor(new ConsoleSpanExporter())]
});
provider.register({
contextManager: new ZoneContextManager(),
});
fetchInstrumentation = new FetchInstrumentation({
// This will track all fetch requests in the application
ignoreUrls: [/localhost/],
propagateTraceHeaderCorsUrls: [/.*/],
clearTimingResources: true,
});
registerInstrumentations({
instrumentations: [fetchInstrumentation],
});
// Create a tracer
tracer = provider.getTracer('example-tracer');
console.log('OpenTelemetry tracing initialized and enabled');
}
// Disable tracing
function disableTracing() {
if (fetchInstrumentation) {
fetchInstrumentation.disable();
}
tracingEnabled = false;
console.log('OpenTelemetry tracing disabled');
}
// Enable tracing
function enableTracing() {
if (!provider) {
initTracing();
} else if (fetchInstrumentation) {
fetchInstrumentation.enable();
}
tracingEnabled = true;
console.log('OpenTelemetry tracing enabled');
}
// Toggle tracing state
export function toggleTracing() {
if (tracingEnabled) {
disableTracing();
return false;
} else {
enableTracing();
return true;
}
}
// === Test functions for different header types ===
// Case 1: Regular object headers
function makeFetchRequestWithObjectHeaders() {
return fetch('https://httpbin.org/get', {
headers: {
'X-Custom-Object-Header': 'test-object-header',
'privy-app-id': 'object-privy-id',
'X-B3-TraceId': 'test-trace-id',
'X-B3-SpanId': 'test-span-id'
}
});
}
// Case 2: Headers instance
function makeFetchRequestWithHeadersInstance() {
const headers = new Headers();
headers.append('X-Custom-Headers-Instance', 'test-headers-instance');
headers.append('privy-app-id', 'headers-privy-id');
headers.append('X-B3-TraceId', 'test-trace-id');
headers.append('X-B3-SpanId', 'test-span-id');
return fetch('https://httpbin.org/get', { headers });
}
// Case 3: Map instance
function makeFetchRequestWithMapHeaders() {
// Convert Map to a regular object for Headers compatibility
const mapHeaders = new Map();
mapHeaders.set('X-Custom-Map-Header', 'test-map-header');
mapHeaders.set('privy-app-id', 'map-privy-id');
mapHeaders.set('X-B3-TraceId', 'test-trace-id');
mapHeaders.set('X-B3-SpanId', 'test-span-id');
// Convert Map to Headers
const headers = new Headers();
mapHeaders.forEach((value, key) => {
headers.append(key.toString(), value.toString());
});
return fetch('https://httpbin.org/get', { headers });
}
// Case 4: Custom header object with getters
function makeFetchRequestWithGetterHeaders() {
const headers = {
get 'X-Custom-Getter-Header'() { return 'test-getter-header'; },
get 'privy-app-id'() { return 'getter-privy-id'; },
get 'X-B3-TraceId'() { return 'test-trace-id'; },
get 'X-B3-SpanId'() { return 'test-span-id'; }
};
return fetch('https://httpbin.org/get', { headers });
}
// Case 5: Workaround with spread operator
function makeFetchRequestWithSpreadHeaders() {
const options = { method: 'GET' };
const headers = {
'X-Custom-Spread-Header': 'test-spread-header',
'privy-app-id': 'spread-privy-id',
'X-B3-TraceId': 'test-trace-id',
'X-B3-SpanId': 'test-span-id'
};
return fetch('https://httpbin.org/get', {
...options,
headers
});
}
// Standard fetch function (used for the basic case)
function makeFetchRequest() {
return fetch('https://httpbin.org/get', {
headers: {
'privy-app-id': 'basic-privy-id',
'X-B3-TraceId': "test",
'X-B3-SpanId': "test"
},
});
}
// Header test cases
type HeaderTestType = 'Object Headers' | 'Headers Instance' | 'Map Headers' | 'Getter Headers' | 'Spread Headers' | 'Basic Headers';
const headerTestCases: Record<HeaderTestType, () => Promise<Response>> = {
"Object Headers": makeFetchRequestWithObjectHeaders,
"Headers Instance": makeFetchRequestWithHeadersInstance,
"Map Headers": makeFetchRequestWithMapHeaders,
"Getter Headers": makeFetchRequestWithGetterHeaders,
"Spread Headers": makeFetchRequestWithSpreadHeaders,
"Basic Headers": makeFetchRequest
};
// Export function to make a fetch request
export function makeTracedFetchRequest(headerType: HeaderTestType = 'Basic Headers') {
const fetchFunction = headerTestCases[headerType] || makeFetchRequest;
if (!tracingEnabled) {
console.log(`Making untraced fetch request with ${headerType} (tracing is disabled)`);
return fetchFunction();
}
// Create a span for our test
const span = tracer.startSpan(`test-fetch-${headerType}`);
// Use the context to ensure the span is properly associated with the fetch
return context.with(trace.setSpan(context.active(), span), async () => {
try {
// Perform the fetch operation
console.log(`Making traced fetch request with ${headerType}`);
const response = await fetchFunction();
console.log('Fetch status:', response.status);
return response;
} catch (error) {
console.error('Fetch error:', error);
throw error;
} finally {
// End the span
span.end();
}
});
}
// Initialize the application
function initApp() {
// Initialize tracing on start
initTracing();
// Add event listener to the toggle button
const toggleButton = document.getElementById('toggleTracing');
if (toggleButton) {
toggleButton.addEventListener('click', () => {
const isEnabled = toggleTracing();
toggleButton.textContent = isEnabled ? 'Tracing: Enabled' : 'Tracing: Disabled';
toggleButton.classList.toggle('active', isEnabled);
});
// Set initial state
toggleButton.classList.toggle('active', tracingEnabled);
}
// Add event listener to the request button
const requestButton = document.getElementById('makeRequest');
if (requestButton) {
requestButton.addEventListener('click', async () => {
// Get the selected header type
const headerTypeSelect = document.getElementById('headerType') as HTMLSelectElement;
const headerType = headerTypeSelect ?
(headerTypeSelect.value as HeaderTestType) : 'Basic Headers';
console.log(`Making fetch request with ${headerType}...`);
try {
const response = await makeTracedFetchRequest(headerType);
const data = await response.json();
// Display status and data
const statusElement = document.getElementById('status');
if (statusElement) {
statusElement.innerHTML = `
<h3>Response with ${headerType}:</h3>
<pre>${JSON.stringify(data, null, 2)}</pre>
<p>Tracing: ${tracingEnabled ? 'Enabled' : 'Disabled'}</p>
`;
}
} catch (error) {
console.error('Error making request:', error);
}
});
}
}
// Initialize when DOM is loaded
window.addEventListener('DOMContentLoaded', initApp);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment