Tips & Tricks
SDK patterns
Section titled “SDK patterns”Environment-aware initialization
Section titled “Environment-aware initialization”const gunsole = createGunsoleClient({ projectId: "my-app", mode: process.env.NODE_ENV === "production" ? "cloud" : "local", apiKey: process.env.GUNSOLE_API_KEY, // only needed for cloud mode env: process.env.NODE_ENV, appVersion: process.env.npm_package_version, defaultTags: { buildId: process.env.BUILD_ID, commit: process.env.GIT_SHA?.slice(0, 7), },});Request-scoped logging
Section titled “Request-scoped logging”Create a per-request logger that automatically carries the trace ID and user:
function createRequestLogger(req) { const traceId = req.headers["x-request-id"] || crypto.randomUUID();
return { info: (bucket, message, extra = {}) => gunsole.info({ bucket, message, traceId, ...extra }), error: (bucket, message, extra = {}) => gunsole.error({ bucket, message, traceId, ...extra }), warn: (bucket, message, extra = {}) => gunsole.warn({ bucket, message, traceId, ...extra }), debug: (bucket, message, extra = {}) => gunsole.debug({ bucket, message, traceId, ...extra }), };}
app.use((req, res, next) => { req.log = createRequestLogger(req); next();});
// In route handlersapp.get("/users", (req, res) => { req.log.info("api", "Fetching users"); // ...});Timing helper
Section titled “Timing helper”function timed(gunsole, bucket, message, fn) { const start = Date.now(); const result = fn();
if (result instanceof Promise) { return result.then((val) => { gunsole.debug({ bucket, message: `${message} (${Date.now() - start}ms)`, context: { durationMs: Date.now() - start }, tags: { timed: "true" }, }); return val; }); }
gunsole.debug({ bucket, message: `${message} (${Date.now() - start}ms)`, context: { durationMs: Date.now() - start }, tags: { timed: "true" }, }); return result;}
// Usageconst users = await timed(gunsole, "db", "Fetch all users", () => db.query("SELECT * FROM users"));Desktop app patterns
Section titled “Desktop app patterns”Saved filter recipes
Section titled “Saved filter recipes”Some useful filter combinations to save:
| Name | Config |
|---|---|
| Errors only | Level: error + fatal only |
| Slow queries | Bucket: db, search: “slow” or tag timed: true |
| Auth issues | Bucket: auth, level: warn + error |
| Last 5 min | Time range: 5m preset, all levels |
| Specific user | Tag filter on user-related tags |
Keyboard shortcuts
Section titled “Keyboard shortcuts”| Shortcut | Action |
|---|---|
Cmd+, / Ctrl+, | Open settings |
Cmd+N / Ctrl+N | New window |
Cmd+K / Ctrl+K | Quick switch (command palette) |
Cmd+B / Ctrl+B | Toggle sidebar |
Cmd+R / Ctrl+R | Refresh logs |
Cmd+= / Cmd+- / Cmd+0 | Zoom in / out / reset |
Cmd+F / Ctrl+F | Full screen |
Cmd+[ / Cmd+] | Navigate back / forward |
Cmd+Shift+N | New filter |
Cmd+S / Ctrl+S | Save filter |
Cmd+D | Duplicate filter |
Cmd+1 / 2 / 3 / 4 | Toggle info / debug / warn / error |
Cmd+E / Ctrl+E | Export logs |
Cmd+Shift+C | Copy selected logs |
Cmd+P / Ctrl+P | Pause/resume live tail |
Cmd+Up / Cmd+Down | Scroll to top / bottom |
Cmd+A / Ctrl+A | Select all logs |
Cmd+/ / Ctrl+/ | Keyboard shortcuts reference |
Multi-window workflow
Section titled “Multi-window workflow”Open the same project in multiple windows with different filters. One window for errors, another for the full firehose. Each window maintains its own filter state. Use Cmd+N / Ctrl+N to create a new main window instance.
Log hygiene
Section titled “Log hygiene”Don’t over-log
Section titled “Don’t over-log”// Bad — this will flood your viewerapp.use((req, res, next) => { gunsole.debug({ bucket: "http", message: `Headers: ${JSON.stringify(req.headers)}` }); gunsole.debug({ bucket: "http", message: `Body: ${JSON.stringify(req.body)}` }); gunsole.debug({ bucket: "http", message: `Query: ${JSON.stringify(req.query)}` }); next();});
// Good — one log with contextapp.use((req, res, next) => { gunsole.debug({ bucket: "http", message: `${req.method} ${req.path}`, context: { headers: req.headers, body: req.body, query: req.query }, }); next();});Use buckets, not prefixes
Section titled “Use buckets, not prefixes”// Badgunsole.info({ bucket: "general", message: "[API] Request received" });gunsole.info({ bucket: "general", message: "[DB] Query executed" });
// Goodgunsole.info({ bucket: "api", message: "Request received" });gunsole.info({ bucket: "db", message: "Query executed" });Tags are for filtering, context is for inspection
Section titled “Tags are for filtering, context is for inspection”// Bad — high-cardinality taggunsole.info({ bucket: "api", message: "Request", tags: { userId: "u_12345", orderId: "ord_67890" },});
// Good — IDs in context, categories in tagsgunsole.info({ bucket: "api", message: "Request", tags: { route: "/orders", method: "POST" }, context: { userId: "u_12345", orderId: "ord_67890" },});Scripting with the API
Section titled “Scripting with the API”Pipe logs to a file
Section titled “Pipe logs to a file”curl -s "http://localhost:17655/api/logs?projectId=my-app&level=error&limit=1000" | jq '.logs[] | .message'Watch for errors
Section titled “Watch for errors”while true; do curl -s "http://localhost:17655/api/logs/tail?level=error&limit=5" | jq '.logs[] | "\(.timestamp) \(.message)"' sleep 2doneSend a log from any language
Section titled “Send a log from any language”The protocol is just POST /logs with JSON. If there’s no SDK for your language, it takes about 10 lines of code to send a log. See the REST API docs for the exact format.