SodaxLogger instead of calling
console.* directly. This lets you silence SDK output or forward it to a structured sink (Sentry,
Pino, Datadog, etc.) without patching globals.
React apps have a second, unrelated seam for failed React Query mutations. See
React mutation errors below; most dApps wire both.
Select a logger at construction
logger is a field on SodaxOptions, the Sodax constructor’s parameter type. It accepts a preset
name or a custom implementation:
ConfigService independently of the
swappable dynamic config, so sodax.config.initialize() cannot replace it and the backend can
neither set nor overwrite it. Read the resolved sink back as sodax.config.logger. Note that
sodax.instanceConfig is the merged options object and carries the logger key too; the sink the
services use is sodax.config.logger.
Interface
error() takes the thrown value as a separate second argument, before structured data, so adapters
can attach it as the exception. warn, info and debug take only (message, data?).
What the SDK emits
Counts cover the non-test sources under
packages/sdk/src.
There is no level filter. Every call reaches the configured sink, so filtering belongs in the
adapter:
Errors reaching the sink
Theerror argument is whatever the service caught: usually the raw transport, viem or wallet error,
occasionally a SodaxError,
and sometimes undefined when a site logs a message alone. The SodaxError a caller receives in the
failed Result is built after the log call, so do not expect it in the sink. Use isSodaxError(error)
before tagging on error.feature, error.code and error.context.action, and never parse
error.message, which is human-readable and may change.
SodaxError.toJSON() is the canonical serialization surface, and JSON.stringify(error) invokes it
automatically — including the bigint values inside context, which it coerces to strings. A plain
Error has no toJSON() and serializes as {}, so an adapter still has to copy name, message
and stack itself, and bigint values in data still need a replacer (see the constraints below).
See Errors And Results
for the error contract.
Built-in loggers
consoleLogger, silentLogger and resolveLogger(option) are exported from @sodax/sdk for
composition — wrapping the console logger to add a prefix, or resolving a preset name directly:
Example: Sentry in the browser
Example: structured JSON on a backend
Newline-delimited JSON on stdout is the wire shape Pino, Winston and the Datadog and CloudWatch agents consume, so this adapter needs no dependencies:apps/node/src/logging.ts
(pnpm --filter node logging). It requires no private key, RPC or network access: the backend base
URL points at a closed local port, so a real internal SDK failure reaches the sink immediately.
Example: HTTP intake without a vendor SDK
The demo app ships two adapters atapps/demo/src/lib/loggers:
createDatadogLogger() uses the plain HTTP logs intake with no Datadog SDK and no agent, and
createSentryLogger() uses a lazily imported @sentry/react behind a tunnel.
datadogLogger.ts shows the three constraints a transport-backed adapter has to satisfy:
-
Coerce
bigintbefore serializing. SDKdatarecords carrybiginttoken amounts and chain IDs. Without a replacerJSON.stringifythrows aTypeError, and a log call must never throw. -
Serialize the thrown value explicitly. Prefer
toJSON()so aSodaxErrorkeeps itsfeature,codeandcontext, and fall back to{ name, message, stack }for a plainError. -
Send fire-and-forget. Never
awaitthe transport inside a log call, and swallow its failures — an unreachable intake should drop the line rather than surface as an SDK error.
pnpm mock-intake, port 9009) sits behind the Vite dev server’s same-origin /__intake/* proxy, so
there is no CORS preflight and no DNS lookup.
Coverage
These route through the configured logger:SwapServiceandSolverApiServiceBridgeService,LeverageYieldService,PartnerFeeClaimService,SponsoringServiceMoneyMarketDataServiceandConcentratedLiquidityServiceBackendApiService,SwapsApiServiceand the shared request helperSpokeService,StellarSpokeService,BitcoinSpokeServiceand the spoke balance helpers
AssetService emit nothing today. Some pure utility and
static-helper functions — shared/utils/*, entities/btc/RadfiProvider, entities/solana/utils,
swap/IntentDataService — still call console.* directly because they hold no instance logger;
threading it through is tracked as follow-up.
React mutation errors
@sodax/dapp-kit re-exports SodaxLogger and forwards SodaxOptions through SodaxProvider, so
everything above applies unchanged. It also has a separate seam for React Query mutation failures:
onMutationError fires for every failed mutation, which is a UI-level concern, while logger
carries the SDK’s internal diagnostics. The two overlap on one failure class: a backend request error
is logged through the sink inside the SDK, and the hook then rethrows the failed Result, so
onMutationError sees the same failure again. Send exceptions to your tracker from only one of them.
A single mutation opts out of the global hook with meta: { silent: true }.
See also
- Configure SDK — the full
SodaxOptionsshape. analytics, the adjacent option onSodaxOptions, carries structured product events and is off by default, whereloggercarries free-form diagnostics and is on.