Skip to content

Instantly share code, notes, and snippets.

@liamwh
Last active May 3, 2024 13:37
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • 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",
"...": "..."
}
}
@GraemeF
Copy link

GraemeF commented Feb 20, 2024

Nice! Works great in dev mode, but how did you get around this issue which I think leads to the error below when running the built application?

file:///app/server/chunks/hooks.server-DIulfZoa.js:22320
        (getMachineId = require('./getMachineId-linux').getMachineId);
        ^

ReferenceError: require is not defined in ES module scope, you can use import instead
This file is being treated as an ES module because it has a '.js' file extension and '/app/package.json' contains "type": "module". To treat it as a CommonJS script, rename it to use the '.cjs' file extension.
    at file:///app/server/chunks/hooks.server-DIulfZoa.js:22320:9
    at ModuleJob.run (node:internal/modules/esm/module_job:218:25)
    at async ModuleLoader.import (node:internal/modules/esm/loader:329:24)
    at async get_hooks (file:///app/server/index.js:211:8)
    at async Server.init (file:///app/server/index.js:4151:24)
    at async file:///app/handler.js:1167:1

Node.js v20.11.1

(sorry if there's more context for this that I missed, I came straight here)

@liamwh
Copy link
Author

liamwh commented Feb 20, 2024

Unfortunately I created this gist asking for help. I moved on and never got to the bottom of this one unfortunately, although I believe your issue can be resolved with a vite or ts config setting (apologies am on mobile so don’t have exact answer now)

@GraemeF
Copy link

GraemeF commented Feb 20, 2024

Unfortunately I created this gist asking for help. I moved on and never got to the bottom of this one unfortunately, although I believe your issue can be resolved with a vite or ts config setting (apologies am on mobile so don’t have exact answer now)

Thanks for the quick response! I've spent some time (too much time..) fiddling with those settings but no luck. Will keep an eye on that issue and keep fingers crossed! Good luck 😆

@GraemeF
Copy link

GraemeF commented Feb 20, 2024

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 🤠

#!/bin/bash

packages="./node_modules/@opentelemetry/*/package.json"

for file in ${packages}; do
  sed -i '/"module": "build\/esm\/index\.js",/d' ${file}
done

@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