From 1fb56aafd0e612b3afa693770d0f27591282c9e9 Mon Sep 17 00:00:00 2001 From: Henry <30578846+hjball@users.noreply.github.com> Date: Fri, 13 Mar 2026 22:57:47 +0000 Subject: [PATCH] feat: add logger package to improve observability (#437) * feat: add logger package to improve observability * chore: add LOG_LEVEL to docker compose and readme --- .env.example | 3 + AGENTS.md | 20 +- README.md | 1 + apps/web/package.json | 1 + apps/web/src/components/CommandPallette.tsx | 1 - apps/web/src/components/LabelForm.tsx | 6 +- apps/web/src/pages/api/stripe/webhook.ts | 9 +- .../src/pages/settings/trello/authorize.tsx | 41 +-- .../boards/components/ImportBoardsForm.tsx | 4 +- cloud/docker-compose.yml | 3 + docker-compose.yml | 3 + packages/api/package.json | 1 + packages/api/src/trpc.ts | 39 ++- packages/api/src/utils/notifications.ts | 17 +- packages/api/src/utils/rateLimit.ts | 7 +- packages/api/src/utils/webhook.ts | 11 +- packages/auth/package.json | 1 + packages/auth/src/hooks.ts | 7 +- packages/auth/src/plugins.ts | 23 +- packages/auth/src/utils.ts | 7 +- packages/db/package.json | 1 + packages/db/src/client.ts | 6 +- packages/db/src/repository/list.repo.ts | 2 - packages/email/package.json | 1 + packages/email/src/sendEmail.tsx | 14 +- packages/logger/package.json | 34 +++ packages/logger/src/index.ts | 20 ++ packages/logger/tsconfig.json | 6 + pnpm-lock.yaml | 271 +++++++++++++++--- turbo.json | 3 +- 30 files changed, 440 insertions(+), 123 deletions(-) create mode 100644 packages/logger/package.json create mode 100644 packages/logger/src/index.ts create mode 100644 packages/logger/tsconfig.json diff --git a/.env.example b/.env.example index 4b8f89e4..2b4cd8d3 100644 --- a/.env.example +++ b/.env.example @@ -51,6 +51,9 @@ TRELLO_APP_SECRET= # If not provided, rate limiting will use in-memory storage REDIS_URL= # e.g. redis://default:your_password@your_host:6379 +# Logging (optional - debug, info, warn, error; defaults to debug in dev, info in prod) +LOG_LEVEL= + # OAuth providers (optional) BETTER_AUTH_TRUSTED_ORIGINS= # Optional: Restrict OIDC/Social sign-ins to specific email domains (comma-separated) diff --git a/AGENTS.md b/AGENTS.md index 99b22278..65e04c69 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -185,9 +185,17 @@ Use `assertUserInWorkspace` helper for workspace checks. - Use TRPCError with appropriate codes (UNAUTHORIZED, NOT_FOUND, etc.) - Provide user-friendly error messages -- Log errors appropriately +- Log errors appropriately using the `@kan/logger` package - Show popup notifications for user-facing errors +### Logging + +- Import from `@kan/logger`: `import { createLogger } from "@kan/logger"` +- Create a module-scoped logger: `const logger = createLogger("module-name")` +- Log level is controlled by `LOG_LEVEL` env var (debug, info, warn, error) +- Defaults to `debug` in development, `info` in production +- Never use `console.log` — always use the logger + ## Common Patterns ### Creating a Card @@ -219,6 +227,16 @@ Use `assertUserInWorkspace` helper for workspace checks. 5. **Frontend**: Add UI components in `apps/web/src/` 6. **i18n**: Add translations for new strings +## Adding a New Environment Variable + +Update all of the following: + +1. `.env.example` — add the variable with an empty value and a comment explaining it +2. `turbo.json` — add to `globalEnv` (or `globalPassThroughEnv` for CI/platform vars) +3. `docker-compose.yml` — add to the `web` service `environment` section +4. `cloud/docker-compose.yml` — add to the `web` service `environment` section +5. `README.md` — add a row to the Environment Variables table + ## Database Changes - Always create migrations (never modify existing migrations) diff --git a/README.md b/README.md index 12c987d0..ea4527f9 100644 --- a/README.md +++ b/README.md @@ -213,6 +213,7 @@ pnpm dev | `NEXT_PUBLIC_DISABLE_SIGN_UP` | Disable sign up | For authentication | `false` | | `NEXT_PUBLIC_WHITE_LABEL_HIDE_POWERED_BY` | Hide “Powered by kan.bn” on public boards (self-host) | For white labelling | `true` | | `KAN_ADMIN_API_KEY` | Admin API key for stats and admin endpoints | For admin/monitoring | `your-secret-admin-key` | +| `LOG_LEVEL` | Log verbosity level (debug, info, warn, error) | No (defaults to debug in dev, info in prod) | `info` | See `.env.example` for a complete list of supported environment variables. diff --git a/apps/web/package.json b/apps/web/package.json index 01e8b0db..14889ab2 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -27,6 +27,7 @@ "@kan/api": "workspace:*", "@kan/auth": "workspace:*", "@kan/db": "workspace:^", + "@kan/logger": "workspace:^", "@kan/shared": "workspace:^", "@lingui/babel-preset-react": "^2.9.2", "@lingui/conf": "^5.3.2", diff --git a/apps/web/src/components/CommandPallette.tsx b/apps/web/src/components/CommandPallette.tsx index 86d7993f..aed82cd3 100644 --- a/apps/web/src/components/CommandPallette.tsx +++ b/apps/web/src/components/CommandPallette.tsx @@ -145,7 +145,6 @@ export default function CommandPallette({ value={result} className="cursor-pointer select-none px-4 py-3 data-[focus]:bg-light-200 hover:bg-light-200 focus:outline-none dark:data-[focus]:bg-dark-200 dark:hover:bg-dark-200" onClick={() => { - console.log("clicked", url); void router.push(url); onClose(); setQuery(""); diff --git a/apps/web/src/components/LabelForm.tsx b/apps/web/src/components/LabelForm.tsx index 780bb07f..46fd2b8f 100644 --- a/apps/web/src/components/LabelForm.tsx +++ b/apps/web/src/components/LabelForm.tsx @@ -75,7 +75,7 @@ export function LabelForm({ reset(newFormState); } } catch (e) { - console.log(e); + console.error(e); } }, }); @@ -231,9 +231,7 @@ export function LabelForm({ diff --git a/apps/web/src/pages/api/stripe/webhook.ts b/apps/web/src/pages/api/stripe/webhook.ts index b138fb26..842f4991 100644 --- a/apps/web/src/pages/api/stripe/webhook.ts +++ b/apps/web/src/pages/api/stripe/webhook.ts @@ -3,8 +3,11 @@ import type { Readable } from "node:stream"; import { createNextApiContext } from "@kan/api/trpc"; import * as workspaceRepo from "@kan/db/repository/workspace.repo"; +import { createLogger } from "@kan/logger"; import { createStripeClient } from "@kan/stripe"; +const log = createLogger("stripe-webhook"); + async function buffer(readable: Readable) { const chunks = []; for await (const chunk of readable) { @@ -41,6 +44,8 @@ export default async function handler( const { db } = await createNextApiContext(req); + log.info({ eventType: event.type, eventId: event.id }, "Stripe webhook received"); + switch (event.type) { case "checkout.session.completed": { const checkoutSession = event.data.object; @@ -56,12 +61,12 @@ export default async function handler( break; } default: - console.log(`Unhandled event type: ${event.type}`); + log.warn({ eventType: event.type }, "Unhandled Stripe event type"); } return res.status(200).json({ received: true }); } catch (err) { - console.error("Webhook error:", err); + log.error({ err }, "Stripe webhook handler failed"); return res.status(400).json({ message: "Webhook handler failed" }); } } diff --git a/apps/web/src/pages/settings/trello/authorize.tsx b/apps/web/src/pages/settings/trello/authorize.tsx index bada6e5c..5261eea9 100644 --- a/apps/web/src/pages/settings/trello/authorize.tsx +++ b/apps/web/src/pages/settings/trello/authorize.tsx @@ -1,24 +1,25 @@ import { useEffect } from "react"; export default function TrelloAuthorize() { - useEffect(() => { - const hash = window.location.hash; - const token = hash.split("=")[1]; - if (token) { - console.log("Posting token to /api/trello/authenticate", token); - fetch("/api/trello/authenticate", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ token }), - }).then(() => { - window.close(); - }); - } - }, []); + useEffect(() => { + const hash = window.location.hash; + const token = hash.split("=")[1]; + if (token) { + fetch("/api/trello/authenticate", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ token }), + }).then(() => { + window.close(); + }); + } + }, []); - return
-

Connecting to Trello...

-
; -} \ No newline at end of file + return ( +
+

Connecting to Trello...

+
+ ); +} diff --git a/apps/web/src/views/boards/components/ImportBoardsForm.tsx b/apps/web/src/views/boards/components/ImportBoardsForm.tsx index c0e64376..72bfcb40 100644 --- a/apps/web/src/views/boards/components/ImportBoardsForm.tsx +++ b/apps/web/src/views/boards/components/ImportBoardsForm.tsx @@ -234,7 +234,7 @@ const ImportGithub: React.FC = () => { await refetchBoards(); closeModal(); } catch (e) { - console.log(e); + console.error(e); } }, onError: () => { @@ -386,7 +386,7 @@ const ImportTrello: React.FC = () => { await refetchBoards(); closeModal(); } catch (e) { - console.log(e); + console.error(e); } }, onError: () => { diff --git a/cloud/docker-compose.yml b/cloud/docker-compose.yml index ac069743..596875f8 100644 --- a/cloud/docker-compose.yml +++ b/cloud/docker-compose.yml @@ -37,6 +37,9 @@ services: - NEXT_PUBLIC_USE_STANDALONE_OUTPUT=${NEXT_PUBLIC_USE_STANDALONE_OUTPUT} - REDIS_URL=${REDIS_URL} + # Logging (optional - debug, info, warn, error; defaults to debug in dev, info in prod) + - LOG_LEVEL=${LOG_LEVEL} + # Stripe - STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY} - STRIPE_WEBHOOK_SECRET=${STRIPE_WEBHOOK_SECRET} diff --git a/docker-compose.yml b/docker-compose.yml index eaa56b81..f731cebe 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -37,6 +37,9 @@ services: # Redis (optional - for rate limiting) - REDIS_URL=${REDIS_URL} + # Logging (optional - debug, info, warn, error; defaults to debug in dev, info in prod) + - LOG_LEVEL=${LOG_LEVEL} + # Admin API key (optional) - KAN_ADMIN_API_KEY=${KAN_ADMIN_API_KEY} diff --git a/packages/api/package.json b/packages/api/package.json index b8caf407..92ad0486 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -48,6 +48,7 @@ "@kan/auth": "workspace:*", "@kan/db": "workspace:*", "@kan/email": "workspace:^", + "@kan/logger": "workspace:^", "@kan/shared": "workspace:^", "@kan/stripe": "workspace:^", "@trpc/server": "catalog:", diff --git a/packages/api/src/trpc.ts b/packages/api/src/trpc.ts index fa8722d3..3d9994db 100644 --- a/packages/api/src/trpc.ts +++ b/packages/api/src/trpc.ts @@ -9,6 +9,9 @@ import { ZodError } from "zod"; import type { dbClient } from "@kan/db/client"; import { initAuth } from "@kan/auth/server"; import { createDrizzleClient } from "@kan/db/client"; +import { createLogger } from "@kan/logger"; + +const log = createLogger("trpc"); export interface User { id: string; @@ -90,7 +93,7 @@ export const createRESTContext = async ({ req }: CreateNextContextOptions) => { try { session = await auth.api.getSession(); } catch (error) { - console.error("Error getting session, ", error); + log.error({ err: error }, "Error getting session"); throw error; } @@ -118,7 +121,23 @@ export const createTRPCRouter = t.router; export const createCallerFactory = t.createCallerFactory; -export const publicProcedure = t.procedure.meta({ +const loggingMiddleware = t.middleware(async ({ path, type, next, ctx }) => { + const start = Date.now(); + const result = await next(); + const duration = Date.now() - start; + + const meta = { procedure: path, type, duration, userId: (ctx as { user?: { id: string } }).user?.id }; + + if (result.ok) { + log.info(meta, "tRPC OK"); + } else { + log.error({ ...meta, err: result.error }, "tRPC error"); + } + + return result; +}); + +export const publicProcedure = t.procedure.use(loggingMiddleware).meta({ openapi: { method: "GET", path: "/public" }, }); @@ -142,14 +161,18 @@ const enforceUserIsAdmin = t.middleware(async ({ ctx, next }) => { }); }); -export const protectedProcedure = t.procedure.use(enforceUserIsAuthed).meta({ - openapi: { - method: "GET", - path: "/protected", - }, -}); +export const protectedProcedure = t.procedure + .use(loggingMiddleware) + .use(enforceUserIsAuthed) + .meta({ + openapi: { + method: "GET", + path: "/protected", + }, + }); export const adminProtectedProcedure = t.procedure + .use(loggingMiddleware) .use(enforceUserIsAdmin) .meta({ openapi: { diff --git a/packages/api/src/utils/notifications.ts b/packages/api/src/utils/notifications.ts index e19ac367..7c516a08 100644 --- a/packages/api/src/utils/notifications.ts +++ b/packages/api/src/utils/notifications.ts @@ -1,6 +1,9 @@ import { env } from "next-runtime-env"; import type { dbClient } from "@kan/db/client"; +import { createLogger } from "@kan/logger"; + +const log = createLogger("notifications"); import * as cardRepo from "@kan/db/repository/card.repo"; import * as memberRepo from "@kan/db/repository/member.repo"; import * as notificationRepo from "@kan/db/repository/notification.repo"; @@ -72,6 +75,7 @@ export async function sendMentionEmails({ const baseUrl = env("NEXT_PUBLIC_BASE_URL"); const cardUrl = `${baseUrl}/cards/${cardPublicId}`; + log.info({ cardPublicId, mentionCount: membersToNotify.length, commenterUserId }, "Sending mention emails"); // Send emails to all mentioned members (only if notification doesn't exist) await Promise.all( membersToNotify.map(async (member) => { @@ -91,6 +95,7 @@ export async function sendMentionEmails({ // If notification already exists, skip sending email if (notificationExists) { + log.debug({ email, cardPublicId }, "Skipping duplicate mention email"); return; } @@ -114,20 +119,14 @@ export async function sendMentionEmails({ cardUrl, }, ); + log.info({ email, cardPublicId }, "Mention email sent"); } catch (error) { - console.error("Failed to send mention email:", { - email, - cardPublicId, - error: error instanceof Error ? error.message : String(error), - }); + log.error({ err: error, email, cardPublicId }, "Failed to send mention email"); } }), ); } catch (error) { - console.error("Error sending mention emails:", { - cardPublicId, - error: error instanceof Error ? error.message : String(error), - }); + log.error({ err: error, cardPublicId }, "Error sending mention emails"); } } diff --git a/packages/api/src/utils/rateLimit.ts b/packages/api/src/utils/rateLimit.ts index 7459f534..948618a8 100644 --- a/packages/api/src/utils/rateLimit.ts +++ b/packages/api/src/utils/rateLimit.ts @@ -5,6 +5,9 @@ import { } from "rate-limiter-flexible"; import { getRedisClient } from "@kan/db/redis"; +import { createLogger } from "@kan/logger"; + +const log = createLogger("rateLimit"); export interface RateLimitOptions { points?: number; @@ -45,7 +48,7 @@ function createRateLimiter(options: RateLimitOptions = {}) { // Use Redis if available, otherwise fall back to in-memory storage if (redis) { - console.log("Using Redis for rate limiting"); + log.debug("Using Redis for rate limiting"); return new RateLimiterRedis({ storeClient: redis, points, @@ -53,7 +56,7 @@ function createRateLimiter(options: RateLimitOptions = {}) { }); } - console.log("Using in-memory for rate limiting"); + log.debug("Redis unavailable, falling back to in-memory rate limiting"); return new RateLimiterMemory({ points, duration, diff --git a/packages/api/src/utils/webhook.ts b/packages/api/src/utils/webhook.ts index 5838e827..6b59dd02 100644 --- a/packages/api/src/utils/webhook.ts +++ b/packages/api/src/utils/webhook.ts @@ -4,6 +4,9 @@ import { z } from "zod"; import type { dbClient } from "@kan/db/client"; import type { WebhookEvent } from "@kan/db/schema"; import * as webhookRepo from "@kan/db/repository/webhook.repo"; +import { createLogger } from "@kan/logger"; + +const log = createLogger("webhook"); export type WebhookEventType = WebhookEvent; @@ -199,9 +202,9 @@ export async function sendWebhooksForWorkspace( sendWebhookToUrl(webhook.url, webhook.secret ?? undefined, payload).then( (result) => { if (!result.success) { - console.error( - `Webhook delivery failed to ${webhook.url}: ${result.error}`, - ); + log.error({ url: webhook.url, event: payload.event, error: result.error, statusCode: result.statusCode }, "Webhook delivery failed"); + } else { + log.info({ url: webhook.url, event: payload.event, statusCode: result.statusCode }, "Webhook delivered"); } }, ), @@ -210,7 +213,7 @@ export async function sendWebhooksForWorkspace( // Wait for all to complete but don't block on failures await Promise.allSettled(promises); } catch (error) { - console.error("Failed to send webhooks for workspace:", error); + log.error({ err: error, workspaceId }, "Failed to send webhooks for workspace"); } } diff --git a/packages/auth/package.json b/packages/auth/package.json index c63e1fcf..56503c69 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -39,6 +39,7 @@ "prettier": "@kan/prettier-config", "dependencies": { "@better-auth/stripe": "^1.4.6", + "@kan/logger": "workspace:^", "better-auth": "^1.4.6" } } diff --git a/packages/auth/src/hooks.ts b/packages/auth/src/hooks.ts index 246fb1f8..dcdc35aa 100644 --- a/packages/auth/src/hooks.ts +++ b/packages/auth/src/hooks.ts @@ -7,8 +7,11 @@ import type { dbClient } from "@kan/db/client"; import * as memberRepo from "@kan/db/repository/member.repo"; import * as userRepo from "@kan/db/repository/user.repo"; import { notificationClient } from "@kan/email"; +import { createLogger } from "@kan/logger"; import { createEmailUnsubscribeLink, createS3Client } from "@kan/shared"; +const log = createLogger("auth"); + import { downloadImage } from "./utils"; type BetterAuthUser = { @@ -103,6 +106,7 @@ export function createDatabaseHooks(db: dbClient) { const unsubscribeUrl = await createEmailUnsubscribeLink(user.id); + log.info({ workflowId: "user-signup", userId: user.id, email: user.email }, "Triggering Novu workflow"); await notificationClient.trigger({ to: { subscriberId: user.id, @@ -122,6 +126,7 @@ export function createDatabaseHooks(db: dbClient) { }, workflowId: "user-signup", }); + log.info({ workflowId: "user-signup", userId: user.id }, "Novu workflow triggered"); await notificationClient.subscribers.credentials.update( { @@ -134,7 +139,7 @@ export function createDatabaseHooks(db: dbClient) { user.id, ); } catch (error) { - console.error("Error adding user to notification client", error); + log.error({ err: error }, "Error adding user to notification client"); } } }, diff --git a/packages/auth/src/plugins.ts b/packages/auth/src/plugins.ts index 5f2627b0..fb475ffa 100644 --- a/packages/auth/src/plugins.ts +++ b/packages/auth/src/plugins.ts @@ -8,7 +8,10 @@ import * as subscriptionRepo from "@kan/db/repository/subscription.repo"; import * as userRepo from "@kan/db/repository/user.repo"; import * as workspaceRepo from "@kan/db/repository/workspace.repo"; import { sendEmail } from "@kan/email"; +import { createLogger } from "@kan/logger"; import { generateUID } from "@kan/shared/utils"; + +const log = createLogger("auth"); import { createStripeClient } from "@kan/stripe"; import { socialProvidersPlugin } from "./providers"; @@ -101,9 +104,7 @@ export function createPlugins(db: dbClient) { unlimitedSeats: true, }, ); - console.log( - `Pro subscription ${stripeSubscription.id} activated with unlimited seats`, - ); + log.info({ subscriptionId: stripeSubscription.id }, "Pro subscription activated with unlimited seats"); const workspace = await workspaceRepo.getByPublicId( db, @@ -175,11 +176,7 @@ export function createPlugins(db: dbClient) { sendMagicLink: async ({ email, url }) => { try { const decodedUrl = decodeURIComponent(url); - console.log("Sending magic link to:", email, "URL:", url); - console.log( - "Magic link contains invite:", - decodedUrl.includes("type=invite"), - ); + log.info({ email, isInvite: decodedUrl.includes("type=invite") }, "Sending magic link"); if (decodedUrl.includes("type=invite")) { let inviterName = ""; let workspaceName = ""; @@ -211,7 +208,7 @@ export function createPlugins(db: dbClient) { } } } catch (error) { - console.error("Failed to fetch invite details:", error); + log.error({ err: error }, "Failed to fetch invite details"); } await sendEmail( @@ -239,11 +236,7 @@ export function createPlugins(db: dbClient) { ); } } catch (error) { - console.error("Error sending magic link:", { - email, - url, - error, - }); + log.error({ err: error, email }, "Error sending magic link"); } }, }), @@ -273,7 +266,7 @@ export function createPlugins(db: dbClient) { picture?: string; avatar?: string; }) => { - console.log("OIDC profile:", profile); + log.debug({ profile }, "OIDC profile received"); const name = profile.name ?? diff --git a/packages/auth/src/utils.ts b/packages/auth/src/utils.ts index 3188b554..76b6af1f 100644 --- a/packages/auth/src/utils.ts +++ b/packages/auth/src/utils.ts @@ -4,8 +4,11 @@ import type Stripe from "stripe"; import type { dbClient } from "@kan/db/client"; import * as userRepo from "@kan/db/repository/user.repo"; import { notificationClient } from "@kan/email"; +import { createLogger } from "@kan/logger"; import { createEmailUnsubscribeLink } from "@kan/shared"; +const log = createLogger("auth"); + export async function downloadImage(url: string): Promise { const response = await fetch(url); if (!response.ok) { @@ -32,6 +35,7 @@ export async function triggerWorkflow( const unsubscribeUrl = await createEmailUnsubscribeLink(user.id); + log.info({ workflowId, userId: user.id }, "Triggering Novu workflow"); await notificationClient.trigger({ to: { subscriberId: user.id, @@ -43,7 +47,8 @@ export async function triggerWorkflow( }, workflowId, }); + log.info({ workflowId, userId: user.id }, "Novu workflow triggered"); } catch (error) { - console.error("Error triggering workflow", error); + log.error({ err: error, workflowId }, "Error triggering workflow"); } } diff --git a/packages/db/package.json b/packages/db/package.json index 1363da56..ea4bab54 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -44,6 +44,7 @@ }, "dependencies": { "@electric-sql/pglite": "^0.3.7", + "@kan/logger": "workspace:^", "@kan/shared": "workspace:^", "drizzle-orm": "^0.42.0", "drizzle-zod": "^0.5.1", diff --git a/packages/db/src/client.ts b/packages/db/src/client.ts index 32bcb6a9..c058491b 100644 --- a/packages/db/src/client.ts +++ b/packages/db/src/client.ts @@ -6,8 +6,12 @@ import { drizzle as drizzlePgLite } from "drizzle-orm/pglite"; import { migrate } from "drizzle-orm/pglite/migrator"; import { Pool } from "pg"; +import { createLogger } from "@kan/logger"; + import * as schema from "./schema"; +const log = createLogger("db"); + export type dbClient = NodePgDatabase & { $client: Pool; }; @@ -16,7 +20,7 @@ export const createDrizzleClient = (): dbClient => { const connectionString = process.env.POSTGRES_URL; if (!connectionString) { - console.log("POSTGRES_URL environment variable is not set, using PGLite"); + log.warn("POSTGRES_URL not set, falling back to PGLite"); const client = new PGlite({ dataDir: "./pgdata", diff --git a/packages/db/src/repository/list.repo.ts b/packages/db/src/repository/list.repo.ts index 1b5249c9..1f4ff3d4 100644 --- a/packages/db/src/repository/list.repo.ts +++ b/packages/db/src/repository/list.repo.ts @@ -402,8 +402,6 @@ export const softDeleteById = async ( .groupBy(lists.index) .having(gt(countExpr, 1)); - console.log(duplicateIndices); - if (duplicateIndices.length > 0) { throw new Error( `Duplicate indices found after reordering in board ${result.boardId}`, diff --git a/packages/email/package.json b/packages/email/package.json index cc15c860..ed3736d4 100644 --- a/packages/email/package.json +++ b/packages/email/package.json @@ -23,6 +23,7 @@ "typecheck": "tsc --noEmit --emitDeclarationOnly false" }, "dependencies": { + "@kan/logger": "workspace:^", "@novu/api": "^3.11.0", "@react-email/components": "^1.0.1", "nodemailer": "^7.0.3", diff --git a/packages/email/src/sendEmail.tsx b/packages/email/src/sendEmail.tsx index 1a696c62..a4005988 100644 --- a/packages/email/src/sendEmail.tsx +++ b/packages/email/src/sendEmail.tsx @@ -1,5 +1,8 @@ import { render } from "@react-email/render"; import nodemailer from "nodemailer"; +import { createLogger } from "@kan/logger"; + +const log = createLogger("email"); import JoinWorkspaceTemplate from "./templates/join-workspace"; import MagicLinkTemplate from "./templates/magic-link"; @@ -44,6 +47,7 @@ export const sendEmail = async ( template: Templates, data: Record, ) => { + log.info({ to, subject, template }, "Sending email"); try { const EmailTemplate = emailTemplates[template]; @@ -62,16 +66,10 @@ export const sendEmail = async ( throw new Error(`Failed to send email: ${response.response}`); } + log.info({ to, subject, template, messageId: response.messageId }, "Email sent"); return response; } catch (error) { - console.error("Email sending failed:", { - to, - from: process.env.EMAIL_FROM, - subject, - template, - error: error instanceof Error ? error.message : String(error), - stack: error instanceof Error ? error.stack : undefined, - }); + log.error({ err: error, to, from: process.env.EMAIL_FROM, subject, template }, "Email sending failed"); throw error; } }; diff --git a/packages/logger/package.json b/packages/logger/package.json new file mode 100644 index 00000000..2e031b88 --- /dev/null +++ b/packages/logger/package.json @@ -0,0 +1,34 @@ +{ + "name": "@kan/logger", + "private": true, + "version": "0.1.0", + "type": "module", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./src/index.ts" + } + }, + "license": "GPL-3.0", + "scripts": { + "build": "tsc", + "clean": "git clean -xdf .cache .turbo dist node_modules", + "dev": "tsc", + "format": "prettier --check . --ignore-path ../../.gitignore", + "lint": "eslint", + "typecheck": "tsc --noEmit --emitDeclarationOnly false" + }, + "dependencies": { + "pino": "^9.14.0", + "pino-pretty": "^13.1.3" + }, + "devDependencies": { + "@kan/eslint-config": "workspace:*", + "@kan/prettier-config": "workspace:*", + "@kan/tsconfig": "workspace:*", + "eslint": "catalog:", + "prettier": "catalog:", + "typescript": "catalog:" + }, + "prettier": "@kan/prettier-config" +} diff --git a/packages/logger/src/index.ts b/packages/logger/src/index.ts new file mode 100644 index 00000000..1896af48 --- /dev/null +++ b/packages/logger/src/index.ts @@ -0,0 +1,20 @@ +import pino from "pino"; + +const isDev = process.env.NODE_ENV !== "production"; +const level = process.env.LOG_LEVEL ?? (isDev ? "debug" : "info"); + +export const logger = pino({ + level, + ...(isDev && { + transport: { + target: "pino-pretty", + options: { + colorize: true, + ignore: "pid,hostname", + translateTime: "HH:MM:ss", + }, + }, + }), +}); + +export const createLogger = (module: string) => logger.child({ module }); diff --git a/packages/logger/tsconfig.json b/packages/logger/tsconfig.json new file mode 100644 index 00000000..7496acfb --- /dev/null +++ b/packages/logger/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "@kan/tsconfig/internal-package.json", + "compilerOptions": {}, + "include": ["*.ts", "src"], + "exclude": ["node_modules"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 764bf98f..1f24ff1f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -103,6 +103,9 @@ importers: '@kan/db': specifier: workspace:^ version: link:../../packages/db + '@kan/logger': + specifier: workspace:^ + version: link:../../packages/logger '@kan/shared': specifier: workspace:^ version: link:../../packages/shared @@ -311,6 +314,9 @@ importers: '@kan/email': specifier: workspace:^ version: link:../email + '@kan/logger': + specifier: workspace:^ + version: link:../logger '@kan/shared': specifier: workspace:^ version: link:../shared @@ -353,13 +359,16 @@ importers: version: 5.9.2 vitest: specifier: ^3.0.0 - version: 3.2.4(@types/debug@4.1.12)(@types/node@25.0.0)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.1) + version: 3.2.4(@types/debug@4.1.12)(@types/node@25.0.0)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.1) packages/auth: dependencies: '@better-auth/stripe': specifier: ^1.4.6 version: 1.4.6(@better-auth/core@1.4.6(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.18)(better-call@1.1.5(zod@4.1.13))(jose@6.1.3)(kysely@0.28.8)(nanostores@1.1.0))(better-auth@1.4.6(next@16.0.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(stripe@18.5.0(@types/node@25.0.0)) + '@kan/logger': + specifier: workspace:^ + version: link:../logger better-auth: specifier: ^1.4.6 version: 1.4.6(next@16.0.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -400,6 +409,9 @@ importers: '@electric-sql/pglite': specifier: ^0.3.7 version: 0.3.7 + '@kan/logger': + specifier: workspace:^ + version: link:../logger '@kan/shared': specifier: workspace:^ version: link:../shared @@ -452,6 +464,9 @@ importers: packages/email: dependencies: + '@kan/logger': + specifier: workspace:^ + version: link:../logger '@novu/api': specifier: ^3.11.0 version: 3.11.0 @@ -487,6 +502,34 @@ importers: specifier: 'catalog:' version: 5.9.2 + packages/logger: + dependencies: + pino: + specifier: ^9.14.0 + version: 9.14.0 + pino-pretty: + specifier: ^13.1.3 + version: 13.1.3 + devDependencies: + '@kan/eslint-config': + specifier: workspace:* + version: link:../../tooling/eslint + '@kan/prettier-config': + specifier: workspace:* + version: link:../../tooling/prettier + '@kan/tsconfig': + specifier: workspace:* + version: link:../../tooling/typescript + eslint: + specifier: 'catalog:' + version: 9.34.0(jiti@2.6.1) + prettier: + specifier: 'catalog:' + version: 3.6.2 + typescript: + specifier: 'catalog:' + version: 5.9.2 + packages/shared: dependencies: '@aws-sdk/client-s3': @@ -2976,6 +3019,9 @@ packages: '@octokit/types@9.3.2': resolution: {integrity: sha512-D4iHGTdAnEEVsB8fl95m1hiz7D5YiRdQ9b/OEb3BYRVwbLsGHcRVPz+u+BgRLNk0Q0/4iZCBqDN96j2XNxfXrA==} + '@pinojs/redact@0.4.0': + resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -4468,6 +4514,10 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + atomically@2.1.0: resolution: {integrity: sha512-+gDffFXRW6sl/HCwbta7zK4uNqbPjv4YJEAdz7Vu+FLQHe77eZ4bvbJGi4hE0QPeJlMYMA3piXEr1UL3dAwx7Q==} @@ -4809,6 +4859,9 @@ packages: resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} engines: {node: '>=12.5.0'} + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + colors@1.0.3: resolution: {integrity: sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==} engines: {node: '>=0.1.90'} @@ -4964,6 +5017,9 @@ packages: date-fns@4.1.0: resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + dateformat@4.6.3: + resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} + debounce-fn@6.0.0: resolution: {integrity: sha512-rBMW+F2TXryBwB54Q0d8drNEI+TfoS9JpNTAoVpukbWEhjXQq4rySFYLaqXMFXwdv61Zb2OHtj5bviSoimqxRQ==} engines: {node: '>=18'} @@ -4989,15 +5045,6 @@ packages: supports-color: optional: true - debug@4.4.1: - resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -5270,6 +5317,9 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + engine.io-parser@5.2.3: resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==} engines: {node: '>=10.0.0'} @@ -5533,6 +5583,9 @@ packages: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} + fast-copy@4.0.2: + resolution: {integrity: sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -5546,6 +5599,9 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} @@ -5818,6 +5874,9 @@ packages: header-case@1.0.1: resolution: {integrity: sha512-i0q9mkOeSuhXw6bGgiQCCBgY/jlZuV/7dZXyZ9c6LcBrqwvT8eT719E9uxE5LiZftdl+z81Ugbg/VvXV4OJOeQ==} + help-me@5.0.0: + resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} + hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} @@ -6181,6 +6240,10 @@ packages: jose@6.1.3: resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==} + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + js-sha256@0.10.1: resolution: {integrity: sha512-5obBtsz9301ULlsgggLg542s/jqtddfOpV5KJc4hajc9JV8GeY2gZHSVpYBn4nWqAUTJ9v+xwtbJ1mIBgIH5Vw==} @@ -6719,10 +6782,6 @@ packages: resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==} hasBin: true - minimatch@10.0.3: - resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==} - engines: {node: 20 || >=22} - minimatch@10.1.1: resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} engines: {node: 20 || >=22} @@ -6952,6 +7011,10 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -7143,6 +7206,23 @@ packages: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} + pino-abstract-transport@2.0.0: + resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} + + pino-abstract-transport@3.0.0: + resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} + + pino-pretty@13.1.3: + resolution: {integrity: sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==} + hasBin: true + + pino-std-serializers@7.1.0: + resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} + + pino@9.14.0: + resolution: {integrity: sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==} + hasBin: true + pirates@4.0.7: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} @@ -7314,6 +7394,9 @@ packages: resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} engines: {node: '>=6'} + process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} @@ -7394,6 +7477,9 @@ packages: engines: {node: '>=16.0.0'} hasBin: true + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + punycode.js@2.3.1: resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} engines: {node: '>=6'} @@ -7409,6 +7495,9 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + radix3@1.1.2: resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} @@ -7523,6 +7612,10 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + rechoir@0.6.2: resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} engines: {node: '>= 0.10'} @@ -7694,6 +7787,10 @@ packages: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -7711,6 +7808,9 @@ packages: resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} engines: {node: '>=4'} + secure-json-parse@4.1.0: + resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} + selderee@0.11.0: resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==} @@ -7843,6 +7943,9 @@ packages: resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + sonic-boom@4.2.1: + resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -7966,6 +8069,10 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strip-json-comments@5.0.3: + resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} + engines: {node: '>=14.16'} + strip-literal@3.1.0: resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} @@ -8098,6 +8205,9 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + thread-stream@3.1.0: + resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} + through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} @@ -10483,7 +10593,7 @@ snapshots: '@eslint/config-array@0.21.0': dependencies: '@eslint/object-schema': 2.1.6 - debug: 4.4.1 + debug: 4.4.3 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -10497,7 +10607,7 @@ snapshots: '@eslint/eslintrc@3.3.1': dependencies: ajv: 6.12.6 - debug: 4.4.1 + debug: 4.4.3 espree: 10.4.0 globals: 14.0.0 ignore: 5.3.2 @@ -11000,7 +11110,7 @@ snapshots: dependencies: '@apidevtools/swagger-parser': 10.1.1(openapi-types@12.1.3) '@mintlify/prebuild': 1.0.33(@apidevtools/swagger-parser@10.1.1(openapi-types@12.1.3))(@mintlify/models@0.0.29(openapi-types@12.1.3))(@mintlify/validation@0.1.63(@mintlify/models@0.0.29(openapi-types@12.1.3))(openapi-types@12.1.3))(fs-extra@11.3.1)(gray-matter@4.0.3)(openapi-types@12.1.3)(unist-util-visit@4.1.2) - chalk: 5.6.0 + chalk: 5.6.2 fs-extra: 11.3.1 is-absolute-url: 4.0.1 openapi-types: 12.1.3 @@ -11037,7 +11147,7 @@ snapshots: '@mintlify/validation': 0.1.63(@mintlify/models@0.0.29(openapi-types@12.1.3))(openapi-types@12.1.3) '@octokit/rest': 19.0.13 axios: 1.11.0 - chalk: 5.6.0 + chalk: 5.6.2 chokidar: 3.6.0 fs-extra: 11.3.1 gray-matter: 4.0.3 @@ -11226,6 +11336,8 @@ snapshots: dependencies: '@octokit/openapi-types': 18.1.1 + '@pinojs/redact@0.4.0': {} + '@pkgjs/parseargs@0.11.0': optional: true @@ -12684,13 +12796,13 @@ snapshots: optionalDependencies: vite: 7.3.1(@types/node@20.19.11)(jiti@1.21.7)(terser@5.44.1)(yaml@2.8.1) - '@vitest/mocker@3.2.4(vite@7.3.1(@types/node@25.0.0)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.1))': + '@vitest/mocker@3.2.4(vite@7.3.1(@types/node@25.0.0)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.1))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.1(@types/node@25.0.0)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.1) + vite: 7.3.1(@types/node@25.0.0)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.1) '@vitest/pretty-format@3.2.4': dependencies: @@ -12977,6 +13089,8 @@ snapshots: asynckit@0.4.0: {} + atomic-sleep@1.0.0: {} + atomically@2.1.0: dependencies: stubborn-fs: 2.0.0 @@ -13341,6 +13455,8 @@ snapshots: color-convert: 2.0.1 color-string: 1.9.1 + colorette@2.0.20: {} + colors@1.0.3: {} combined-stream@1.0.8: @@ -13488,6 +13604,8 @@ snapshots: date-fns@4.1.0: {} + dateformat@4.6.3: {} + debounce-fn@6.0.0: dependencies: mimic-function: 5.0.1 @@ -13502,10 +13620,6 @@ snapshots: dependencies: ms: 2.1.3 - debug@4.4.1: - dependencies: - ms: 2.1.3 - debug@4.4.3: dependencies: ms: 2.1.3 @@ -13685,6 +13799,10 @@ snapshots: emoji-regex@9.2.2: {} + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + engine.io-parser@5.2.3: {} engine.io@6.6.4: @@ -14112,7 +14230,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.1 + debug: 4.4.3 escape-string-regexp: 4.0.0 eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 @@ -14154,7 +14272,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.1 + debug: 4.4.3 escape-string-regexp: 4.0.0 eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 @@ -14243,6 +14361,8 @@ snapshots: iconv-lite: 0.4.24 tmp: 0.0.33 + fast-copy@4.0.2: {} + fast-deep-equal@3.1.3: {} fast-glob@3.3.3: @@ -14257,6 +14377,8 @@ snapshots: fast-levenshtein@2.0.6: {} + fast-safe-stringify@2.1.1: {} + fast-uri@3.1.0: {} fast-xml-parser@5.2.5: @@ -14448,7 +14570,7 @@ snapshots: dependencies: foreground-child: 3.3.1 jackspeak: 4.1.1 - minimatch: 10.0.3 + minimatch: 10.1.1 minipass: 7.1.2 package-json-from-dist: 1.0.1 path-scurry: 2.0.0 @@ -14581,6 +14703,8 @@ snapshots: no-case: 2.3.2 upper-case: 1.1.3 + help-me@5.0.0: {} + hoist-non-react-statics@3.3.2: dependencies: react-is: 16.13.1 @@ -14971,6 +15095,8 @@ snapshots: jose@6.1.3: {} + joycon@3.1.1: {} + js-sha256@0.10.1: {} js-tokens@4.0.0: {} @@ -15099,7 +15225,7 @@ snapshots: log-symbols@5.1.0: dependencies: - chalk: 5.6.0 + chalk: 5.6.2 is-unicode-supported: 1.3.0 log-symbols@6.0.0: @@ -15854,10 +15980,6 @@ snapshots: mini-svg-data-uri@1.4.4: {} - minimatch@10.0.3: - dependencies: - '@isaacs/brace-expansion': 5.0.0 - minimatch@10.1.1: dependencies: '@isaacs/brace-expansion': 5.0.0 @@ -16084,6 +16206,8 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + on-exit-leak-free@2.1.2: {} + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -16142,7 +16266,7 @@ snapshots: ora@6.3.1: dependencies: - chalk: 5.6.0 + chalk: 5.6.2 cli-cursor: 4.0.0 cli-spinners: 2.9.2 is-interactive: 2.0.0 @@ -16321,6 +16445,46 @@ snapshots: pify@2.3.0: {} + pino-abstract-transport@2.0.0: + dependencies: + split2: 4.2.0 + + pino-abstract-transport@3.0.0: + dependencies: + split2: 4.2.0 + + pino-pretty@13.1.3: + dependencies: + colorette: 2.0.20 + dateformat: 4.6.3 + fast-copy: 4.0.2 + fast-safe-stringify: 2.1.1 + help-me: 5.0.0 + joycon: 3.1.1 + minimist: 1.2.8 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pump: 3.0.4 + secure-json-parse: 4.1.0 + sonic-boom: 4.2.1 + strip-json-comments: 5.0.3 + + pino-std-serializers@7.1.0: {} + + pino@9.14.0: + dependencies: + '@pinojs/redact': 0.4.0 + atomic-sleep: 1.0.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 2.0.0 + pino-std-serializers: 7.1.0 + process-warning: 5.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.1 + thread-stream: 3.1.0 + pirates@4.0.7: {} pkg-types@2.3.0: @@ -16428,6 +16592,8 @@ snapshots: prismjs@1.30.0: {} + process-warning@5.0.0: {} + prompts@2.4.2: dependencies: kleur: 3.0.3 @@ -16563,6 +16729,11 @@ snapshots: dependencies: commander: 10.0.1 + pump@3.0.4: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + punycode.js@2.3.1: {} punycode@2.3.1: {} @@ -16573,6 +16744,8 @@ snapshots: queue-microtask@1.2.3: {} + quick-format-unescaped@4.0.4: {} + radix3@1.1.2: {} raf-schd@4.0.3: {} @@ -16726,6 +16899,8 @@ snapshots: readdirp@4.1.2: {} + real-require@0.2.0: {} + rechoir@0.6.2: dependencies: resolve: 1.22.10 @@ -16979,6 +17154,8 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.1 + safe-stable-stringify@2.5.0: {} + safer-buffer@2.1.2: {} sax@1.2.1: {} @@ -16999,6 +17176,8 @@ snapshots: extend-shallow: 2.0.1 kind-of: 6.0.3 + secure-json-parse@4.1.0: {} + selderee@0.11.0: dependencies: parseley: 0.12.1 @@ -17212,6 +17391,10 @@ snapshots: ip-address: 10.0.1 smart-buffer: 4.2.0 + sonic-boom@4.2.1: + dependencies: + atomic-sleep: 1.0.0 + source-map-js@1.2.1: {} source-map-support@0.5.21: @@ -17350,6 +17533,8 @@ snapshots: strip-json-comments@3.1.1: {} + strip-json-comments@5.0.3: {} + strip-literal@3.1.0: dependencies: js-tokens: 9.0.1 @@ -17520,6 +17705,10 @@ snapshots: dependencies: any-promise: 1.3.0 + thread-stream@3.1.0: + dependencies: + real-require: 0.2.0 + through@2.3.8: {} tiny-invariant@1.3.3: {} @@ -17955,13 +18144,13 @@ snapshots: - tsx - yaml - vite-node@3.2.4(@types/node@25.0.0)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.1): + vite-node@3.2.4(@types/node@25.0.0)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.1): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.3.1(@types/node@25.0.0)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.1) + vite: 7.3.1(@types/node@25.0.0)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.1) transitivePeerDependencies: - '@types/node' - jiti @@ -17991,7 +18180,7 @@ snapshots: terser: 5.44.1 yaml: 2.8.1 - vite@7.3.1(@types/node@25.0.0)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.1): + vite@7.3.1(@types/node@25.0.0)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.1): dependencies: esbuild: 0.27.2 fdir: 6.5.0(picomatch@4.0.3) @@ -18002,7 +18191,7 @@ snapshots: optionalDependencies: '@types/node': 25.0.0 fsevents: 2.3.3 - jiti: 2.4.2 + jiti: 2.6.1 terser: 5.44.1 yaml: 2.8.1 @@ -18048,11 +18237,11 @@ snapshots: - tsx - yaml - vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.0.0)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.1): + vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.0.0)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.1): dependencies: '@types/chai': 5.2.3 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@25.0.0)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.1)) + '@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@25.0.0)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.1)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -18070,8 +18259,8 @@ snapshots: tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.3.1(@types/node@25.0.0)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.1) - vite-node: 3.2.4(@types/node@25.0.0)(jiti@2.4.2)(terser@5.44.1)(yaml@2.8.1) + vite: 7.3.1(@types/node@25.0.0)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.1) + vite-node: 3.2.4(@types/node@25.0.0)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.1) why-is-node-running: 2.3.0 optionalDependencies: '@types/debug': 4.1.12 diff --git a/turbo.json b/turbo.json index 47274710..d3f74541 100644 --- a/turbo.json +++ b/turbo.json @@ -129,7 +129,8 @@ "BETTER_AUTH_TRUSTED_ORIGINS", "NOVU_API_KEY", "EMAIL_UNSUBSCRIBE_SECRET", - "REDIS_URL" + "REDIS_URL", + "LOG_LEVEL" ], "globalPassThroughEnv": [ "NODE_ENV",