Skip to content

JavaScript / TypeScript SDK

The SDK ships as two packages:

PackageUse whenInstall
@gunsole/webCode runs in the browserpnpm add @gunsole/web
@gunsole/coreCode runs on the serverpnpm add @gunsole/core

@gunsole/web — for any browser code: React, Angular, Solid, Vue, Next.js client components.

@gunsole/core — for any server code: Node.js backends, Next.js server components, API routes, CLI tools.

SSR frameworks (Next.js, Nuxt, etc.) — use both. @gunsole/core for server-side, @gunsole/web for client-side. See the Next.js guide for a full example.

Zero runtime dependencies.

import { createGunsoleClient } from "@gunsole/web";
const gunsole = createGunsoleClient({
projectId: "my-app",
mode: "local",
});
OptionTypeDefaultDescription
projectIdstringrequiredIdentifies your project in the desktop app
mode"local" | "desktop" | "cloud"requiredDetermines the endpoint URL
apiKeystringAPI key (required for cloud mode)
endpointstringOverride the endpoint URL entirely
envstringEnvironment label (e.g. "production", "staging")
appNamestringApplication name, attached to every log
appVersionstringApplication version, attached to every log
defaultTagsRecord<string, string>Tags merged into every log entry
batchSizenumber10Number of logs to collect before auto-flushing
flushIntervalnumber5000Auto-flush interval in milliseconds
maxQueueSizenumber1000Drop oldest entries when exceeded
maxLogRatenumber10Logs per second (token bucket, 0 = unlimited)
maxBurstnumber100Burst allowance for rate limiting
bucketsstring[]Typed bucket accessors with autocomplete
beforeSend(entry) => entry | nullTransform or drop logs before batching
hooksobjectLifecycle hooks: onLog, onFlush, onError
isDisabledbooleanfalseKill switch — disables all logging
isDebugbooleanfalseDisables gzip compression for debugging
sessionIdstringauto-generatedOverride auto-generated session ID
fetchFetchFunctionglobalThis.fetchCustom fetch implementation
ModeEndpointUse Case
desktophttp://localhost:17655Gunsole desktop app running on your machine
localhttps://local.gunsole.comBrowser-based log viewer — logs stored in your browser’s local database
cloudhttps://api.gunsole.comGunsole Cloud (coming soon, requires apiKey)
  • desktop — sends logs directly to the Gunsole desktop app on localhost.
  • local — sends logs to local.gunsole.com (coming soon), a browser-based log viewer. Works like the desktop app but runs entirely in your browser — any environment can send logs to it and they’re stored locally in the browser’s database. No installation required.
  • cloud — sends logs to Gunsole Cloud (coming soon) for production metrics, error tracking, and team access.

The endpoint option overrides the mode-based URL if you need a custom address.

Five methods, one for each level:

gunsole.info({ bucket: "api", message: "Request handled" });
gunsole.debug({ bucket: "api", message: "Cache miss", context: { key: "user:123" } });
gunsole.warn({ bucket: "api", message: "Slow query", context: { duration: 2500 } });
gunsole.error({ bucket: "api", message: "Connection refused", tags: { db: "postgres" } });
gunsole.fatal({ bucket: "app", message: "Unhandled crash", context: { stack: err.stack } });

gunsole.log() accepts an optional level as the first argument:

gunsole.log({ bucket: "api", message: "Request handled" }); // defaults to "info"
gunsole.log("error", { bucket: "api", message: "Something broke" }); // explicit level
FieldTypeRequiredDescription
messagestringyesThe log message
bucketstringyesCategory/namespace for this log
contextRecord<string, unknown>noArbitrary structured data
tagsRecord<string, string>noKey-value pairs for filtering (string values only)
traceIdstringnoLinks related logs across operations

Each log entry is internally enriched before sending:

