From 631537306991414307243fb15137a65a70af328a Mon Sep 17 00:00:00 2001 From: KyleTryon Date: Mon, 12 Jan 2026 12:15:00 -0500 Subject: [PATCH 1/3] fix: sentry integration hono --- docs/architecture/sentry-integration.md | 53 +++++++++++++++++++++++++ packages/api/src/config/sentry.ts | 4 ++ packages/api/src/entries/cloudflare.ts | 12 +++++- packages/api/src/entries/node.ts | 8 +++- packages/api/src/hono/app.ts | 38 ++++++++++++++++++ packages/api/src/trpc/init.ts | 42 +++++++++----------- 6 files changed, 132 insertions(+), 25 deletions(-) diff --git a/docs/architecture/sentry-integration.md b/docs/architecture/sentry-integration.md index 743de83..1a0b8a1 100644 --- a/docs/architecture/sentry-integration.md +++ b/docs/architecture/sentry-integration.md @@ -125,6 +125,15 @@ await Sentry.startSpan( // Metrics Sentry.metrics.count("api.request", 1, { attributes: { endpoint: "/users" } }); Sentry.metrics.distribution("response_time", 150, { unit: "millisecond" }); + +// Structured logging +Sentry.logger.info("User subscribed to feed", { feedId: "123", userId: "456" }); +Sentry.logger.warn("Rate limit approaching", { current: 95, limit: 100 }); +Sentry.logger.error("Failed to fetch feed", { url: feed.url, error: err.message }); + +// Parameterized logs (recommended - makes values searchable) +const username = "john_doe"; +Sentry.logger.info(Sentry.logger.fmt`User '${username}' logged in`); ``` ### `startSpan` Behavior @@ -298,6 +307,47 @@ Ensure `vitest.config.ts` has the Sentry alias configured before other `@/utils` In Node.js/tests, metrics are no-ops. They only work in Cloudflare Workers with a valid `SENTRY_DSN`. +## Structured Logging + +Sentry structured logs are enabled via `enableLogs: true` in the Sentry config. + +### Log Levels + +Six log levels available: `trace`, `debug`, `info`, `warn`, `error`, `fatal` + +```typescript +Sentry.logger.trace("Entering function", { step: "init" }); +Sentry.logger.debug("Cache miss", { key: "user:123" }); +Sentry.logger.info("User action completed", { action: "subscribe" }); +Sentry.logger.warn("Rate limit approaching", { current: 95, limit: 100 }); +Sentry.logger.error("Operation failed", { error: err.message }); +Sentry.logger.fatal("Critical system failure", { component: "database" }); +``` + +### Parameterized Logs + +Use `Sentry.logger.fmt` for searchable parameter values: + +```typescript +const user = "john_doe"; +const action = "subscribed"; +Sentry.logger.info(Sentry.logger.fmt`User '${user}' ${action} to feed`); + +// Automatically creates searchable attributes: +// - message.template: "User %s %s to feed" +// - message.parameter.0: "john_doe" +// - message.parameter.1: "subscribed" +``` + +### Viewing Logs + +1. Navigate to **Explore → Logs** in Sentry UI +2. Filter by service, environment, level, or attributes +3. Search by message text or parameter values +4. Correlate logs with errors and traces + +**See:** [Sentry Logging Guide](../sentry-logging-guide.md) for complete documentation + ## Best Practices 1. **Always use the wrapper**: Import from `@/utils/sentry`, never directly from SDK @@ -305,3 +355,6 @@ In Node.js/tests, metrics are no-ops. They only work in Cloudflare Workers with 3. **Use spans for async operations**: Wrap database queries, API calls, etc. 4. **Add breadcrumbs for debugging**: They help trace issues in production 5. **Tag errors appropriately**: Use `tags` for filtering, `extra` for context +6. **Use appropriate log levels**: Don't log everything as `error` +7. **Add context with attributes**: More searchable than string interpolation +8. **Use parameterized logs**: Better for analysis and alerting diff --git a/packages/api/src/config/sentry.ts b/packages/api/src/config/sentry.ts index 7cc9720..6207c17 100644 --- a/packages/api/src/config/sentry.ts +++ b/packages/api/src/config/sentry.ts @@ -73,6 +73,10 @@ export function getSentryConfig(env: Env): Record | null { // Enable logs for better debugging enableLogs: true, + // Send default PII (request headers, IP) for better context + // Safe to enable because we filter sensitive fields via beforeSend callbacks + sendDefaultPii: true, + // Debug mode (verbose logging - useful for development) debug: environment === "development", diff --git a/packages/api/src/entries/cloudflare.ts b/packages/api/src/entries/cloudflare.ts index 5646ef0..23457d2 100644 --- a/packages/api/src/entries/cloudflare.ts +++ b/packages/api/src/entries/cloudflare.ts @@ -123,7 +123,14 @@ export default Sentry.withSentry((env: Env) => { const existingIntegrations = Array.isArray(config.integrations) ? (config.integrations as unknown[]) : []; - config.integrations = [...existingIntegrations, Sentry.vercelAIIntegration()]; + config.integrations = [ + ...existingIntegrations, + Sentry.vercelAIIntegration(), + // Automatically capture console.log, console.warn, and console.error as logs + Sentry.consoleLoggingIntegration({ levels: ["log", "warn", "error"] }), + // Hono error capturing integration (enabled by default, but explicit for clarity) + Sentry.honoIntegration(), + ]; // Log Sentry initialization in development const environment = (env.SENTRY_ENVIRONMENT || @@ -135,6 +142,9 @@ export default Sentry.withSentry((env: Env) => { release: config.release, hasDsn: !!config.dsn, aiTracking: true, + consoleLogging: true, + httpTracing: true, + trpcTracing: true, }); } diff --git a/packages/api/src/entries/node.ts b/packages/api/src/entries/node.ts index ab672bf..0953d87 100644 --- a/packages/api/src/entries/node.ts +++ b/packages/api/src/entries/node.ts @@ -68,9 +68,15 @@ if (env.SENTRY_DSN) { recordInputs: true, // Safe: only used for pro/enterprise users with opt-in recordOutputs: true, // Captures structured category suggestions }), + // Automatically capture console.log, console.warn, and console.error as logs + Sentry.consoleLoggingIntegration({ levels: ["log", "warn", "error"] }), + // Hono error capturing integration + Sentry.honoIntegration(), ], }); - console.log("✅ Sentry initialized (with metrics and AI tracking enabled)"); + console.log( + "✅ Sentry initialized (metrics, AI tracking, console logging, HTTP tracing, tRPC tracing enabled)" + ); } } diff --git a/packages/api/src/hono/app.ts b/packages/api/src/hono/app.ts index 0228ccd..c74d60d 100644 --- a/packages/api/src/hono/app.ts +++ b/packages/api/src/hono/app.ts @@ -38,6 +38,44 @@ export function createHonoApp(config: HonoAppConfig) { await next(); }); + // Sentry HTTP tracing middleware + // Creates spans for all HTTP requests with proper transaction names + app.use("*", async (c, next) => { + const Sentry = c.get("sentry"); + const env = c.get("env"); + + // Only create spans if Sentry is configured + if (!Sentry || !env.SENTRY_DSN) { + return await next(); + } + + // Create transaction name from method and path + const method = c.req.method; + const path = c.req.path; + + // Use Sentry.startSpan to create a trace for this HTTP request + return await Sentry.startSpan( + { + name: `${method} ${path}`, + op: "http.server", + attributes: { + "http.method": method, + "http.route": path, + "http.url": c.req.url, + }, + }, + async () => { + await next(); + + // Add response status to span + const currentSpan = Sentry.getActiveSpan?.(); + if (currentSpan) { + currentSpan.setAttribute("http.status_code", c.res.status); + } + } + ); + }); + // CORS middleware (must be before routes) const corsOrigins = getCorsOrigins(config.env); console.log("🔧 CORS Configuration:", { diff --git a/packages/api/src/trpc/init.ts b/packages/api/src/trpc/init.ts index 1ac3ffa..3b36adf 100644 --- a/packages/api/src/trpc/init.ts +++ b/packages/api/src/trpc/init.ts @@ -56,37 +56,33 @@ const t = initTRPC.context().create({ export const router = t.router; /** - * Sentry tRPC middleware (optional) + * Sentry tRPC middleware * Creates spans and improves error capturing for tRPC handlers * See: https://docs.sentry.io/platforms/javascript/guides/cloudflare/configuration/integrations/trpc * - * The middleware is created at module load time, but will only create spans - * if Sentry is initialized (checked internally by Sentry). + * Uses build-time aliased @/utils/sentry which resolves to: + * - @sentry/cloudflare in Workers + * - @sentry/node in Node.js + * - noop in tests */ -let sentryMiddleware: ReturnType | null = null; -try { - // Try to import Sentry and create middleware - // This will work in Cloudflare Workers where @sentry/cloudflare is available - // In Node.js, this will fail gracefully and we'll continue without it - const SentryModule = await import("@sentry/cloudflare"); - if (SentryModule.trpcMiddleware) { - sentryMiddleware = t.middleware( - SentryModule.trpcMiddleware({ - attachRpcInput: true, // Include RPC input in error context for debugging +const baseProcedure = (() => { + // Check if Sentry has trpcMiddleware (available in Cloudflare and Node.js) + if (typeof Sentry.trpcMiddleware === "function") { + const sentryMiddleware = t.middleware( + Sentry.trpcMiddleware({ + attachRpcInput: true, // Include RPC input in spans and error context }) ); + return t.procedure.use(sentryMiddleware); } -} catch { - // Sentry not available (e.g., in Node.js environment or not installed) - // Continue without Sentry middleware - it's optional - sentryMiddleware = null; -} -// Base procedure with Sentry middleware if available -// The middleware will only create spans if Sentry is initialized at runtime -export const publicProcedure = sentryMiddleware - ? t.procedure.use(sentryMiddleware) - : t.procedure; + // Fallback to base procedure if trpcMiddleware not available (e.g., in tests) + return t.procedure; +})(); + +// Export as publicProcedure for compatibility +// All procedures will now inherit Sentry tracing +export const publicProcedure = baseProcedure; /** * Helper function to get cached user record From a6efc61f82922f8d1b7476da733f288a5e77f299 Mon Sep 17 00:00:00 2001 From: KyleTryon Date: Mon, 12 Jan 2026 12:25:39 -0500 Subject: [PATCH 2/3] fix: middlware sentry integration --- packages/api/src/trpc/init.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/api/src/trpc/init.ts b/packages/api/src/trpc/init.ts index 3b36adf..f19a0a9 100644 --- a/packages/api/src/trpc/init.ts +++ b/packages/api/src/trpc/init.ts @@ -247,11 +247,13 @@ const isAuthedWithoutVerification = t.middleware(async ({ ctx, next }) => { }); // Protected procedure - requires authentication -export const protectedProcedure = t.procedure.use(isAuthed); +// Uses baseProcedure to inherit Sentry tracing middleware +export const protectedProcedure = baseProcedure.use(isAuthed); // Protected procedure without email verification check // Use this for endpoints that unverified users need (e.g., checkVerificationStatus, resendVerificationEmail) -export const protectedProcedureWithoutVerification = t.procedure.use( +// Uses baseProcedure to inherit Sentry tracing middleware +export const protectedProcedureWithoutVerification = baseProcedure.use( isAuthedWithoutVerification ); @@ -342,7 +344,8 @@ const isAdmin = t.middleware(async ({ ctx, next }) => { }); // Admin procedure - requires authentication and admin role -export const adminProcedure = t.procedure.use(isAdmin); +// Uses baseProcedure to inherit Sentry tracing middleware +export const adminProcedure = baseProcedure.use(isAdmin); /** * Rate limiting middleware From f39d4a88bb02972ff590df317ca01b17a3bcc914 Mon Sep 17 00:00:00 2001 From: KyleTryon Date: Mon, 12 Jan 2026 12:31:02 -0500 Subject: [PATCH 3/3] fix(api): use span parameter directly in Sentry HTTP tracing Use the span parameter provided by Sentry.startSpan() callback instead of calling getActiveSpan(). This is the recommended pattern per Sentry documentation and provides several benefits: - Simpler code (no optional chaining or null checks needed) - More explicit (direct reference to the span being created) - Safer (avoids potential issues with nested spans in complex scenarios) - Better compatibility (span parameter is guaranteed to be provided) References: - https://docs.sentry.io/platforms/javascript/guides/cloudflare/performance/instrumentation/custom-instrumentation/ - https://docs.sentry.io/platforms/javascript/guides/node/performance/instrumentation/custom-instrumentation/ Co-Authored-By: Claude Sonnet 4.5 --- packages/api/src/hono/app.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/api/src/hono/app.ts b/packages/api/src/hono/app.ts index c74d60d..5961f46 100644 --- a/packages/api/src/hono/app.ts +++ b/packages/api/src/hono/app.ts @@ -64,14 +64,11 @@ export function createHonoApp(config: HonoAppConfig) { "http.url": c.req.url, }, }, - async () => { + async (span) => { await next(); - // Add response status to span - const currentSpan = Sentry.getActiveSpan?.(); - if (currentSpan) { - currentSpan.setAttribute("http.status_code", c.res.status); - } + // Add response status to span using the provided span parameter + span.setAttribute("http.status_code", c.res.status); } ); });