See What a Bot Is Doing
Readable records from the first run, with error codes, hints and the token masked

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
INFOlifecycle[lifecycle
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
Shard 0 is ready
shardId=0attempt=1durationMs=36mode=identifyshards=2
INFOlifecycle[lifecycle
Shard 1 is ready
shardId=1attempt=1durationMs=1033mode=identifyshards=2
INFOlifecycle[lifecycle
Connected to Fluxer as MyBot in 1520 communities
communities=1520shards=2
WARNlifecycle[lifecycle
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
Shard 1 resumed its session after 766 ms offline
shardId=1attempt=2durationMs=1mode=resumeshards=2
INFOlifecycle[lifecycle
The signal passed to runBot was aborted, so the bot is shutting down
INFOlifecycle[lifecycle
Shutting down
INFOlifecycle[lifecycle
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
ERRORevents[events
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 DocsERRORevents[events
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
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
import type { Client } from "@neontechspace/fluxerly"export function traceRequests(client: Client) { client.logging.configure({ level: "warn", categories: { rest: "debug" } }) }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
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 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
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
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: {} }) }





