Jump to content

How I Handle Logs and Errors in My SaaS Projects

From JOHNWICK
Revision as of 16:52, 14 December 2025 by PC (talk | contribs) (Created page with "650px When you’re running a SaaS — even a small one — logs and error handling can make or break your sanity. If something fails silently, you’re blind. If everything logs too much, you’re buried. Over time, I’ve built a simple, consistent system that gives me visibility without chaos. The Goal: Context Without Noise The key is balance.
You need enough context to understand what happened — but not so much that...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

When you’re running a SaaS — even a small one — logs and error handling can make or break your sanity. If something fails silently, you’re blind. If everything logs too much, you’re buried. Over time, I’ve built a simple, consistent system that gives me visibility without chaos.


The Goal: Context Without Noise The key is balance.
You need enough context to understand what happened — but not so much that logs become unreadable. So for every major operation, I log:

  • The user or process that triggered it
  • What it tried to do
  • Whether it succeeded or failed

Example:

console.info(`[Backup Created] user:${userId} scenario:${scenarioId}`)

If something goes wrong:

console.error(`[Backup Failed] user:${userId} scenario:${scenarioId}`, error)

Readable. Searchable. Easy to grep.


Separate Logs From Errors I treat logs and errors differently:

  • Logs = expected events
  • Errors = unexpected events

That distinction helps me spot real issues faster.
I don’t want to scroll through hundreds of “normal” logs to find one broken API call. In production, I filter or route them separately — for example, sending error level events to monitoring while keeping info logs lightweight.


Centralized Logging I use a consistent logging utility stored in src/lib/log.ts.

export function logInfo(message: string, meta?: any) {
  console.info(`[INFO] ${message}`, meta ?? "")
}
export function logError(message: string, error?: any) {
  console.error(`[ERROR] ${message}`, error ?? "")
}

This way, I can replace console later with a more advanced service (like Logtail, Sentry, or AWS CloudWatch) without touching the rest of my code. Centralizing logging logic gives flexibility — I can scale visibility later without rewriting everything.


Error Boundaries on the Frontend For client-side errors, I always wrap major areas in React error boundaries. It’s simple: if something crashes, show a fallback instead of a white screen.

<ErrorBoundary fallback={<ErrorMessage />}>
  <Dashboard />
</ErrorBoundary>

It’s better UX, and I can still send the error to Sentry or a custom handler.


Catching Errors on the Server Server-side, I use try/catch in all async functions:

try {
  await doSomethingCritical()
} catch (error) {
  logError("Critical operation failed", error)
  throw new Error("Operation failed")
}

The user gets a clean message; I get the stack trace. If it’s something that shouldn’t ever happen, I send it to an external alert — so I can fix it before a user even reports it.


Minimalism Scales I don’t need a complex setup for 10 users, but I build as if I might have 10,000. So, my system is simple, but ready to grow:

  • Centralized logging functions
  • Separate log and error levels
  • Graceful frontend fallbacks
  • Alert hooks for serious issues

No giant monitoring dashboards — just clarity and consistency.


Wrap-Up Good error handling isn’t about tools — it’s about discipline.

  • Log what matters
  • Separate normal from broken
  • Store your logic in one place
  • Make it easy to upgrade later

The result: calm debugging, faster fixes, and no 3 a.m. panic scrolls through unreadable logs.

Read the full article here: https://medium.com/@joseph.goins/how-i-handle-logs-and-errors-in-my-saas-projects-95c23075b54b