Overview
Resilience hooks provide callbacks at key points in the execution lifecycle, enabling metrics collection, logging, alerting, and custom behavior. All hooks are optional and receive detailed context about the execution.
ResilienceHooks Type
type ResilienceHooks = {
onAttempt?: (info: { name: string; attempt: number }) => void;
onSuccess?: (info: { name: string; attempt: number; timeMs: number }) => void;
onFailure?: (info: { name: string; attempt: number; timeMs: number; error: unknown }) => void;
onRetry?: (info: { name: string; attempt: number; delayMs: number; error: unknown }) => void;
onCircuitOpen?: (info: { name: string }) => void;
onCircuitHalfOpen?: (info: { name: string }) => void;
onCircuitClosed?: (info: { name: string }) => void;
}
Defined in src/global.d.ts:24-32.
Hook Reference
onAttempt
Called at the start of each execution attempt, before the function runs.
onAttempt?: (info: { name: string; attempt: number }) => void
Name of the function being executed.
Current attempt number (1-based). First attempt is 1, first retry is 2, etc.
When called: Before every execution attempt, including initial and retries.
Use cases:
- Increment attempt counters
- Log execution attempts
- Track in-flight requests
Example:
hooks: {
onAttempt: ({ name, attempt }) => {
metrics.increment(`${name}.attempts`, { attempt });
logger.debug(`[${name}] Starting attempt ${attempt}`);
}
}
Implementation location: src/index.ts:130
onSuccess
Called when a function execution completes successfully.
onSuccess?: (info: { name: string; attempt: number; timeMs: number }) => void
Name of the function that succeeded.
The attempt number that succeeded (1 if succeeded on first try).
Execution time in milliseconds for this successful attempt.
When called: After successful execution, before returning the result.
Use cases:
- Record success metrics
- Track execution time histograms
- Log successful operations
- Update circuit breaker state
Example:
hooks: {
onSuccess: ({ name, attempt, timeMs }) => {
metrics.recordTiming(`${name}.duration`, timeMs);
metrics.increment(`${name}.success`, { attempt });
if (attempt > 1) {
logger.info(`[${name}] Succeeded on attempt ${attempt} after ${timeMs}ms`);
}
}
}
Implementation location: src/index.ts:148
onFailure
Called when a function execution fails.
onFailure?: (info: { name: string; attempt: number; timeMs: number; error: unknown }) => void
Name of the function that failed.
The attempt number that failed.
Execution time in milliseconds before failure.
The error that was thrown. Can be any type (Error, string, object, etc.).
When called: After every failed execution attempt, regardless of whether it will be retried.
Use cases:
- Record failure metrics
- Log errors
- Track error types
- Send alerts for repeated failures
- Update circuit breaker state
Example:
hooks: {
onFailure: ({ name, attempt, timeMs, error }) => {
metrics.increment(`${name}.failure`, { attempt });
const errorType = error instanceof Error ? error.constructor.name : typeof error;
metrics.increment(`${name}.error_type`, { type: errorType });
logger.error(`[${name}] Attempt ${attempt} failed after ${timeMs}ms:`, error);
// Alert on final failure
if (attempt === maxRetries + 1) {
alerts.send(`${name} failed after ${attempt} attempts`, error);
}
}
}
Implementation location: src/index.ts:155
onRetry
Called when a retry will be attempted after a failure.
onRetry?: (info: { name: string; attempt: number; delayMs: number; error: unknown }) => void
Name of the function being retried.
The attempt number that just failed (the next attempt will be attempt + 1).
The backoff delay in milliseconds before the next retry.
The error that triggered the retry.
When called: After a failed attempt when a retry will occur, before the backoff delay.
Use cases:
- Log retry decisions
- Track retry frequency
- Monitor backoff delays
- Debug retry behavior
Example:
hooks: {
onRetry: ({ name, attempt, delayMs, error }) => {
metrics.increment(`${name}.retry`, { attempt });
metrics.recordValue(`${name}.retry_delay`, delayMs);
logger.warn(
`[${name}] Retrying after attempt ${attempt} ` +
`(waiting ${delayMs}ms) due to:`,
error
);
}
}
Implementation location: src/index.ts:161
onCircuitOpen
Called when the circuit breaker opens due to excessive failures.
onCircuitOpen?: (info: { name: string }) => void
Name of the function whose circuit opened.
When called: When failure count reaches circuitBreaker.failureThreshold.
Use cases:
- Send critical alerts
- Trigger fallback mechanisms
- Update service health status
- Log circuit breaker events
Example:
hooks: {
onCircuitOpen: ({ name }) => {
metrics.increment(`${name}.circuit_open`);
logger.error(`[${name}] Circuit breaker OPENED - all requests will be rejected`);
alerts.send({
severity: 'critical',
message: `Circuit breaker opened for ${name}`,
action: 'All requests will be rejected until reset timeout'
});
// Update service health
healthCheck.markDegraded(name);
}
}
Implementation location: src/index.ts:64
onCircuitHalfOpen
Called when the circuit breaker enters half-open state for testing.
onCircuitHalfOpen?: (info: { name: string }) => void
Name of the function whose circuit is being tested.
When called: After circuitBreaker.resetTimeoutMs has elapsed since circuit opened.
Use cases:
- Log circuit breaker state changes
- Track recovery attempts
- Monitor circuit health
Example:
hooks: {
onCircuitHalfOpen: ({ name }) => {
metrics.increment(`${name}.circuit_half_open`);
logger.info(`[${name}] Circuit breaker HALF-OPEN - testing with probe request`);
}
}
Implementation location: src/index.ts:45
onCircuitClosed
Called when the circuit breaker closes after successful recovery.
onCircuitClosed?: (info: { name: string }) => void
Name of the function whose circuit closed.
When called:
- After a successful execution when circuit was in HALF_OPEN state
- When transitioning from any non-CLOSED state to CLOSED
Use cases:
- Send recovery notifications
- Log circuit breaker recovery
- Update service health status
- Clear alerts
Example:
hooks: {
onCircuitClosed: ({ name }) => {
metrics.increment(`${name}.circuit_closed`);
logger.info(`[${name}] Circuit breaker CLOSED - normal operation resumed`);
alerts.resolve({
message: `Circuit breaker recovered for ${name}`,
action: 'Normal operation resumed'
});
// Update service health
healthCheck.markHealthy(name);
}
}
Implementation location: src/index.ts:54
Hook Execution Flow
For a function with 3 retries and circuit breaker:
┌─────────────────────────────────────────────────────────────┐
│ Start Execution │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────┐
│ onAttempt(1)│
└─────────────┘
│
▼
┌─────────────┐
│ Execute │
└─────────────┘
/ \
/ \
Success Failure
│ │
▼ ▼
┌──────────┐ ┌──────────────┐
│onSuccess │ │ onFailure(1)│
└──────────┘ └──────────────┘
│ │
│ ▼
│ ┌──────────────┐
│ │ onRetry(1) │
│ └──────────────┘
│ │
│ ▼
│ (backoff delay)
│ │
│ ▼
│ ┌─────────────┐
│ │ onAttempt(2)│
│ └─────────────┘
│ │
│ (repeat)
│ │
│ After N failures:
│ │
│ ▼
│ ┌──────────────┐
│ │onCircuitOpen │
│ └──────────────┘
│ │
│ (wait resetTimeout)
│ │
│ ▼
│ ┌──────────────────┐
│ │onCircuitHalfOpen │
│ └──────────────────┘
│ │
│ ┌─────────────┐
│ │ onAttempt(N)│
│ └─────────────┘
│ / \
│ Success Failure
│ / \
▼ ▼ ▼
┌──────────────────┐ ┌──────────────┐
│ onCircuitClosed │ │onCircuitOpen │
└──────────────────┘ └──────────────┘
Complete Hooks Example
import { withResilience } from '@oldwhisper/resilience';
import { logger, metrics, alerts } from './monitoring';
const resilientFunction = withResilience(
async (data: any) => {
return await processData(data);
},
{
name: 'data-processor',
retries: 3,
timeoutMs: 5000,
circuitBreaker: {
failureThreshold: 5,
resetTimeoutMs: 60000
},
hooks: {
onAttempt: ({ name, attempt }) => {
metrics.increment(`${name}.attempts`, { attempt });
logger.debug(`[${name}] Attempt ${attempt}`);
},
onSuccess: ({ name, attempt, timeMs }) => {
metrics.recordTiming(`${name}.duration`, timeMs);
metrics.increment(`${name}.success`, { attempt });
if (attempt > 1) {
logger.info(
`[${name}] Succeeded on attempt ${attempt} after ${timeMs}ms`
);
}
},
onFailure: ({ name, attempt, timeMs, error }) => {
metrics.increment(`${name}.failure`, { attempt });
const errorType = error instanceof Error
? error.constructor.name
: typeof error;
metrics.increment(`${name}.error_type.${errorType}`);
logger.error(
`[${name}] Attempt ${attempt} failed after ${timeMs}ms:`,
error
);
},
onRetry: ({ name, attempt, delayMs, error }) => {
metrics.increment(`${name}.retry`, { attempt });
metrics.recordValue(`${name}.retry_delay`, delayMs);
logger.warn(
`[${name}] Retrying after attempt ${attempt} ` +
`(waiting ${delayMs}ms) due to:`,
error
);
},
onCircuitOpen: ({ name }) => {
metrics.increment(`${name}.circuit_open`);
logger.error(
`[${name}] Circuit breaker OPENED - ` +
`rejecting all requests`
);
alerts.send({
severity: 'critical',
title: `Circuit breaker opened: ${name}`,
message: 'All requests will be rejected until recovery'
});
},
onCircuitHalfOpen: ({ name }) => {
metrics.increment(`${name}.circuit_half_open`);
logger.info(
`[${name}] Circuit breaker HALF-OPEN - ` +
`testing recovery`
);
},
onCircuitClosed: ({ name }) => {
metrics.increment(`${name}.circuit_closed`);
logger.info(
`[${name}] Circuit breaker CLOSED - ` +
`normal operation resumed`
);
alerts.resolve({
title: `Circuit breaker recovered: ${name}`,
message: 'Normal operation resumed'
});
}
}
}
);
Common Patterns
Metrics Collection
const metricsHooks = {
onAttempt: ({ name }) => {
metrics.increment(`${name}.attempt`);
},
onSuccess: ({ name, timeMs }) => {
metrics.recordTiming(`${name}.duration`, timeMs);
metrics.increment(`${name}.success`);
},
onFailure: ({ name }) => {
metrics.increment(`${name}.failure`);
}
};
Structured Logging
const loggingHooks = {
onAttempt: ({ name, attempt }) => {
logger.debug({ fn: name, attempt }, 'Function attempt');
},
onSuccess: ({ name, attempt, timeMs }) => {
logger.info({ fn: name, attempt, timeMs }, 'Function succeeded');
},
onFailure: ({ name, attempt, error }) => {
logger.error({ fn: name, attempt, err: error }, 'Function failed');
}
};
Alerting on Critical Failures
const alertingHooks = {
onCircuitOpen: ({ name }) => {
pagerDuty.trigger({
severity: 'critical',
summary: `Circuit breaker opened for ${name}`
});
},
onCircuitClosed: ({ name }) => {
pagerDuty.resolve({ summary: `Circuit recovered for ${name}` });
}
};
Tracing and Observability
const tracingHooks = {
onAttempt: ({ name, attempt }) => {
const span = tracer.startSpan(`${name}.attempt.${attempt}`);
span.setAttribute('attempt', attempt);
},
onSuccess: ({ name, timeMs }) => {
tracer.getCurrentSpan()?.end();
},
onFailure: ({ name, error }) => {
const span = tracer.getCurrentSpan();
span?.recordException(error);
span?.end();
}
};