{
// from your call
bucket: "api",
message: "Request handled",
level: "info",
context: { ... },
tags: { ...defaultTags, ...yourTags }, // merged
// auto-populated
timestamp: 1708214400000, // Date.now()
userId: "u_123", // from setUser()
sessionId: "sess_abc", // from setSessionId()
env: "production", // from config
appName: "my-app", // from config
appVersion: "1.0.0", // from config
}
gunsole.setUser({
id: "u_123",
name: "Ada Lovelace",
traits: {
plan: "pro",
team: "backend",
},
});
gunsole.setSessionId("sess_abc123");

Once set, userId and sessionId are attached to every subsequent log. Useful for filtering logs by user in the desktop app.

In browser apps, use persistSession() to keep the same session ID across page reloads:

import { createGunsoleClient, persistSession } from "@gunsole/web";
const gunsole = createGunsoleClient({ projectId: "my-app", mode: "local" });
persistSession(gunsole);

Tags are flat key-value pairs (string → string) that become dynamic filters in the desktop app.

gunsole.info({
bucket: "api",
message: "POST /users",
tags: {
route: "/users",
method: "POST",
status: "201",
},
});

Default tags from config are merged with per-log tags. Per-log tags win on conflict.

const gunsole = createGunsoleClient({
projectId: "my-app",
mode: "local",
defaultTags: { service: "api-server", region: "us-east-1" },
});
// Sent tags: { service: "api-server", region: "us-east-1", route: "/users" }
gunsole.info({ bucket: "api", message: "Request", tags: { route: "/users" } });

For compile-time tag safety with TypeScript, see Typed Tags.

Logs are buffered and sent in batches. A batch is flushed when:

  • The batch reaches batchSize (default: 10)
  • The flushInterval timer fires (default: 5000ms)
  • You call gunsole.flush() manually
await gunsole.flush();

The SDK compresses payloads with gzip by default. Set isDebug: true to send plain JSON (useful for inspecting network requests).

The SDK uses a token bucket algorithm to prevent flooding. Default: 10 logs/sec with a burst allowance of 100. Configure via maxLogRate and maxBurst, or set maxLogRate: 0 to disable.

If logs accumulate faster than they can be sent, maxQueueSize (default: 1000) limits the queue. When exceeded, the oldest entries are dropped.

Transform or drop logs before they’re added to the batch:

const gunsole = createGunsoleClient({
projectId: "my-app",
mode: "local",
beforeSend: (entry) => {
if (entry.message.includes("sensitive")) return null; // drop log
return entry;
},
});
const gunsole = createGunsoleClient({
projectId: "my-app",
mode: "local",
hooks: {
onLog: (entry) => console.log("Logged:", entry.message),
onFlush: (logs, success) => console.log(`Flushed ${logs.length}, ok=${success}`),
onError: (error) => console.error("Flush failed:", error),
},
});

The SDK is built to never crash your app. All internal errors are silently caught (with optional onError hook). Failed HTTP requests are retried up to 3 times with exponential backoff (1s, 2s, 4s) with jitter (0.5-1.5x). HTTP 413 splits the batch and retries. 4xx (except 429) are not retried. Logs are dropped after 10 consecutive flush failures.

Automatically catch unhandled errors and promise rejections, logging them as fatal:

// @gunsole/core — manual attachment
gunsole.attachGlobalErrorHandlers();
// @gunsole/web — automatically attached on createGunsoleClient()

In the browser, this listens to window.onerror and window.onunhandledrejection.

In Node.js, this listens to process.uncaughtException and process.unhandledRejection.

Captured errors are logged as fatal level to these buckets: global_error, unhandled_rejection, uncaught_exception.

// Detach when done (e.g., in tests)
gunsole.detachGlobalErrorHandlers();

@gunsole/web automatically handles browser edge cases so you don’t lose logs:

  • Tab closing — logs are flushed before the page unloads
  • Offline/online — queued logs are flushed when the browser comes back online
  • Tab switch/minimize — logs are flushed when the page goes to background
  • Debug mode — add ?gunsole_debug=1 to any URL to enable verbose logging

When your app is shutting down (or a component unmounts):

gunsole.destroy();

This flushes remaining logs, clears timers, and detaches global error handlers.

// React
useEffect(() => {
const gunsole = createGunsoleClient({ ... });
return () => gunsole.destroy();
}, []);