See What a Bot Is Doing

Readable records from the first run, with error codes, hints and the token masked

A scroll of output lines unrolling from a spool

Readable from the First Run

Every client prints what it is doing without any setup: Startup, each shard becoming ready, lost connections with their close code and next step, long rate-limit waits and shutdown. A shard is one gateway connection, the live link that delivers events

On a terminal each record is one line with its level, category, code in brackets, message and facts, and an error's details follow on indented lines. Each line also starts with its time, left out here. Under a process manager the same records print as JSON. The Effect entry point logs through the running program's Effect logger unless a format or sink is set

Read the Default Output
Terminal

INFOlifecycle[lifecycle.startupDeadline]

Startup of 2 shards may take up to 31.0 s: The configured 30.0 s (connection.startupTimeoutMs) plus 1 s per additional shard, because the SDK starts shard sessions 1 s apart

startupTimeoutMs=31000configuredMs=30000shards=2

INFOlifecycle[lifecycle.ready]

Shard 0 is ready

shardId=0attempt=1durationMs=36mode=identifyshards=2

INFOlifecycle[lifecycle.ready]

Shard 1 is ready

shardId=1attempt=1durationMs=1033mode=identifyshards=2

INFOlifecycle[lifecycle.connected]

Connected to Fluxer as MyBot in 1520 communities

communities=1520shards=2

WARNlifecycle[lifecycle.connectionLost]

Shard 1 lost its connection. Close code 4000 (UNKNOWN_ERROR): The gateway reported an unspecified error. Reconnecting and resuming the session in 755 ms

shardId=1closeCode=4000attempt=1delayMs=755reason=closedclassification=closednext=resume

INFOlifecycle[lifecycle.ready]

Shard 1 resumed its session after 766 ms offline

shardId=1attempt=2durationMs=1mode=resumeshards=2

INFOlifecycle[lifecycle.stopRequested]

The signal passed to runBot was aborted, so the bot is shutting down

INFOlifecycle[lifecycle.shutdown]

Shutting down

INFOlifecycle[lifecycle.shutdownComplete]

Shutdown complete in 3 ms

durationMs=3

A Failed Handler, in Full

A handler that throws is printed at Error with its message, stack and cause chain, because the error came from the bot's own code

Frames inside the SDK collapse into one line, so the bot's own frames stand out. Credentials are masked everywhere in the record, causes included

Failed Handlers in the Docs
Terminal

ERRORevents[events.handlerFailed]

The messageCreate handler failed: Cannot read properties of undefined (reading 'content')

event=messageCreatesubscriptionId=messageCreate#1messageId=1456074443980800005channelId=1456074443980800002guildId=1456074443980800001

TypeError: Cannot read properties of undefined (reading 'content')

at messageCreate (file:///srv/bot/bot.js:3:78)

... 9 SDK and Effect frames hidden

Permission Hints

An expected SDK error, such as a refused request, prints its code, hint and details instead of stack frames

For this failed reply, the hint names View channel and Send messages and where to grant them, and requiredPermissions lists them in the details. Fluxer does not say which one is missing, so the SDK names every permission it checks

The hint names the permissions and where to grant them

Rejections in the Docs
Terminal

ERRORevents[events.handlerFailed]

The messageCreate handler failed: Message send failed: Fluxer reports that the bot lacks a required permission (MISSING_PERMISSIONS, HTTP 403)

event=messageCreatesubscriptionId=messageCreate#1messageId=1456074443980800006channelId=1456074443980800002guildId=1456074443980800001

MessageError [message.send.rejected]: Message send failed: Fluxer reports that the bot lacks a required permission (MISSING_PERMISSIONS, HTTP 403)

HintThe bot needs View channel and Send messages for this operation. Grant them to the bot's role in the community settings or the channel's permission overrides. Embeds also need Embed links, files need Attach files, and a forward needs View channel in its source channel

Detailsoperation=sendreason=rejectedoutcome=rejectedstatus=403apiError=MISSING_PERMISSIONSrequiredPermissions=["ViewChannel","SendMessages"]

Turn the Detail Up or Down

The default level is info. Set a level for the whole client and one for single areas, such as rest for requests, when the client is created or while it runs, without a restart

To look closer without touching the code, set FLUXERLY_DEBUG before starting the bot. 1 adds Debug records everywhere, and a list such as rest,ratelimit adds them only there

Levels and Categories
TypeScript
import type { Client } from "@neontechspace/fluxerly"export function traceRequests(client: Client) {    client.logging.configure({ level: "warn", categories: { rest: "debug" } })}
Terminal
FLUXERLY_DEBUG=rest,ratelimit node bot.js

Into the Logger You Already Use

The sink option replaces the console output. It receives each record as an object with a level, a fixed code, a readable message and safe facts

A sink that throws is counted and reported once, and the bot keeps working. Match on the code, because the wording of a message can change

Send Records to a Logger
TypeScript
import { createClient } from "@neontechspace/fluxerly"/** A logger with one log method that reads the level from the entry */interface EntryLogger {    log(entry: { level: string; message: string; [key: string]: unknown }): void}export function createEntryLoggerClient(token: string, logger: EntryLogger) {    return createClient({        token,        logging: {            sink: (record) => logger.log({ ...record, level: record.level === "fatal" ? "error" : record.level }),        },    })}

Count What Happened

client.diagnostics().counters keeps running totals since the client was created: Handler failures, dropped events by reason, reconnects, retries, rate-limit waits and more

Identical warnings and errors within a minute print once, then say how many were hidden. The counters still include every occurrence, hidden repeats too

Counters in the Logging Guide
TypeScript
import type { Client } from "@neontechspace/fluxerly"export function healthSnapshot(client: Client) {    const { counters, state } = client.diagnostics()    return {        state,        reconnects: counters.reconnects,        droppedEvents: Object.values(counters.eventsDropped).reduce((total, count) => total + count, 0),        handlerFailures: counters.handlerFailures,    }}

Metrics and Traces

Pass observe to receive a measurement each time the SDK finishes a piece of work: A request with its route, status and duration, a rate-limit wait, a reconnect, and a handler or command run with its outcome

Measurements never contain tokens, payloads or message content, so they can go to any monitoring system. An observe function that throws changes nothing the bot does

OpenTelemetry Export in the Logging Guide
TypeScript
import { runBot, type Observation } from "@neontechspace/fluxerly"type Attributes = Record<string, string | number>/** The parts of an OpenTelemetry Meter this bot uses */interface Meter {    createHistogram(name: string, options?: { unit?: string }): {        record(value: number, attributes?: Attributes): void    }    createCounter(name: string): { add(value: number, attributes?: Attributes): void }}export function meterObserver(meter: Meter) {    const requests = meter.createHistogram("bot.rest.duration", { unit: "ms" })    const handlers = meter.createHistogram("bot.handler.duration", { unit: "ms" })    const reconnects = meter.createCounter("bot.gateway.reconnects")    return (observation: Observation) => {        if (observation.type === "rest")            requests.record(observation.durationMs, {                method: observation.method,                route: observation.route,                status: observation.status ?? "none",            })        else if (observation.type === "handler")            handlers.record(observation.durationMs, {                event: observation.event,                ...(observation.command === undefined ? {} : { command: observation.command }),                outcome: observation.outcome,            })        else if (observation.type === "reconnect") reconnects.add(1, { shard: observation.shardId })    }}export function runObservedBot(token: string | undefined, meter: Meter) {    return runBot({ token, observe: meterObserver(meter), events: {} })}