The SDK provides six log methods corresponding to the Aires severity levels:
import { aires } from "@aires/sdk"
aires.trace("entering parseConfig") // Finest-grained debug info
aires.debug("loaded 42 config entries") // Development diagnostics
aires.info("server listening on :3000") // Normal operational events
aires.warn("connection pool at 80%") // Potential problems
aires.error("request handler threw") // Errors that need attention
aires.fatal("database unreachable") // System cannot continue
All six methods share the same signature:
aires.info(message: string, opts?: LogOptions): void
The call is non-blocking. The event is enqueued in the batch channel and returns immediately. Events are shipped to the collector in batches (default: 256 events or 500ms).
Every logging method accepts an optional second argument with additional context:
type LogOptions = {
traceId?: string
spanId?: string
sessionId?: string
userId?: string
agentId?: string
category?: string
displayText?: string
tags?: string[]
attr?: Record<string, string>
data?: Record<string, unknown>
file?: string
line?: number
fn?: string
http?: {
method: string
path: string
status: number
durationMs: number
}
error?: {
type: string
message: string
stack?: string
handled?: boolean
}
}
String key-value pairs for structured metadata. Use attr for data you want to filter and search on:
aires.info("user authenticated", {
attr: {
userId: "user-42",
method: "oauth2",
provider: "github",
ip: "203.0.113.42",
},
})
Attributes are stored in the attributes map column in ClickHouse and can be queried with:
SELECT * FROM events
WHERE attributes['userId'] = 'user-42';
Free-form string tags for categorization and filtering:
aires.info("deployment complete", {
tags: ["deploy", "production", "v1.2.0"],
})
Query tags with ClickHouse array functions:
SELECT * FROM events
WHERE has(tags, 'deploy');
Use data for arbitrary JSON objects that don’t fit into string key-value pairs:
aires.info("order processed", {
data: {
order: {
id: "ord-789",
items: 3,
total: 149.99,
currency: "USD",
},
customer: {
tier: "premium",
lifetime_orders: 42,
},
},
})
Data values are JSON-serialized and stored as bytes in the data map column. They’re preserved as-is for later retrieval and analysis.
Use displayText for a formatted version of the log message. This can include ANSI escape codes for terminal rendering or rich text for UI display:
aires.info("build complete", {
displayText: "\x1b[32m✓\x1b[0m Build complete in 4.2s (42 modules)",
})
The message field should always be a plain, searchable string. displayText is for rendering only.
Categories group related events. Common categories:
// HTTP request/response events
aires.info("GET /api/users", { category: "http" })
// Database operations
aires.info("SELECT users", { category: "db" })
// Authentication events
aires.warn("invalid token", { category: "auth" })
// AI agent events
aires.info("tool invoked", { category: "ai" })
// Kubernetes events
aires.info("pod scheduled", { category: "k8s" })
Filter by category in ClickHouse:
SELECT * FROM events
WHERE category = 'http'
AND severity = 'error'
ORDER BY timestamp DESC;
Attach structured error details to any log event:
try {
await db.query("SELECT ...")
} catch (err) {
aires.error("database query failed", {
category: "db",
error: {
type: err.constructor.name, // "PostgresError"
message: err.message, // "connection refused"
stack: err.stack, // full stack trace
handled: true, // was this caught?
},
attr: {
query: "SELECT ...",
database: "primary",
},
})
}
For unhandled errors:
process.on("uncaughtException", (err) => {
aires.fatal("uncaught exception", {
error: {
type: err.constructor.name,
message: err.message,
stack: err.stack,
handled: false,
},
})
})
Query errors in ClickHouse:
-- Unhandled errors in the last hour
SELECT timestamp, service, error_type, error_message
FROM events
WHERE severity = 'error'
AND error_handled = false
AND timestamp > now() - INTERVAL 1 HOUR
ORDER BY timestamp DESC;
You can manually attach source file, line number, and function name to any event:
aires.info("processing started", {
file: "src/worker.ts",
line: 42,
fn: "processTask",
})
This is useful for custom logging wrappers. The Rust SDK’s aires_log! macro captures source location automatically via file!() and line!().
Attach HTTP request/response details to events:
aires.info("request completed", {
category: "http",
http: {
method: "POST",
path: "/api/tasks",
status: 201,
durationMs: 47,
},
attr: {
userAgent: req.headers["user-agent"],
contentLength: req.headers["content-length"],
},
})
See HTTP Middleware for automatic HTTP instrumentation.
Attach trace and span IDs to correlate events across services:
aires.info("processing order", {
traceId: "abc-123-def-456",
spanId: "span-001",
sessionId: "sess-789",
userId: "user-42",
})
See Tracing for full distributed tracing documentation.
Combining all options:
aires.error("payment processing failed", {
// Tracing
traceId: "trace-abc-123",
spanId: "span-payment-001",
sessionId: "sess-checkout-789",
userId: "user-42",
agentId: "agent-billing",
// Categorization
category: "payment",
tags: ["stripe", "card-declined", "retry-eligible"],
// Display
displayText: "\x1b[31m✗\x1b[0m Payment failed: card_declined (amount: $149.99)",
// Structured attributes
attr: {
paymentId: "pay-xyz",
amount: "14999",
currency: "usd",
stripeErrorCode: "card_declined",
},
// Rich data
data: {
stripeResponse: {
id: "ch_xxx",
status: "failed",
decline_code: "insufficient_funds",
},
},
// Error details
error: {
type: "StripeCardError",
message: "Your card was declined.",
stack: "StripeCardError: Your card was declined.\n at processPayment (payment.ts:89)",
handled: true,
},
// Source location
file: "src/services/payment.ts",
line: 89,
fn: "processPayment",
})