openFetch gives you two complementary ways to see what happens around HTTP: a built-in debug pipeline on createClient (structured lifecycle events), and a debug() middleware for request/response/error snapshots. The package also ships redaction utilities and typed errors so diagnostics stay useful without leaking secrets.
Enable structured logging on the client defaults (or per request). When debug is set, each call emits OpenFetchDebugEvent objects with at least:
stage— Where you are in the lifecycle (see tables below).timestamp—Date.now()when the event was emitted.- Additional fields — Depends on the stage (
url,status,durationMs,reason, …).
import { createClient } from "@hamdymohamedak/openfetch";
const client = createClient({
debug: true, // same as "verbose"
logger: (log) => {
// Optional. Omit to use console.debug with an [OpenFetch] prefix.
console.debug(log.stage, log);
},
});debug value |
Behavior |
|---|---|
true or "verbose" |
Full pipeline: merge/init, outgoing request, each fetch attempt, parse, schema, retries, final response, errors. |
"basic" |
Only request, final response, and error (good for production-style tracing with less noise). |
false / omitted |
No pipeline events. |
Per-request overrides merge like the rest of OpenFetchConfig: you can set debug: "basic" on the client and debug: "verbose" on a single client.get(url, { debug: "verbose" }) when investigating one call.
const client = createClient({
debug: import.meta.env.DEV ? "verbose" : false,
logger: (e) => {
if (import.meta.env.DEV) console.debug("[API]", e);
},
});On Node you can use process.env.NODE_ENV === "development" the same way.
The logger function receives one structured object per event. You can forward to OpenTelemetry, a test spy, or a small in-memory ring buffer:
const events: import("@hamdymohamedak/openfetch").OpenFetchDebugEvent[] = [];
const client = createClient({
debug: "verbose",
logger: (log) => {
events.push(log);
},
});The exported type OpenFetchDebugEvent is { stage: string; timestamp: number } & Record<string, unknown> — treat unknown keys as optional metadata.
These stages are emitted when debug is true or "verbose" (not for "basic" except where noted):
| Stage | When | Useful fields (examples) |
|---|---|---|
config |
After merge, before init |
method, url (redacted path), hasJsonSchema, retryMaxAttempts, … |
init |
After all init[] callbacks |
hooksRun |
request |
After request interceptors, before middleware | basic too — method, url (fully built + query redacted), optional headers (masked) |
attempt_start |
Start of each retry loop iteration | attempt, maxAttempts |
fetch |
Before fetch() for that attempt |
attempt, method, url, headers (masked) |
fetch_complete |
After headers received | attempt, status, statusText, durationMs, contentLength (if server sent Content-Length) |
parse |
After body read / parse | attempt, ok, responseType or skipped + reason for rawResponse |
schema |
After Standard Schema validation (if jsonSchema set) |
attempt, ok, issueCount on failure |
hook_after_response |
When onAfterResponse throws OpenFetchForceRetry |
hook, action |
retry |
When scheduling another attempt after a failure | failedAttempt, nextAttempt, reason, delayMs |
response |
End of successful client call (basic too) |
status, statusText, durationMs (wall time for the whole call, including retries) |
error |
Uncaught failure from the client run (basic too) |
name, message, code for OpenFetchError, stack (truncated), durationMs |
Retry reasons (on the retry stage) include values such as forceRetry, timeout, http_503, network, parse, retryBudget, canceled, unknown — derived from the error about to trigger backoff.
- URLs in debug events use the same sensitive query redaction as
redactSensitiveUrlQuery(tokens, passwords,code, etc. →[REDACTED]by default). - Headers on
requestandfetchare passed throughmaskHeaderValues(authorization, cookies, API keys, …).
Do not log raw tokens in your own logger implementation; treat events as trusted dev output only.
The debug(options?) export is a middleware that wraps next() and logs three phases: request, response, error. It does not know about merge, retry attempt indices, or schema validation — it is lighter and stack-local.
import { createClient } from "@hamdymohamedak/openfetch";
import { debug } from "@hamdymohamedak/openfetch/plugins";
const client = createClient({
middlewares: [
debug({
includeRequestHeaders: true,
maskStrategy: "partial",
maskUrlQuery: true,
log: (phase, payload) => console.debug("[openfetch]", phase, payload),
}),
],
});Options include maskHeaders, maskStrategy, sensitiveQueryParamNames, sensitiveQueryParamReplacement, and enabled. See Plugins & fluent API.
| Need | Use |
|---|---|
| Full lifecycle, retries, parse/schema, one switch on the client | createClient({ debug, logger }) |
Quick middleware-only traces, or logs only around fetch |
debug() plugin |
| Both | Possible; avoid duplicate noise by using "basic" on the client or disabling the plugin’s log when the pipeline is verbose. |
For user-facing or cross-service errors, serialize with OpenFetchError.toShape() / toJSON() instead of logging the Error instance directly. By default, shapes omit sensitive fields and can redact URLs (see Errors & security).
Use isOpenFetchError, isHTTPError, isTimeoutError, and isSchemaValidationError to branch in logger or global handlers.
| Export | Use in debugging |
|---|---|
maskHeaderValues |
Build your own logs or UI with the same masking rules as the pipeline. |
redactSensitiveUrlQuery |
Redact query params in arbitrary URL strings. |
DEFAULT_SENSITIVE_QUERY_PARAM_NAMES |
Extend or document what the package treats as sensitive. |
cloneResponse |
Clone a native Response if you read the body in middleware and still want OpenFetch or the browser to consume it. |
OpenFetchForceRetry, isOpenFetchForceRetry |
Understand forced retries in retry.onAfterResponse. |
For custom spans (e.g. “before business mapper”), combine the debug pipeline with:
hooks({ onRequest, onResponse, onError, onBeforeRetry, onAfterResponse })— see Plugins & fluent API and Retry & cache.- Request / response interceptors — see Interceptors & middleware.
The built-in pipeline shows when the stack runs; your hooks carry domain context.
createFluentClient is built on createClient, so debug and logger on defaults apply to fluent chains as well.
- Configuration — full
OpenFetchConfigreference - Features & request pipeline — order of merge, interceptors, middleware, dispatch
- Errors & security —
toShape, redaction, safe logging - Retry & cache — retry options and
OpenFetchForceRetry - Plugins & fluent API —
debug(),hooks(),retry()