Skip to content

Tips & Tricks

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),
},
});

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 handlers
app.get("/users", (req, res) => {
req.log.info("api", "Fetching users");
// ...
});
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;
}
// Usage
const users = await timed(gunsole, "db", "Fetch all users", () => db.query("SELECT * FROM users"));

Some useful filter combinations to save:

NameConfig
Errors onlyLevel: error + fatal only
Slow queriesBucket: db, search: “slow” or tag timed: true
Auth issuesBucket: auth, level: warn + error
Last 5 minTime range: 5m preset, all levels
Specific userTag filter on user-related tags
ShortcutAction
Cmd+, / Ctrl+,Open settings
Cmd+N / Ctrl+NNew window
Cmd+K / Ctrl+KQuick switch (command palette)
Cmd+B / Ctrl+BToggle sidebar
Cmd+R / Ctrl+RRefresh logs
Cmd+= / Cmd+- / Cmd+0Zoom in / out / reset
Cmd+F / Ctrl+FFull screen
Cmd+[ / Cmd+]Navigate back / forward
Cmd+Shift+NNew filter
Cmd+S / Ctrl+SSave filter
Cmd+DDuplicate filter
Cmd+1 / 2 / 3 / 4Toggle info / debug / warn / error
Cmd+E / Ctrl+EExport logs
Cmd+Shift+CCopy selected logs
Cmd+P / Ctrl+PPause/resume live tail
Cmd+Up / Cmd+DownScroll to top / bottom
Cmd+A / Ctrl+ASelect all logs
Cmd+/ / Ctrl+/Keyboard shortcuts reference

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.

// Bad — this will flood your viewer
app.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 context
app.use((req, res, next) => {
gunsole.debug({
bucket: "http",
message: `${req.method} ${req.path}`,
context: { headers: req.headers, body: req.body, query: req.query },
});
next();
});
// Bad
gunsole.info({ bucket: "general", message: "[API] Request received" });
gunsole.info({ bucket: "general", message: "[DB] Query executed" });
// Good
gunsole.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 tag
gunsole.info({
bucket: "api",
message: "Request",
tags: { userId: "u_12345", orderId: "ord_67890" },
});
// Good — IDs in context, categories in tags
gunsole.info({
bucket: "api",
message: "Request",
tags: { route: "/orders", method: "POST" },
context: { userId: "u_12345", orderId: "ord_67890" },
});
Terminal window
curl -s "http://localhost:17655/api/logs?projectId=my-app&level=error&limit=1000" | jq '.logs[] | .message'
Terminal window
while true; do
curl -s "http://localhost:17655/api/logs/tail?level=error&limit=5" | jq '.logs[] | "\(.timestamp) \(.message)"'
sleep 2
done

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.