Observability
This skill should be used when the user asks about "Effect logging", "Effect.log", "Effect metrics", "Effect tracing", "spans", "telemetry", "Metric.counter", "Metric.gauge", "Metric.histogram", "OpenTelemetry", "structured logging", "log levels", "Effect.logDebug", "Effect.logInfo", "Effect.logW...
SKILL.md
Full skill instructions
Observability in Effect
Overview
Effect provides built-in observability:
- Logging - Structured, leveled logging
- Metrics - Counters, gauges, histograms
- Tracing - Distributed tracing with spans
All three integrate seamlessly with Effect's execution model.
Logging
Basic Logging
import { Effect } from "effect"
const program = Effect.gen(function* () {
yield* Effect.log("Starting process")
yield* Effect.logDebug("Debug information")
yield* Effect.logInfo("Processing item")
yield* Effect.logWarning("Resource running low")
yield* Effect.logError("Failed to connect")
yield* Effect.logFatal("Critical system failure")
})
Log Levels
import { LogLevel, Logger } from "effect"
// Set minimum log level
const filtered = program.pipe(
Logger.withMinimumLogLevel(LogLevel.Info)
)
// Available levels (lowest to highest):
// Trace, Debug, Info, Warning, Error, Fatal, None
Structured Logging
// Log with structured data
yield* Effect.log("User action").pipe(
Effect.annotateLogs({
userId: "123",
action: "login",
ip: "192.168.1.1"
})
)
// Annotations apply to all logs in scope
const program = Effect.gen(function* () {
yield* Effect.log("First log") // Has userId annotation
yield* Effect.log("Second log") // Has userId annotation
}).pipe(
Effect.annotateLogs({ userId: "123" })
)
Log Spans
// Add timing/context spans
const program = Effect.gen(function* () {
yield* Effect.log("Processing")
yield* processItems()
yield* Effect.log("Complete")
}).pipe(
Effect.withLogSpan("request-handler")
)
// Logs include: [request-handler 45ms] Processing
Custom Logger
import { Logger } from "effect"
const JsonLogger = Logger.make(({ logLevel, message, annotations, date }) => {
console.log(JSON.stringify({
level: logLevel.label,
message: String(message),
timestamp: date.toISOString(),
...annotations
}))
})
const program = Effect.gen(function* () {
yield* Effect.log("Hello")
}).pipe(
Effect.provide(Logger.replace(Logger.defaultLogger, JsonLogger))
)
Metrics
Counter - Track Occurrences
import { Metric } from "effect"
// Define counter
const requestCount = Metric.counter("http_requests_total", {
description: "Total HTTP requests"
})
// Increment
const program = Effect.gen(function* () {
yield* Metric.increment(requestCount)
// or increment by value
yield* Metric.incrementBy(requestCount, 5)
})
// Track effect executions
const tracked = handleRequest.pipe(
Metric.trackAll(requestCount)
)
Gauge - Track Current Value
const activeConnections = Metric.gauge("active_connections", {
description: "Current active connections"
})
const program = Effect.gen(function* () {
yield* Metric.set(activeConnections, 10)
yield* Metric.incrementBy(activeConnections, 1)
yield* Metric.decrementBy(activeConnections, 1)
})
Histogram - Track Distributions
const requestDuration = Metric.histogram("http_request_duration_ms", {
description: "Request duration in milliseconds",
boundaries: [10, 50, 100, 250, 500, 1000]
})
// Record values
yield* Metric.observe(requestDuration, 125)
// Track effect duration automatically
const tracked = handleRequest.pipe(
Metric.trackDuration(requestDuration)
)
Summary - Statistical Summary
const responseSizes = Metric.summary("response_size_bytes", {
description: "Response payload sizes",
maxAge: "1 minute",
maxSize: 100,
quantiles: [0.5, 0.9, 0.99]
})
yield* Metric.observe(responseSizes, 1024)
Frequency - Count by Tag
const statusCodes = Metric.frequency("http_status_codes")
yield* Metric.observe(statusCodes, "200")
yield* Metric.observe(statusCodes, "404")
yield* Metric.observe(statusCodes, "500")
Tagged Metrics
const requestCount = Metric.counter("requests").pipe(
Metric.tagged("service", "api"),
Metric.tagged("version", "v1")
)
// Dynamic tags
const taggedCount = Metric.counter("requests").pipe(
Metric.taggedWithLabels(["method", "endpoint"])
)
yield* Metric.increment(taggedCount).pipe(
Metric.taggedWithLabels(["GET", "/users"])
)
Reading Metrics
const program = Effect.gen(function* () {
yield* Metric.increment(requestCount)
yield* Metric.increment(requestCount)
const snapshot = yield* Metric.value(requestCount)
// snapshot.count === 2
})
Tracing
Creating Spans
import { Effect } from "effect"
// Wrap effect in span
const traced = handleRequest.pipe(
Effect.withSpan("handle-request")
)
// With attributes
const traced = handleRequest.pipe(
Effect.withSpan("handle-request", {
attributes: {
"http.method": "GET",
"http.url": "/api/users"
}
})
)
Nested Spans
const program = Effect.gen(function* () {
yield* fetchUser(id).pipe(Effect.withSpan("fetch-user"))
yield* processData(data).pipe(Effect.withSpan("process-data"))
yield* saveResult(result).pipe(Effect.withSpan("save-result"))
}).pipe(
Effect.withSpan("main-operation")
)
// Creates: main-operation
// ├── fetch-user
// ├── process-data
// └── save-result
Adding Span Attributes
import { Tracer } from "effect"
const program = Effect.gen(function* () {
// Add attribute to current span
yield* Effect.annotateCurrentSpan("user.id", userId)
// Add event
yield* Effect.annotateCurrentSpan("event", "user_validated")
const result = yield* processUser(userId)
yield* Effect.annotateCurrentSpan("result.status", result.status)
})
Span Status
const program = Effect.gen(function* () {
try {
return yield* riskyOperation
} catch (error) {
yield* Effect.setSpanStatus({
code: "error",
message: error.message
})
return yield* Effect.fail(error)
}
}).pipe(Effect.withSpan("risky-operation"))
Custom Tracer
import { Tracer } from "effect"
const ConsoleTracer = Tracer.make({
span: (name, parent, context, links, startTime) => ({
attribute: (key, value) => console.log(`[${name}] ${key}=${value}`),
end: (endTime, exit) => console.log(`[${name}] ended`),
event: (name, startTime, attributes) => console.log(`[${name}] event: ${name}`),
status: (status) => console.log(`[${name}] status: ${status.code}`)
})
})
const program = myEffect.pipe(
Effect.provide(Tracer.layer(ConsoleTracer))
)
OpenTelemetry Integration
import { NodeSdk } from "@effect/opentelemetry"
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"
const TracingLive = NodeSdk.layer(() => ({
resource: { serviceName: "my-service" },
spanProcessor: new BatchSpanProcessor(new OTLPTraceExporter())
}))
const program = myEffect.pipe(
Effect.provide(TracingLive)
)
Combining Observability
const handleRequest = (req: Request) =>
Effect.gen(function* () {
yield* Effect.log("Request received").pipe(
Effect.annotateLogs({ path: req.path, method: req.method })
)
yield* Metric.increment(requestCount)
const result = yield* processRequest(req)
yield* Effect.annotateCurrentSpan("response.status", result.status)
yield* Metric.observe(requestDuration, result.duration)
return result
}).pipe(
Effect.withSpan("handle-request", {
attributes: {
"http.method": req.method,
"http.url": req.path
}
})
)
Best Practices
- Use structured logging - Add context via annotations
- Name spans descriptively - Use verb-noun format
- Add meaningful attributes - Enable debugging/analysis
- Track key metrics - Request count, latency, errors
- Use appropriate log levels - Debug in dev, Info in prod
Additional Resources
For comprehensive observability documentation, consult ${CLAUDE_PLUGIN_ROOT}/references/llms-full.txt.
Search for these sections:
- "Built-in Logging" for logging APIs
- "Metrics" for metric types
- "Tracing" for distributed tracing
