Skip to content

What is gunsole?

Gunsole is a desktop log viewer. You install it, it runs a local HTTP server on port 17655, and your apps send structured logs to it via a lightweight SDK.

That’s the whole pitch.

console.log doesn’t scale. It’s fine when you have 5 logs. It’s not fine when you have 5,000, across 3 services, and you need to find the one request that broke.

Cloud logging services are powerful but they cost money, require accounts, send your data to someone else’s servers, and come with a pile of configuration you don’t need during development.

Gunsole sits in between. It’s a real log viewer — with filtering, bucketing, tagging, search — but it runs entirely on your machine. No account. No monthly bill. No data leaving localhost.

Desktop app — a native application. Receives logs, stores them in SQLite, displays them in a virtualized table with real-time streaming. Runs on macOS, Windows, Linux.

local.gunsole.com (coming soon) — the same log viewer, but in your browser. No installation required. Any environment can send logs to it and they’re stored locally in your browser’s database.

Gunsole Cloud (coming soon) — hosted SaaS for production metrics, error tracking, and team access at app.gunsole.com.

SDK — JavaScript/TypeScript, shipped as two packages:

  • @gunsole/web — for browser apps (React, Next.js client, Angular, Solid, Vue)
  • @gunsole/core — for Node.js, server-side, and CLI

Both collect logs, batch them, gzip-compress, and send them to your chosen destination. Handle retries and never crash your app.

HTTP API — the desktop app exposes a REST API on localhost:17655 for querying logs programmatically. It also runs an MCP server so AI agents can read your logs.

The SDK is built for TypeScript. Two killer features:

Type-safe buckets — define your buckets once, get callable accessors with full autocomplete. gunsole.payments("Checkout done") instead of gunsole.info({ bucket: "payments", ... }). Typos are caught at compile time. Reserved names are enforced at both the type level and runtime.

Type-safe tags — pass a tag schema as a generic and every tag key is validated at compile time. Invalid keys fail in your editor, not in production.

type Tags = { action: string; status: string };
const gunsole = createGunsoleClient<Tags>({
projectId: "my-app",
mode: "local",
buckets: ["auth", "payments"] as const,
});
gunsole.auth("User logged in", { tags: { action: "login" } }); // ✅
gunsole.payments.error("Charge failed", { tags: { typo: "x" } }); // ❌ compile error

See Typed Buckets and Tags for the full deep dive.

Each level answers a different question about your system:

LevelQuestionExampleWhen to use
debugWhat’s happening internally?State snapshots, flow tracing, variable dumpsDevelopment-time visibility. Trace execution paths, inspect intermediate state.
infoWhat just happened?User logged in, payment processed, email sentNormal operations you want a record of. The “everything is fine” level.
warnIs something off?Empty response, deprecated usage, slow query (>2s)Something unexpected but not broken. The app continues, but you should investigate.
errorIs something broken?Failed write, invalid state, backend 500, network timeoutSomething failed and a feature isn’t working. Needs attention.
fatalIs something dead?Uncaught exception, unhandled rejection, error boundary catchThe app (or a part of it) has crashed. Immediate action required.

This is the most important mental model:

  • error = something is broken, but the app keeps running. A payment failed, an API returned garbage, a write didn’t persist. The user might see an error message, but they can still use the app.
  • fatal = something is dead. An uncaught exception, an unhandled promise rejection, a React error boundary catch. This part of the app has stopped working entirely.

Think of it this way:

  • error → “This feature is broken”
  • fatal → “This page/process is dead”

The SDK’s global error handlers automatically log fatal for:

  • window.onerror (browser uncaught exceptions)
  • window.onunhandledrejection (browser unhandled promise rejections)
  • process.uncaughtException (Node.js)
  • process.unhandledRejection (Node.js)

You can also call gunsole.fatal() explicitly — for example, in a React error boundary:

class ErrorBoundary extends React.Component {
componentDidCatch(error, info) {
gunsole.fatal({
bucket: "error_boundary",
message: error.message,
context: { stack: error.stack, componentStack: info.componentStack },
});
}
}
  • Not a production monitoring tool
  • Not an APM (no metrics, no traces visualization yet)
  • Not a cloud service (nothing leaves your machine)
  • Not trying to replace your production logging pipeline

It’s a development tool. It makes the “what the hell is happening in my app” question answerable without grepping through terminal output.