Skip to content

Tips & Tricks

import { Aside } from ‘@astrojs/starlight/components’;

const gun = createGunsoleClient({
projectId: "my-app",
apiKey: process.env.GUNSOLE_API_KEY || "dev",
mode: process.env.NODE_ENV === "production" ? "cloud" : "local",
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 = {}) =>
gun.info({ bucket, message, traceId, ...extra }),
error: (bucket, message, extra = {}) =>
gun.error({ bucket, message, traceId, ...extra }),
warn: (bucket, message, extra = {}) =>
gun.warn({ bucket, message, traceId, ...extra }),
debug: (bucket, message, extra = {}) =>
gun.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(gun, bucket, message, fn) {
const start = Date.now();
const result = fn();
if (result instanceof Promise) {
return result.then((val) => {
gun.debug({
bucket,
message: `${message} (${Date.now() - start}ms)`,
context: { durationMs: Date.now() - start },
tags: { timed: "true" },
});
return val;
});
}
gun.debug({
bucket,
message: `${message} (${Date.now() - start}ms)`,
context: { durationMs: Date.now() - start },
tags: { timed: "true" },
});
return result;
}
// Usage
const users = await timed(gun, "db", "Fetch all users", () => db.query("SELECT * FROM users"));

Some useful filter combinations to save:

NameConfig
Errors onlyLevel: error 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

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.

// Bad — this will flood your viewer
app.use((req, res, next) => {
gun.debug({ bucket: "http", message: `Headers: ${JSON.stringify(req.headers)}` });
gun.debug({ bucket: "http", message: `Body: ${JSON.stringify(req.body)}` });
gun.debug({ bucket: "http", message: `Query: ${JSON.stringify(req.query)}` });
next();
});
// Good — one log with context
app.use((req, res, next) => {
gun.debug({
bucket: "http",
message: `${req.method} ${req.path}`,
context: { headers: req.headers, body: req.body, query: req.query },
});
next();
});
// Bad
gun.info({ bucket: "general", message: "[API] Request received" });
gun.info({ bucket: "general", message: "[DB] Query executed" });
// Good
gun.info({ bucket: "api", message: "Request received" });
gun.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
gun.info({
bucket: "api",
message: "Request",
tags: { userId: "u_12345", orderId: "ord_67890" },
});
// Good — IDs in context, categories in tags
gun.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.