Server-side: @matters/lib/errors/logger — singleton logger with .info, .warn,
.error, .debug, and .child(context) for adding default context.
import { logger } from "@/lib/errors/logger";
logger.info("Webinar published", { webinarId, slug });
logger.error("Salesforce push failed", err, { leadEmail });
Behaviour:
info / warn / error logged (debug suppressed).level: "error" AND not a 4xx AppException subclass get sent to Sentry with
scoped tags + context.Never use console.log in app code. The logger handles client-vs-server detection and respects
the Sentry filter (4xx client errors don't bloat the Sentry dashboard).
Custom exception classes from @matters/lib/errors/exceptions:
| Class | Status | Use case |
|---|---|---|
ValidationError | 400 | Zod / business validation failure. |
UnauthorizedError | 401 | No / invalid session. |
ForbiddenError | 403 | Authenticated but not permitted. |
NotFoundError | 404 | Resource missing. |
RateLimitError | 429 | Quota exceeded. |
WordPressAPIError | 502 | WordPress integration failure. |
DatabaseError | 500 | Mongo / persistence failure. |
ExternalServiceError | 502 | Third-party API failure (Salesforce, MailerLite, …). |
ConfigurationError | 500 | Missing / invalid config (boot-time). |
Logger's Sentry filter intentionally drops 4xx client errors — those are expected, not bugs.
lib/config/sentry.ts → shouldEnableSentry(). Enabled only when both
NODE_ENV=production and the domain is matters.ai (i.e. not preview deployments,
not dev).sentry.server.config.ts and sentry.edge.config.ts at the repo root initialise the SDK.SENTRY_AUTH_TOKEN is set.errorSource: "logger", errorType: <ExceptionClass>, and arbitrary
errorCategory from context.scope.setContext("logContext", {...})) for structured debugging.Recommendation: create a second Sentry project for matters-workspace so workspace errors
don't drown the public-site signal. Track in
PROD_CHECKLIST.md § 3.6.
MongoDB audit_logs collection, written by auditMutation (@/lib/security/api-guards) and
the lower-level logAuditEvent (lib/security/audit.ts).
Schema documented in Security → Audit Log.
Critical event types (auto-alert to console; future: email/Slack) — the full CRITICAL_EVENTS
list in lib/security/audit.ts (12 total):
multiple_failed_loginssuspicious_activitypermission_escalation_attemptuser_role_changedsuper_admin_actionrate_limit_exceededhoneypot_triggeredlow_recaptcha_scorerecaptcha_verification_failedaccess_deniedresource_not_foundsystem_errorRead with getAuditLogs({ userId?, eventType?, startDate?, endDate?, limit?, skip? }).
No application-layer Prometheus / Grafana stack today. If you need per-route latency or throughput, check Vercel's Logs + Edge metrics tabs.
No distributed tracing today. Next.js exposes an experimental clientTraceMetadata option
(it would go in next.config.ts → experimental) that emits page-level metadata to Vercel,
but it is not configured today — next.config.ts does not set it. If you need
cross-service correlation (WordPress ↔ Next.js ↔ MongoDB), the right level to add it is
lib/api/wrapper.ts's request-ID logging.
| Symptom | First place to look |
|---|---|
| 500s spiking on a route | Sentry → filter by errorSource: "logger" and the route path. |
| Auth failures spiking | Audit log → eventType: access_denied. Cross-reference with Clerk dashboard. |
| WordPress content stale | /api/revalidate logs in Vercel; check WORDPRESS_WEBHOOK_SECRET matches. |
| Salesforce backfill stuck | Check /api/workspace/webinars/registrations/sync-salesforce audit entries; metadata.failed count. |
| Rate-limit blocks | Audit log → eventType: rate_limit_exceeded. Identifies user + action. |