Skip to content

Instantly share code, notes, and snippets.

@rudiv
Last active November 24, 2024 21:06
Show Gist options
  • Save rudiv/6f0cde52a2219db4d18703f4cd92ed37 to your computer and use it in GitHub Desktop.
Save rudiv/6f0cde52a2219db4d18703f4cd92ed37 to your computer and use it in GitHub Desktop.
.NET Aspire & SvelteKit OpenTelemetry (OTEL) / Traces
// /src/routes/**/+[page|layout].server.ts
import { skTracer } from "$lib/server/telemetry";
export async function load(event) {
const span = skTracer.startSpan("some-trace");
// Do something here, if you call fetch() it will be traced automatically so not needed
span.end();
return ...
}
// /src/app.d.ts
declare global {
namespace App {
interface Locals {
requestId: string;
}
}
}
export { };
// /src/hooks.server.ts
import { handleTelemetry } from "$lib/server/telemetry";
export const handle = handleTelemetry(async function handle({ event, resolve }) {
// Your custom handle hook
return await resolve(event);
});
// /src/lib/server/telemetry.ts
import { NodeSDK } from '@opentelemetry/sdk-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc';
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-grpc';
import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-grpc';
import { SimpleLogRecordProcessor } from '@opentelemetry/sdk-logs';
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
import { RedisInstrumentation } from '@opentelemetry/instrumentation-redis-4';
import { UndiciInstrumentation } from '@opentelemetry/instrumentation-undici';
import { diag, DiagConsoleLogger, DiagLogLevel, SpanKind, type Attributes } from '@opentelemetry/api';
import { credentials } from '@grpc/grpc-js';
import type { Handle } from '@sveltejs/kit';
import { trace } from '@opentelemetry/api';
import { dev } from "$app/environment";
import { env } from "$env/dynamic/private";
diag.setLogger(new DiagConsoleLogger(), dev ? DiagLogLevel.INFO : DiagLogLevel.WARN);
const otlpServer = env.OTEL_EXPORTER_OTLP_ENDPOINT;
if (otlpServer) {
console.log(`OTLP endpoint: ${otlpServer}`);
const isHttps = otlpServer.startsWith('https://');
const collectorOptions = {
credentials: !isHttps
? credentials.createInsecure()
: credentials.createSsl()
};
const sdk = new NodeSDK({
traceExporter: new OTLPTraceExporter(collectorOptions),
metricReader: new PeriodicExportingMetricReader({
exportIntervalMillis: dev ? 5000 : 10000,
exporter: new OTLPMetricExporter(collectorOptions),
}),
logRecordProcessors: [
new SimpleLogRecordProcessor(new OTLPLogExporter(collectorOptions))
],
instrumentations: [
new HttpInstrumentation(),
new RedisInstrumentation(),
new UndiciInstrumentation()
],
});
sdk.start();
}
export const skTracer = trace.getTracer('sveltekit-otel');
export function handleTelemetry(userHandle: Handle): Handle {
return async (args) => {
const { event: { isDataRequest, isSubRequest, locals, request, route, params, url } } = args;
let attrs: Attributes = {
'http.request.method': request.method,
'http.request.type.data': isDataRequest,
'http.request.type.sub': isSubRequest,
'http.route': route.id ?? undefined,
'server.address': url.host,
'server.port': url.port,
'url.path': url.pathname,
'url.scheme': url.protocol.slice(0, -1),
'user_agent.original': request.headers.get('user-agent') ?? undefined
};
if (url.search) {
attrs['url.query'] = '?' + url.searchParams.toString();
}
Object.entries(params).forEach(([key, value]) => {
attrs['http.route.param.' + key] = value;
});
return skTracer.startActiveSpan(`${request.method} ${route.id}`, {
kind: SpanKind.SERVER,
attributes: attrs
}, async (span) => {
locals.requestId = span.spanContext().traceId;
const result = await userHandle(args);
span.setAttributes({
'http.response.status_code': result.status
}).end();
return result;
});
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment