Last active
November 24, 2024 21:06
-
-
Save rudiv/6f0cde52a2219db4d18703f4cd92ed37 to your computer and use it in GitHub Desktop.
.NET Aspire & SvelteKit OpenTelemetry (OTEL) / Traces
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// /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 ... | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// /src/app.d.ts | |
declare global { | |
namespace App { | |
interface Locals { | |
requestId: string; | |
} | |
} | |
} | |
export { }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// /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); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// /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