Skip to content
Latest stable: v0.8.0.

Debug Mode

Debug Mode is the second-line tool for diagnosing rules that don’t fire. Toggle it with the debug field on ChaosConfig.

import { injectChaos } from '@chaos-maker/playwright';
await injectChaos(page, {
debug: true,
network: {
failures: [{ urlPattern: '/api/payments', statusCode: 503, probability: 1 }],
},
});

Or via the builder:

import { ChaosConfigBuilder } from '@chaos-maker/core';
const config = new ChaosConfigBuilder()
.failRequests('/api/payments', 503, 1)
.withDebug()
.build();

The public type accepts a boolean. Internally Chaos Maker carries a DebugOptions object so future fields can land non-breaking.

FieldTypeDefaultNotes
enabledbooleanfalseToggles the dual-sink Logger.

The fields below are reserved and not yet shipped - included only so you can plan ahead:

ReservedPlanned use
levelFilter stage emission ('verbose' | 'decisions' | 'lifecycle').
prefixOverride the [Chaos] console prefix.
consoleDisable the console sink while keeping structured events.
sinkCustom sink for off-loading debug events.

type: 'debug' events carry the concrete stage on detail.stage. The eight stages cover the full rule decision pipeline plus engine lifecycle.

StageMeaning
rule-evaluatingEntered the rule’s matcher gate.
rule-matchedurlPattern + methods + graphqlOperation all passed.
rule-skip-matchThe matcher gate rejected.
rule-skip-countingCounting condition (onNth / everyNth / afterN) not met.
rule-skip-groupRule’s group is currently disabled.
rule-skip-probabilityMatched, but the probability roll missed.
rule-appliedChaos applied.
lifecycleEngine start, stop, group toggle, or SW config swap.

When detail.stage === 'lifecycle', the concrete moment is on detail.phase:

PhaseOrigin
engine:startChaosMaker.start()
engine:stopChaosMaker.stop()
engine:group-toggledenableGroup() / disableGroup()
sw:installFirst SW install (reserved)
sw:config-appliedSW received a __chaosMakerConfig message
sw:config-stoppedSW received a __chaosMakerStop message
sw:group-toggledSW received a __chaosMakerToggleGroup message

WebSocket / SSE direction stays on detail.direction - phase is intentionally lifecycle-only.

interface ChaosEvent {
type: 'debug';
timestamp: number;
applied: false;
detail: {
stage: ChaosDebugStage;
// Lifecycle-only:
phase?: ChaosLifecyclePhase;
// Rule context:
ruleType?: string; // 'failure' | 'latency' | 'abort' | 'corruption' | 'cors' | 'ui-assault' | 'ws-drop' | 'ws-delay' | 'ws-corrupt' | 'ws-close' | 'sse-drop' | 'sse-delay' | 'sse-corrupt' | 'sse-close'
ruleId?: string; // e.g. 'failure#0' - positional within the current config snapshot
ruleName?: string; // reserved for a future builder field
groupName?: string;
// Inherited per rule type (url, method, statusCode, delayMs, strategy, …)
[key: string]: unknown;
};
}

applied is always false on debug events - they describe decisions, not chaos. The structured event mirrors a [Chaos] <stage>: ... line (or [Chaos SW] <stage>: ... from inside a Service Worker) to console.debug at emit time. The formatted string is not stored on the payload, and the prefix is owned by the Logger so the page and SW prefixes are mutually exclusive.

ruleId is a positional identifier derived from <ruleType>#<index> within the current config. Reordering rules between runs changes the IDs - that’s acceptable for in-test diagnostics. Persistent IDs across config evolution remain out of scope.

ruleName is reserved for a future builder field (e.g. .failRequests({ ..., name: 'slow-api' })). It is undefined today.

instance.on('debug', (event) => {
// event.type === 'debug' always; route on stage.
switch (event.detail.stage) {
case 'rule-applied':
myReporter.record(event);
break;
case 'rule-skip-probability':
myMetrics.incr('chaos.skip.probability', { ruleId: event.detail.ruleId });
break;
case 'lifecycle':
myReporter.lifecycle(event.detail.phase!);
break;
}
});

The '*' wildcard listener also receives debug events, alongside every other chaos event.