Skip to content

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.

  1. You send a log with tags
  2. The desktop app extracts each key-value pair
  3. New keys appear as filter dropdowns in the filter bar
  4. New values appear as options in those dropdowns
  5. Value counts are shown so you know the distribution

No schema. No config file. No “register your tags” step. Just send them.

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.

Both tags and context accept arbitrary data, but they serve different purposes:

TagsContext
TypeRecord<string, string> (strings only)Record<string, unknown> (any JSON)
PurposeFiltering and groupingDetailed inspection
UIAppear as filter dropdownsShown in expanded log view
IndexedYes (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,
},
});

Tags can be passed as an object or as an array of single-key entries (useful for dynamic tag assembly):

// Object form
gun.log({
bucket: "app",
message: "Event",
tags: { region: "us-east", feature: "auth" },
});
// Array form
gun.log({
bucket: "app",
message: "Event",
tags: [{ region: "us-east" }, { feature: "auth" }],
});

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.

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 keys
gun.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" },
});

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 accepted
gun.log({ bucket: "app", message: "Event", tags: { anything: "goes" } });

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 ValidTagSchema
const gun = createGunsoleClient<{ level: string }>({
projectId: "p",
apiKey: "k",
mode: "cloud",
});
ExportDescription
TagEntrySingle-key tag entry type
ReservedTagKeyUnion of reserved tag keys
ValidTagSchemaCompile-time tag schema constraint
TagExample valuesUse case
route/users, /ordersFilter by API endpoint
methodGET, POST, PUTFilter by HTTP method
status200, 404, 500Filter by response status
featureauth, checkout, searchFilter by product area
actionclick, submit, navigateFilter by user action type
serviceapi, worker, schedulerFilter by microservice
envproduction, staging, localFilter by environment
componentHeader, Cart, ModalFilter by React component

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.