Skip to content

Commit 2a4e519

Browse files
committed
otel to api
1 parent 82e6810 commit 2a4e519

File tree

9 files changed

+528
-404
lines changed

9 files changed

+528
-404
lines changed

apps/api/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@
2222
"@databuddy/sdk": "^2.2.0",
2323
"@databuddy/shared": "workspace:*",
2424
"@elysiajs/cors": "^1.4.0",
25+
"@elysiajs/opentelemetry": "^1.4.6",
2526
"@logtail/edge": "^0.5.6",
27+
"@opentelemetry/exporter-trace-otlp-proto": "^0.208.0",
28+
"@opentelemetry/sdk-trace-node": "^2.2.0",
2629
"@upstash/ratelimit": "^2.0.6",
2730
"@upstash/redis": "^1.35.6",
2831
"ai": "^5.0.81",

apps/api/src/index.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import { auth } from "@databuddy/auth";
33
import { appRouter, createRPCContext } from "@databuddy/rpc";
44
import { logger } from "@databuddy/shared/logger";
55
import cors from "@elysiajs/cors";
6+
import { opentelemetry } from "@elysiajs/opentelemetry";
7+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
8+
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-node";
69
import { onError } from "@orpc/server";
710
import { RPCHandler } from "@orpc/server/fetch";
811
import { autumnHandler } from "autumn-js/elysia";
@@ -17,14 +20,27 @@ import { query } from "./routes/query";
1720
const rpcHandler = new RPCHandler(appRouter, {
1821
interceptors: [
1922
onError((error) => {
20-
logger.error(
21-
error
22-
);
23+
logger.error(error);
2324
}),
2425
],
2526
});
2627

2728
const app = new Elysia()
29+
.use(
30+
opentelemetry({
31+
spanProcessors: [
32+
new BatchSpanProcessor(
33+
new OTLPTraceExporter({
34+
url: "https://api.axiom.co/v1/traces",
35+
headers: {
36+
Authorization: `Bearer ${process.env.AXIOM_TOKEN}`,
37+
"X-Axiom-Dataset": process.env.AXIOM_DATASET ?? "api",
38+
},
39+
})
40+
),
41+
],
42+
})
43+
)
2844
.use(publicApi)
2945
.use(
3046
cors({
@@ -88,7 +104,7 @@ const app = new Elysia()
88104
parse: "none",
89105
}
90106
)
91-
.onError(({ error, code }) => {
107+
.onError(function handleError({ error, code }) {
92108
const errorMessage = error instanceof Error ? error.message : String(error);
93109
logger.error({ error, code }, errorMessage);
94110

apps/api/src/routes/assistant.ts

Lines changed: 70 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { auth, type User, websitesApi } from "@databuddy/auth";
1+
import { auth, websitesApi } from "@databuddy/auth";
22
import type { StreamingUpdate } from "@databuddy/shared/types/assistant";
3+
import { record, setAttributes } from "@elysiajs/opentelemetry";
34
import { Elysia } from "elysia";
45
import { processAssistantRequest } from "../agent/processor";
56
import { createStreamingResponse } from "../agent/utils/stream-utils";
67
import { validateWebsite } from "../lib/website-utils";
7-
import { AssistantRequestSchema, type AssistantRequestType } from "../schemas";
8+
import { AssistantRequestSchema } from "../schemas";
89

910
function createErrorResponse(message: string): StreamingUpdate[] {
1011
return [{ type: "error", content: message }];
@@ -36,59 +37,78 @@ export const assistant = new Elysia({ prefix: "/v1/assistant" })
3637
})
3738
.post(
3839
"/stream",
39-
async ({
40-
body,
41-
user,
42-
request,
43-
}: {
44-
body: AssistantRequestType;
45-
user: User;
46-
request: Request;
47-
}) => {
48-
try {
49-
const websiteValidation = await validateWebsite(body.websiteId);
50-
if (!websiteValidation.success) {
51-
return createStreamingResponse(
52-
createErrorResponse(websiteValidation.error || "Website not found")
53-
);
54-
}
40+
function assistantStream({ body, user, request }) {
41+
return record("assistantStream", async () => {
42+
setAttributes({
43+
"assistant.website_id": body.websiteId,
44+
"assistant.user_id": user?.id || "unknown",
45+
"assistant.message_count": body.messages.length,
46+
});
5547

56-
const { website } = websiteValidation;
57-
if (!website) {
58-
return createStreamingResponse(
59-
createErrorResponse("Website not found")
60-
);
61-
}
48+
try {
49+
const websiteValidation = await validateWebsite(body.websiteId);
50+
if (!websiteValidation.success) {
51+
setAttributes({ "assistant.website_validation_failed": true });
52+
return createStreamingResponse(
53+
createErrorResponse(
54+
websiteValidation.error || "Website not found"
55+
)
56+
);
57+
}
6258

63-
// Authorization: allow public websites, org members with permission, or the owner
64-
let authorized = website.isPublic;
65-
if (!authorized) {
66-
if (website.organizationId) {
67-
const { success } = await websitesApi.hasPermission({
68-
headers: request.headers,
69-
body: { permissions: { website: ["read"] } },
70-
});
71-
authorized = success;
72-
} else {
73-
authorized = website.userId === user.id;
59+
const { website } = websiteValidation;
60+
if (!website) {
61+
setAttributes({ "assistant.website_not_found": true });
62+
return createStreamingResponse(
63+
createErrorResponse("Website not found")
64+
);
7465
}
75-
}
7666

77-
if (!authorized) {
78-
return createStreamingResponse(
79-
createErrorResponse(
80-
"You do not have permission to access this website"
81-
)
82-
);
83-
}
67+
// Authorization: allow public websites, org members with permission, or the owner
68+
let authorized = website.isPublic;
69+
if (!authorized) {
70+
if (website.organizationId) {
71+
const { success } = await websitesApi.hasPermission({
72+
headers: request.headers,
73+
body: { permissions: { website: ["read"] } },
74+
});
75+
authorized = success;
76+
} else {
77+
authorized = website.userId === user?.id;
78+
}
79+
}
8480

85-
const updates = await processAssistantRequest(body, user, website);
86-
return createStreamingResponse(updates);
87-
} catch (error) {
88-
const errorMessage =
89-
error instanceof Error ? error.message : "Unknown error occurred";
90-
return createStreamingResponse(createErrorResponse(errorMessage));
91-
}
81+
if (!authorized) {
82+
setAttributes({ "assistant.unauthorized": true });
83+
return createStreamingResponse(
84+
createErrorResponse(
85+
"You do not have permission to access this website"
86+
)
87+
);
88+
}
89+
90+
setAttributes({
91+
"assistant.website_public": website.isPublic,
92+
"assistant.website_org": Boolean(website.organizationId),
93+
});
94+
95+
if (!user) {
96+
setAttributes({ "assistant.no_user": true });
97+
return createStreamingResponse(
98+
createErrorResponse("User not found")
99+
);
100+
}
101+
102+
const updates = await processAssistantRequest(body, user as never, website);
103+
setAttributes({ "assistant.success": true });
104+
return createStreamingResponse(updates);
105+
} catch (error) {
106+
setAttributes({ "assistant.error": true });
107+
const errorMessage =
108+
error instanceof Error ? error.message : "Unknown error occurred";
109+
return createStreamingResponse(createErrorResponse(errorMessage));
110+
}
111+
});
92112
},
93113
{
94114
body: AssistantRequestSchema,

0 commit comments

Comments
 (0)