Tags
Tags are the most powerful filtering mechanism in gunsole. They’re simple key-value pairs attached to log entries, but the magic is in how they surface in the UI.
How it works
Section titled “How it works”- You send a log with tags
- The desktop app extracts each key-value pair
- New keys appear as filter dropdowns in the filter bar
- New values appear as options in those dropdowns
- Value counts are shown so you know the distribution
No schema. No config file. No “register your tags” step. Just send them.
Example
Section titled “Example”gun.info({ bucket: "api", message: "Request handled", tags: { route: "/users", method: "GET", status: "200", },});
gun.info({ bucket: "api", message: "Request handled", tags: { route: "/orders", method: "POST", status: "201", },});
gun.error({ bucket: "api", message: "Request failed", tags: { route: "/orders", method: "POST", status: "500", },});After these three logs, the filter bar shows:
- route dropdown:
/users(1),/orders(2) - method dropdown:
GET(1),POST(2) - status dropdown:
200(1),201(1),500(1)
Select status: 500 to see only the failed request. Select route: /orders + method: POST to see all POST requests to /orders.
Tags vs. context
Section titled “Tags vs. context”Both tags and context accept arbitrary data, but they serve different purposes:
| Tags | Context | |
|---|---|---|
| Type | Record<string, string> (strings only) | Record<string, unknown> (any JSON) |
| Purpose | Filtering and grouping | Detailed inspection |
| UI | Appear as filter dropdowns | Shown in expanded log view |
| Indexed | Yes (fast queries) | No (stored as JSON blob) |
Rule of thumb: If you want to filter by it, use a tag. If you want to inspect it when looking at a specific log, use context.
gun.info({ bucket: "api", message: "POST /orders → 201", // Things you filter by tags: { route: "/orders", method: "POST", status: "201", }, // Things you look at when investigating context: { orderId: "ord_abc123", items: 3, total: 149.99, userId: "u_456", latencyMs: 234, },});Tag entries
Section titled “Tag entries”Tags can be passed as an object or as an array of single-key entries (useful for dynamic tag assembly):
// Object formgun.log({ bucket: "app", message: "Event", tags: { region: "us-east", feature: "auth" },});
// Array formgun.log({ bucket: "app", message: "Event", tags: [{ region: "us-east" }, { feature: "auth" }],});Default tags
Section titled “Default tags”Set defaultTags in your client config to automatically attach tags to every log:
const gun = createGunsoleClient({ projectId: "my-app", apiKey: "dev", mode: "local", defaultTags: { service: "api-server", region: "us-east-1", version: "1.4.2", },});Every log from this client will have service, region, and version tags. Per-log tags are merged on top — if there’s a conflict, the per-log value wins.
Typed tags
Section titled “Typed tags”Define a tag schema as a generic parameter for compile-time safety and autocomplete:
import { createGunsoleClient } from "gunsole-js";
type MyTags = { region: string; feature: string;};
const gun = createGunsoleClient<MyTags>({ projectId: "my-app", apiKey: "dev", mode: "cloud", buckets: ["payment"],});
// Autocomplete + type checking on tag keysgun.payment("Checkout", { tags: { region: "us-east", feature: "checkout" } });
// Error: 'invalid' does not exist in type 'Partial<MyTags>'gun.payment("Checkout", { tags: { invalid: "value" } });Works with both log() and typed bucket accessors:
gun.log({ bucket: "app", message: "Event", tags: { region: "us-east", feature: "auth" },});
// Error: 'typo' does not exist in type 'Partial<MyTags>'gun.log({ bucket: "app", message: "Event", tags: { typo: "value" },});Without a schema
Section titled “Without a schema”Omitting the generic parameter allows any string keys — tags still work, you just don’t get compile-time checking:
const gun = createGunsoleClient({ projectId: "p", apiKey: "k", mode: "cloud",});
// No type error — any key is acceptedgun.log({ bucket: "app", message: "Event", tags: { anything: "goes" } });Reserved tag keys
Section titled “Reserved tag keys”Tag keys that collide with internal log entry fields are rejected at compile time via ValidTagSchema:
bucket, message, level, timestamp, userId, sessionId, env, appName, appVersion
// Compile-time error: Type '{ level: string }' does not satisfy ValidTagSchemaconst gun = createGunsoleClient<{ level: string }>({ projectId: "p", apiKey: "k", mode: "cloud",});TypeScript exports
Section titled “TypeScript exports”| Export | Description |
|---|---|
TagEntry | Single-key tag entry type |
ReservedTagKey | Union of reserved tag keys |
ValidTagSchema | Compile-time tag schema constraint |
Good tag patterns
Section titled “Good tag patterns”| Tag | Example values | Use case |
|---|---|---|
route | /users, /orders | Filter by API endpoint |
method | GET, POST, PUT | Filter by HTTP method |
status | 200, 404, 500 | Filter by response status |
feature | auth, checkout, search | Filter by product area |
action | click, submit, navigate | Filter by user action type |
service | api, worker, scheduler | Filter by microservice |
env | production, staging, local | Filter by environment |
component | Header, Cart, Modal | Filter by React component |
Storage
Section titled “Storage”Tags are stored in a separate tags table with a many-to-many relationship to logs via log_tag_map. This makes tag queries fast — they use indexed JOINs rather than scanning JSON blobs.