Effect Observability And Time
Use this skill for time-based behavior, retries, logs, spans, metrics, and runtime config.
Time And Retry
Use Effect primitives instead of ad hoc timers and raw millisecond math.
import { Duration, Effect, Schedule } from "effect"
const guarded = operation.pipe(Effect.timeout(Duration.seconds(5)))
const retryPolicy = Schedule.exponential("200 millis").pipe(
Schedule.jittered,
)
Rules:
- Use
Clockwhen code needs current time from the Effect environment. - Use
Durationhelpers for time units when clarity matters. - Use
Effect.sleepfor delays, notsetTimeoutwrapped by hand. - Use
Effect.timeoutaround external calls that can hang. - Use
Schedulefor retry; gate retry by failure shape when only some errors are retryable. - Public retry fields should usually accept
Schedule.Schedule<unknown>because schedule input is contravariant.
Logging, Tracing, Metrics
- Use
Effect.logInfo,Effect.logDebug,Effect.logError, andEffect.annotateLogs. - Use
Effect.withSpan("name", { attributes })around meaningful runtime boundaries. - Attach stable IDs and small primitive attributes to logs/spans; avoid dumping large payloads.
- Use
Metric.counter,Metric.gauge, orMetric.histogramwhen a value is operationally useful across runs, not just useful for local debugging. - Avoid
console.login Effect runtime code.
Configuration And Secrets
- Use Effect
Configfor runtime configuration when code needs typed environment values. - Do not read
process.envthroughout domain code. - Read config once through a service/layer, then inject the parsed values.
- Keep secrets redacted in logs and errors.
- Use
ConfigProviderin tests or alternate runtimes when config should come from a map/object rather than the process environment. - Use
Redactedfor secrets and unwrap only at the final foreign boundary that needs the raw value.
Checks
- Does this external call have timeout or cancellation behavior?
- Are retryable and non-retryable failures separated?
- Would this log leak secrets or huge payloads?
- Does the span name describe an operation rather than an implementation detail?
- Is config read at a boundary and injected afterward?