Skip to content

Instantly share code, notes, and snippets.

@liamwh
Last active May 3, 2024 13:37
Show Gist options
  • Save liamwh/980b034106030f774a3563ec5fbd441e to your computer and use it in GitHub Desktop.
Save liamwh/980b034106030f774a3563ec5fbd441e to your computer and use it in GitHub Desktop.
SvelteKit Otel
<script lang="ts">
import type { UserView } from '$lib/stubs/auth';
import { getContext, onMount } from 'svelte';
import { goto } from '$app/navigation';
import { getContacts } from '$lib/repositories/contacts';
import { browser } from '$app/environment';
import { CONTACTS_STORE_KEY, USER_STORE_KEY } from '$lib/store-keys';
import type { Writable } from 'svelte/store';
import AddContactButton from '$lib/components/Contacts/AddContact/AddContactButton.svelte';
import { db } from '$lib/surrealdb';
import type { User } from '$lib/types';
import ContactCard from '$lib/components/Contacts/ContactCard.svelte';
import opentelemetry, { type Span } from '@opentelemetry/api';
import { SpanStatusCode } from '@opentelemetry/api';
import RemoveContactButton from '$lib/components/Contacts/RemoveContact/RemoveContactButton.svelte';
// import { ClientTelemetry } from '$lib/instrumentation/client-telemetry';
const contacts: Writable<User[]> = getContext(CONTACTS_STORE_KEY);
const user: Writable<UserView> = getContext(USER_STORE_KEY);
async function getContactsAndUpdateStore(userId: string) {
const tracer = opentelemetry.trace.getTracer('default');
console.log(tracer);
return tracer.startActiveSpan('getContactsAndUpdateStore', async (span) => {
try {
span.setAttribute('userId', userId);
const users = await getContacts(userId);
contacts.set(users);
span.addEvent('Contacts fetched successfully');
} catch (error) {
console.error('Error fetching contacts:', error);
if (error instanceof Error) {
// Record the exception in the span if it's an Error
span.recordException(error);
span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
} else {
// If it's not an Error instance, stringify if possible.
span.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
}
contacts.set([]); // Fallback in case of error
} finally {
console.log(span);
span.end();
console.log(span);
}
});
}
async function setupLiveContacts(userId: string) {
const tracer = opentelemetry.trace.getTracer('default');
tracer.startActiveSpan('setupLiveContacts', async (span: Span) => {
try {
await db.live('users', async ({ action, result }) => {
if (action === 'CLOSE') return;
const updatedUser = result as User;
await getContactsAndUpdateStore(userId);
console.log(`Contacts updated due to ${action}:`, updatedUser);
});
} catch (error) {
console.error('Error in live contact update:', error);
} finally {
span.end();
}
});
}
onMount(async () => {
const currentUser = $user;
if (!currentUser) {
goto('/login');
} else {
await getContactsAndUpdateStore(currentUser.id);
await setupLiveContacts(currentUser.id);
}
});
</script>
<!--html here -->
import { WebTracerProvider } from '@opentelemetry/sdk-trace-web';
import { getWebAutoInstrumentations } from '@opentelemetry/auto-instrumentations-web';
import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base';
import { registerInstrumentations } from '@opentelemetry/instrumentation';
import { ZoneContextManager } from '@opentelemetry/context-zone';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { W3CTraceContextPropagator } from "@opentelemetry/core";
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
import { Resource } from '@opentelemetry/resources';
const TRACE_URL = import.meta.env.VITE_TRACE_URL || 'http://localhost:4318/v1/traces';
const exporter = new OTLPTraceExporter({
url: TRACE_URL,
headers: {},
});
const provider = new WebTracerProvider({
resource: new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: 'web-service',
}),
});
provider.addSpanProcessor(new SimpleSpanProcessor(exporter));
provider.register({
contextManager: new ZoneContextManager(),
propagator: new W3CTraceContextPropagator(),
});
export class ClientTelemetry {
private static instance: ClientTelemetry;
private initialized = false;
private constructor() {}
public static getInstance(): ClientTelemetry {
if (!ClientTelemetry.instance) {
ClientTelemetry.instance = new ClientTelemetry();
}
return ClientTelemetry.instance;
}
public start() {
if (!this.initialized) {
registerInstrumentations({
instrumentations: [
getWebAutoInstrumentations(),
],
});
console.log("Client Telemetry Initialised")
this.initialized = true;
}
}
}
import type { Handle } from '@sveltejs/kit';
import { Telemetry } from './lib/instrumentation';
export const handle: Handle = async ({ event, resolve }) => {
Telemetry.getInstance().start();
return resolve(event);
};
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-proto';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
import {
AlwaysOnSampler,
} from '@opentelemetry/sdk-trace-base';
import { Resource } from '@opentelemetry/resources';
const TRACE_URL = import.meta.env.VITE_TRACE_URL || 'http://localhost:4318/v1/traces';
const METRICS_URL = import.meta.env.VITE_METRICS_URL || 'http://localhost:4318/v1/metrics';
const SERVICE_NAME = import.meta.env.VITE_OTEL_SERVICE_NAME || 'veloxide-frontend-localdev';
const exporter = new OTLPTraceExporter({
url: TRACE_URL,
headers: {}
});
const otelNodeSdk = new NodeSDK({
autoDetectResources: true,
serviceName: SERVICE_NAME,
traceExporter: exporter,
metricReader: new PeriodicExportingMetricReader({
exporter: new OTLPMetricExporter({
url: METRICS_URL,
headers: {}
})
}),
sampler: new AlwaysOnSampler(),
resource: new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: SERVICE_NAME
}),
instrumentations: [
getNodeAutoInstrumentations({
// load custom configuration for http instrumentation
'@opentelemetry/instrumentation-http': {
applyCustomAttributesOnSpan: (span) => {
span.setAttribute('foo2', 'bar2');
}
}
})
]
});
export class Telemetry {
private static instance: Telemetry;
private initialized = false;
private constructor() {}
public static getInstance(): Telemetry {
if (!Telemetry.instance) {
Telemetry.instance = new Telemetry();
}
return Telemetry.instance;
}
public start() {
if (!this.initialized) {
this.initialized = true;
otelNodeSdk.start();
}
}
}
{
"name": "exampleapp",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"test": "playwright test",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"test:unit": "vitest",
"lint": "prettier --plugin-search-dir . --check . && eslint .",
"format": "prettier --plugin-search-dir . --write ."
},
"devDependencies": {
"removed": "removed"
},
"type": "module",
"dependencies": {
"@opentelemetry/api": "^1.7.0",
"@opentelemetry/auto-instrumentations-node": "^0.40.1",
"@opentelemetry/auto-instrumentations-web": "^0.34.0",
"@opentelemetry/context-zone": "^1.18.1",
"@opentelemetry/core": "^1.18.1",
"@opentelemetry/exporter-metrics-otlp-proto": "^0.45.1",
"@opentelemetry/exporter-trace-otlp-http": "^0.45.1",
"@opentelemetry/exporter-trace-otlp-proto": "^0.45.1",
"@opentelemetry/instrumentation": "^0.45.1",
"@opentelemetry/instrumentation-grpc": "^0.45.1",
"@opentelemetry/instrumentation-http": "^0.45.1",
"@opentelemetry/resources": "^1.18.1",
"@opentelemetry/sdk-metrics": "^1.17.1",
"@opentelemetry/sdk-node": "^0.44.0",
"@opentelemetry/sdk-trace-base": "^1.18.1",
"@opentelemetry/sdk-trace-node": "^1.18.1",
"@opentelemetry/sdk-trace-web": "^1.18.1",
"@opentelemetry/semantic-conventions": "^1.18.1",
"...": "..."
}
}
@DownChapel
Copy link

I tried deleting the module property from all the @opentelemetry packages and that seems to have done the trick - it now uses the cjs files rather than the broken esm ones. Horrible hack but it might do the job until it's fixed properly 🤠

Thanks! I also found huggingface/chat-ui#1013 useful to figure out I should put the call to this in the build script of my package.json.

E.g.

"scripts": {
  "dev": "vite dev",
  "build": "sh clean-otel-files.sh && vite build",
}

@GraemeF
Copy link

GraemeF commented May 3, 2024

I put it in postinstall so it runs after installing packages. Haven't checked in on this in a while so don't know if it's still necessary.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment