From c074a5d935721c695b876a4d2546e8f96ee34059 Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Sun, 25 Jan 2026 13:42:28 +0100 Subject: [PATCH] logging --- app/[locale]/error.tsx | 10 +- app/actions/contact.ts | 16 +- app/health/route.ts | 4 + lib/mail/mailer.ts | 7 +- lib/services/app-services.ts | 4 +- lib/services/cache/redis-cache-service.ts | 8 +- lib/services/create-services.server.ts | 5 +- lib/services/create-services.ts | 9 +- lib/services/logging/logger-service.ts | 11 + lib/services/logging/noop-logger-service.ts | 13 + lib/services/logging/pino-logger-service.ts | 58 +++++ package-lock.json | 248 +++++++++++++++++++- package.json | 2 + 13 files changed, 383 insertions(+), 12 deletions(-) create mode 100644 lib/services/logging/logger-service.ts create mode 100644 lib/services/logging/noop-logger-service.ts create mode 100644 lib/services/logging/pino-logger-service.ts diff --git a/app/[locale]/error.tsx b/app/[locale]/error.tsx index 9fc662fa..d7fbb0a4 100644 --- a/app/[locale]/error.tsx +++ b/app/[locale]/error.tsx @@ -1,8 +1,8 @@ 'use client'; -import * as Sentry from '@sentry/nextjs'; import { useEffect } from 'react'; import { useTranslations } from 'next-intl'; +import { getAppServices } from '@/lib/services/create-services'; import { Container, Button, Heading } from '@/components/ui'; import Scribble from '@/components/Scribble'; @@ -16,7 +16,13 @@ export default function Error({ const t = useTranslations('Error'); useEffect(() => { - Sentry.captureException(error); + const services = getAppServices(); + services.errors.captureException(error); + services.logger.error('Application error caught by boundary', { + message: error.message, + stack: error.stack, + digest: error.digest + }); }, [error]); return ( diff --git a/app/actions/contact.ts b/app/actions/contact.ts index aef36444..3c3250d0 100644 --- a/app/actions/contact.ts +++ b/app/actions/contact.ts @@ -3,19 +3,24 @@ import { sendEmail } from "@/lib/mail/mailer"; import ContactEmail from "@/components/emails/ContactEmail"; import React from "react"; -import * as Sentry from "@sentry/nextjs"; +import { getServerAppServices } from "@/lib/services/create-services.server"; export async function sendContactFormAction(formData: FormData) { + const services = getServerAppServices(); + const logger = services.logger.child({ action: 'sendContactFormAction' }); const name = formData.get("name") as string; const email = formData.get("email") as string; const message = formData.get("message") as string; const productName = formData.get("productName") as string | null; if (!name || !email || !message) { + logger.warn('Missing required fields in contact form', { name: !!name, email: !!email, message: !!message }); return { success: false, error: "Missing required fields" }; } - const subject = productName + logger.info('Sending contact form email', { email, productName }); + + const subject = productName ? `Product Inquiry: ${productName}` : "New Contact Form Submission"; @@ -30,5 +35,12 @@ export async function sendContactFormAction(formData: FormData) { }), }); + if (result.success) { + logger.info('Contact form email sent successfully', { messageId: result.messageId }); + } else { + logger.error('Failed to send contact form email', { error: result.error }); + services.errors.captureException(result.error, { action: 'sendContactFormAction', email }); + } + return result; } diff --git a/app/health/route.ts b/app/health/route.ts index af7d186f..4fe13213 100644 --- a/app/health/route.ts +++ b/app/health/route.ts @@ -1,5 +1,9 @@ +import { getServerAppServices } from '@/lib/services/create-services.server'; + export const dynamic = 'force-dynamic'; export async function GET() { + const services = getServerAppServices(); + services.logger.debug('Health check requested'); return new Response('OK', { status: 200 }); } diff --git a/lib/mail/mailer.ts b/lib/mail/mailer.ts index aecd5173..7bf85e75 100644 --- a/lib/mail/mailer.ts +++ b/lib/mail/mailer.ts @@ -1,6 +1,7 @@ import nodemailer from "nodemailer"; import { render } from "@react-email/components"; import { ReactElement } from "react"; +import { getServerAppServices } from "@/lib/services/create-services.server"; const transporter = nodemailer.createTransport({ host: process.env.MAIL_HOST, @@ -30,12 +31,14 @@ export async function sendEmail({ to, subject, template }: SendEmailOptions) { html, }; + const logger = getServerAppServices().logger.child({ component: 'mailer' }); + try { const info = await transporter.sendMail(mailOptions); - console.log("Email sent: %s", info.messageId); + logger.info("Email sent successfully", { messageId: info.messageId, subject, recipients }); return { success: true, messageId: info.messageId }; } catch (error) { - console.error("Error sending email:", error); + logger.error("Error sending email", { error, subject, recipients }); return { success: false, error }; } } diff --git a/lib/services/app-services.ts b/lib/services/app-services.ts index 0c2e894d..157105e0 100644 --- a/lib/services/app-services.ts +++ b/lib/services/app-services.ts @@ -1,12 +1,14 @@ import type { AnalyticsService } from './analytics/analytics-service'; import type { CacheService } from './cache/cache-service'; import type { ErrorReportingService } from './errors/error-reporting-service'; +import type { LoggerService } from './logging/logger-service'; // Simple constructor-based DI container. export class AppServices { constructor( public readonly analytics: AnalyticsService, public readonly errors: ErrorReportingService, - public readonly cache: CacheService + public readonly cache: CacheService, + public readonly logger: LoggerService ) {} } diff --git a/lib/services/cache/redis-cache-service.ts b/lib/services/cache/redis-cache-service.ts index 6db047c0..03f9ea5c 100644 --- a/lib/services/cache/redis-cache-service.ts +++ b/lib/services/cache/redis-cache-service.ts @@ -1,5 +1,6 @@ import { createClient, type RedisClientType } from 'redis'; import type { CacheService, CacheSetOptions } from './cache-service'; +import { getServerAppServices } from '../create-services.server'; export type RedisCacheServiceOptions = { url: string; @@ -17,7 +18,12 @@ export class RedisCacheService implements CacheService { this.keyPrefix = options.keyPrefix ?? ''; // Fire-and-forget connect. - this.client.connect().catch(() => undefined); + this.client.connect().catch((err) => { + // We can't use getServerAppServices() here because it might cause a circular dependency + // during initialization. But we can log to console as a fallback or use a global logger if we had one. + // For now, let's just use console.error as this is a low-level service. + console.error('Redis connection error:', err); + }); } private k(key: string) { diff --git a/lib/services/create-services.server.ts b/lib/services/create-services.server.ts index 92c6733e..cc323d0c 100644 --- a/lib/services/create-services.server.ts +++ b/lib/services/create-services.server.ts @@ -7,6 +7,7 @@ import { MemoryCacheService } from './cache/memory-cache-service'; import { RedisCacheService } from './cache/redis-cache-service'; import { GlitchtipErrorReportingService } from './errors/glitchtip-error-reporting-service'; import { NoopErrorReportingService } from './errors/noop-error-reporting-service'; +import { PinoLoggerService } from './logging/pino-logger-service'; let singleton: AppServices | undefined; @@ -32,7 +33,9 @@ export function getServerAppServices(): AppServices { }) : new MemoryCacheService(); - singleton = new AppServices(analytics, errors, cache); + const logger = new PinoLoggerService('server'); + + singleton = new AppServices(analytics, errors, cache, logger); return singleton; } diff --git a/lib/services/create-services.ts b/lib/services/create-services.ts index a46ccba0..85399138 100644 --- a/lib/services/create-services.ts +++ b/lib/services/create-services.ts @@ -4,6 +4,8 @@ import { UmamiAnalyticsService } from './analytics/umami-analytics-service'; import { MemoryCacheService } from './cache/memory-cache-service'; import { GlitchtipErrorReportingService } from './errors/glitchtip-error-reporting-service'; import { NoopErrorReportingService } from './errors/noop-error-reporting-service'; +import { NoopLoggerService } from './logging/noop-logger-service'; +import { PinoLoggerService } from './logging/pino-logger-service'; /** * Singleton instance of AppServices. @@ -93,7 +95,12 @@ export function getAppServices(): AppServices { // Use [`getServerAppServices()`](lib/services/create-services.server.ts:1) on the server. const cache = new MemoryCacheService(); + const logger = + typeof window === 'undefined' + ? new PinoLoggerService('server') + : new NoopLoggerService(); + // Create and cache the singleton - singleton = new AppServices(analytics, errors, cache); + singleton = new AppServices(analytics, errors, cache, logger); return singleton; } diff --git a/lib/services/logging/logger-service.ts b/lib/services/logging/logger-service.ts new file mode 100644 index 00000000..266b61a1 --- /dev/null +++ b/lib/services/logging/logger-service.ts @@ -0,0 +1,11 @@ +export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal'; + +export interface LoggerService { + trace(msg: string, ...args: any[]): void; + debug(msg: string, ...args: any[]): void; + info(msg: string, ...args: any[]): void; + warn(msg: string, ...args: any[]): void; + error(msg: string, ...args: any[]): void; + fatal(msg: string, ...args: any[]): void; + child(bindings: Record): LoggerService; +} diff --git a/lib/services/logging/noop-logger-service.ts b/lib/services/logging/noop-logger-service.ts new file mode 100644 index 00000000..e96cbedc --- /dev/null +++ b/lib/services/logging/noop-logger-service.ts @@ -0,0 +1,13 @@ +import type { LoggerService } from './logger-service'; + +export class NoopLoggerService implements LoggerService { + trace() {} + debug() {} + info() {} + warn() {} + error() {} + fatal() {} + child() { + return this; + } +} diff --git a/lib/services/logging/pino-logger-service.ts b/lib/services/logging/pino-logger-service.ts new file mode 100644 index 00000000..ae96fe14 --- /dev/null +++ b/lib/services/logging/pino-logger-service.ts @@ -0,0 +1,58 @@ +import pino, { Logger as PinoLogger } from 'pino'; +import type { LoggerService } from './logger-service'; + +export class PinoLoggerService implements LoggerService { + private readonly logger: PinoLogger; + + constructor(name?: string, parent?: PinoLogger) { + if (parent) { + this.logger = parent.child({ name }); + } else { + this.logger = pino({ + name: name || 'app', + level: process.env.LOG_LEVEL || 'info', + transport: + process.env.NODE_ENV !== 'production' + ? { + target: 'pino-pretty', + options: { + colorize: true, + }, + } + : undefined, + }); + } + } + + trace(msg: string, ...args: any[]) { + this.logger.trace(msg, ...args); + } + + debug(msg: string, ...args: any[]) { + this.logger.debug(msg, ...args); + } + + info(msg: string, ...args: any[]) { + this.logger.info(msg, ...args); + } + + warn(msg: string, ...args: any[]) { + this.logger.warn(msg, ...args); + } + + error(msg: string, ...args: any[]) { + this.logger.error(msg, ...args); + } + + fatal(msg: string, ...args: any[]) { + this.logger.fatal(msg, ...args); + } + + child(bindings: Record): LoggerService { + const childPino = this.logger.child(bindings); + const service = new PinoLoggerService(); + // @ts-ignore - accessing private member for child creation + service.logger = childPino; + return service; + } +} diff --git a/package-lock.json b/package-lock.json index f8d68d47..dea5d43a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,8 @@ "next-mdx-remote": "^5.0.0", "nodemailer": "^7.0.12", "pdf-lib": "^1.17.1", + "pino": "^10.3.0", + "pino-pretty": "^13.1.3", "react": "^18.3.1", "react-dom": "^18.3.1", "react-email": "^5.2.5", @@ -3854,6 +3856,12 @@ "pako": "^1.0.10" } }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "license": "MIT" + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -8068,6 +8076,15 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/atomically": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/atomically/-/atomically-2.1.0.tgz", @@ -8731,6 +8748,12 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "license": "MIT" + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -9082,6 +9105,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/debounce": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/debounce/-/debounce-2.2.0.tgz", @@ -9449,6 +9481,15 @@ "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" } }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/engine.io": { "version": "6.6.5", "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.5.tgz", @@ -10441,6 +10482,12 @@ "node": ">=0.10.0" } }, + "node_modules/fast-copy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-4.0.2.tgz", + "integrity": "sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw==", + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -10461,6 +10508,12 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "license": "MIT" + }, "node_modules/fast-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", @@ -11232,6 +11285,12 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", + "license": "MIT" + }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -12148,6 +12207,15 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/jpeg-exif": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/jpeg-exif/-/jpeg-exif-1.1.4.tgz", @@ -14291,11 +14359,19 @@ ], "license": "MIT" }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -14731,6 +14807,79 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pino": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.0.tgz", + "integrity": "sha512-0GNPNzHXBKw6U/InGe79A3Crzyk9bcSyObF9/Gfo9DLEf5qj5RF50RSjsu0W1rZ6ZqRGdzDFCRBQvi9/rSGPtA==", + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^3.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^4.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-pretty": { + "version": "13.1.3", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.1.3.tgz", + "integrity": "sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==", + "license": "MIT", + "dependencies": { + "colorette": "^2.0.7", + "dateformat": "^4.6.3", + "fast-copy": "^4.0.0", + "fast-safe-stringify": "^2.1.1", + "help-me": "^5.0.0", + "joycon": "^3.1.1", + "minimist": "^1.2.6", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^3.0.0", + "pump": "^3.0.0", + "secure-json-parse": "^4.0.0", + "sonic-boom": "^4.0.1", + "strip-json-comments": "^5.0.2" + }, + "bin": { + "pino-pretty": "bin.js" + } + }, + "node_modules/pino-pretty/node_modules/strip-json-comments": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz", + "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", + "license": "MIT" + }, "node_modules/pkg-types": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", @@ -14871,6 +15020,22 @@ "node": ">=6" } }, + "node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -14926,6 +15091,16 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -14965,6 +15140,12 @@ ], "license": "MIT" }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -15697,6 +15878,15 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, "node_modules/recma-build-jsx": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", @@ -16175,6 +16365,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -16299,6 +16498,22 @@ "node": ">=4" } }, + "node_modules/secure-json-parse": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz", + "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/selderee": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/selderee/-/selderee-0.11.0.tgz", @@ -16649,6 +16864,15 @@ "node": ">=10.0.0" } }, + "node_modules/sonic-boom": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", + "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, "node_modules/source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", @@ -16698,6 +16922,15 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -17269,6 +17502,18 @@ "dev": true, "license": "MIT" }, + "node_modules/thread-stream": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.0.0.tgz", + "integrity": "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==", + "license": "MIT", + "dependencies": { + "real-require": "^0.2.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/tiny-inflate": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", @@ -18618,7 +18863,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, "license": "ISC" }, "node_modules/ws": { diff --git a/package.json b/package.json index ad3fbb03..61d6965b 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,8 @@ "next-mdx-remote": "^5.0.0", "nodemailer": "^7.0.12", "pdf-lib": "^1.17.1", + "pino": "^10.3.0", + "pino-pretty": "^13.1.3", "react": "^18.3.1", "react-dom": "^18.3.1", "react-email": "^5.2.5",