Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
606 changes: 523 additions & 83 deletions package-lock.json

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "worker-apollo-server",
"main": "./build/index.js",
"scripts": {
"preinstall": "npx npm-force-resolutions",
"drizzle-kit": "drizzle-kit",
"db:generate": "drizzle-kit generate && prettier ./drizzle/migrations --write",
"db:check": "drizzle-kit check",
Expand Down Expand Up @@ -69,20 +70,27 @@
"engines": {
"node": ">=18"
},
"resolutions": {
"@opentelemetry/api": "1.6.0"
},
"dependencies": {
"@cloudflare/workers-honeycomb-logger": "^2.3.3",
"@envelop/core": "^5.0.0",
"@envelop/immediate-introspection": "^2.0.0",
"@envelop/opentelemetry": "^6.3.1",
"@envelop/rate-limiter": "^5.0.0",
"@faker-js/faker": "^8.0.2",
"@graphql-authz/envelop-plugin": "^1.0.4",
"@graphql-yoga/plugin-csrf-prevention": "^2.0.3",
"@libsql/client": "^0.3.5",
"@microlabs/otel-cf-workers": "^1.0.0-rc.40",
"@neondatabase/serverless": "^0.4.26",
"@opentelemetry/api": "^1.9.0",
"@pothos/core": "^3.30.0",
"@pothos/plugin-authz": "^3.5.8",
"@pothos/plugin-dataloader": "^3.19.0",
"@pothos/plugin-tracing": "^0.5.8",
"@pothos/tracing-opentelemetry": "^1.1.0",
"@sanity/client": "^6.7.0",
"@tsndr/cloudflare-worker-jwt": "^2.5.3",
"@types/react": "^18.2.22",
Expand All @@ -99,6 +107,7 @@
"graphql-yoga": "^5.6.1",
"hono": "^3.9.0",
"mercadopago": "^2.0.9",
"npm-force-resolutions": "^0.0.10",
"p-map": "^6.0.0",
"pino": "^9.2.0",
"pino-pretty": "^11.2.1",
Expand Down
21 changes: 18 additions & 3 deletions src/builder.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
import { AttributeValue } from "@opentelemetry/api";
import SchemaBuilder from "@pothos/core";
import AuthzPlugin from "@pothos/plugin-authz";
import DataloaderPlugin from "@pothos/plugin-dataloader";
import TracingPlugin, { wrapResolver } from "@pothos/plugin-tracing";
import TracingPlugin, {
isRootField,
wrapResolver,
} from "@pothos/plugin-tracing";
import { createOpenTelemetryWrapper } from "@pothos/tracing-opentelemetry";
import { DateResolver, DateTimeResolver } from "graphql-scalars";

import * as rules from "~/authz";
import { defaultLogger } from "~/logging";
import { tracer } from "~/tracing";
import { Context } from "~/types";

type TracingOptions = boolean | { attributes?: Record<string, AttributeValue> };

const createSpan = createOpenTelemetryWrapper<TracingOptions>(tracer, {
includeSource: true,
includeArgs: true,
});

export const builder = new SchemaBuilder<{
Context: Context;
AuthZRule: keyof typeof rules;
Expand All @@ -24,12 +37,14 @@ export const builder = new SchemaBuilder<{
}>({
plugins: [TracingPlugin, AuthzPlugin, DataloaderPlugin],
tracing: {
default: () => true,
default: (config) => isRootField(config),
wrap: (resolver, options, config) =>
wrapResolver(resolver, (error, duration) => {
defaultLogger.debug(
defaultLogger.info(
`[TRACING] ${config.parentType}.${config.name} in ${duration}ms`,
);

return createSpan(resolver, options);
}),
},
});
Expand Down
37 changes: 35 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { useMaskedErrors } from "@envelop/core";
import { useImmediateIntrospection } from "@envelop/immediate-introspection";
import { useOpenTelemetry } from "@envelop/opentelemetry";
import { authZEnvelopPlugin } from "@graphql-authz/envelop-plugin";
import { instrument, ResolveConfigFn } from "@microlabs/otel-cf-workers";
import { trace } from "@opentelemetry/api";
import { createYoga, maskError } from "graphql-yoga";

import { Env } from "worker-configuration";
Expand All @@ -10,6 +13,7 @@ import { createGraphqlContext } from "~/context";
import { APP_ENV } from "~/env";
import { createLogger } from "~/logging";
import { schema } from "~/schema";
import { tracingPlugin } from "~/tracing";

export const yoga = createYoga<Env>({
landingPage: APP_ENV !== "production",
Expand Down Expand Up @@ -65,15 +69,32 @@ export const yoga = createYoga<Env>({
}),
useImmediateIntrospection(),
authZEnvelopPlugin({ rules }),
// useOpenTelemetry({
// resolvers: true, // Tracks resolvers calls, and tracks resolvers thrown errors
// variables: true, // Includes the operation variables values as part of the metadata collected
// result: true, // Includes execution result object as part of the metadata collected
// }),
tracingPlugin,
].filter(Boolean),
context: createGraphqlContext,
});

export default {
const handler = {
fetch: async (req: Request, env: Env, ctx: ExecutionContext) => {
const span = trace.getActiveSpan();

const internalUUID = crypto.randomUUID();

span?.setAttribute(
"extrenal-trace-id",
req.headers.get("x-trace-id") ?? "unknown",
);
span?.setAttribute("trace-id", internalUUID);

// const span = trace.getActiveSpan()
const logger = createLogger("graphql", {
externalTraceId: req.headers.get("x-trace-id"),
traceId: crypto.randomUUID(),
traceId: internalUUID,
});

logTraceId(req, logger);
Expand All @@ -90,3 +111,15 @@ export default {
return response;
},
};

const config: ResolveConfigFn = (env: Env) => {
return {
exporter: {
url: "https://otel.baselime.io/v1",
headers: { "x-api-key": env.BASELIME_API_KEY },
},
service: { name: "api" },
};
};

export default instrument(handler, config);
35 changes: 35 additions & 0 deletions src/tracing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { trace as OpenTelemetryTrace } from "@opentelemetry/api";
import { AttributeNames, SpanNames } from "@pothos/tracing-opentelemetry";
import { print } from "graphql";
import { Plugin } from "graphql-yoga";

export const trace = OpenTelemetryTrace;
export const tracer = OpenTelemetryTrace.getTracer("graphql-api");

export const tracingPlugin: Plugin = {
onExecute: ({ setExecuteFn, executeFn }) => {
setExecuteFn((options) =>
tracer.startActiveSpan(
SpanNames.EXECUTE,
{
attributes: {
[AttributeNames.OPERATION_NAME]: options.operationName ?? undefined,
[AttributeNames.SOURCE]: print(options.document),
},
},
async (span) => {
try {
const result = (await executeFn(options)) as unknown;

return result;
} catch (error) {
span.recordException(error as Error);
throw error;
} finally {
span.end();
}
},
),
);
},
};
1 change: 1 addition & 0 deletions worker-configuration.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface Env {
HIGHLIGHT_PROJECT_ID: string;
HYPERDRIVE: Hyperdrive;
PURCHASE_CALLBACK_URL: string;
BASELIME_API_KEY: string;
RPC_SERVICE_EMAIL: Service<WorkerEntrypoint>;
}

Expand Down
4 changes: 1 addition & 3 deletions workers/transactional_email_service/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@ import { Resend } from "resend";

import { sendTransactionalHTMLEmail } from "~/datasources/email/sendTransactionalHTMLEmail";
import { createLogger } from "~/logging";
import { ENV } from "~workers/transactional_email_service/types";

import { PurchaseOrderSuccessful } from "../../emails/templates/tickets/purchase-order-successful";

type ENV = {
RESEND_API_KEY: string | undefined;
};
export default class EmailService extends WorkerEntrypoint<ENV> {
logger = createLogger("EmailService");

Expand Down
7 changes: 1 addition & 6 deletions workers/transactional_email_service/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
export type ENV = {
NEON_URL: string;
HIGHLIGHT_PROJECT_ID: string;
MP_ACCESS_TOKEN: string;
MP_PUBLIC_KEY: string;
ST_KEY: string;
RV_KEY: string;
RESEND_API_KEY: string | undefined;
};