@dojocho/effect-ts 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/DOJO.md +22 -0
  2. package/dojo.json +50 -0
  3. package/katas/001-hello-effect/SENSEI.md +72 -0
  4. package/katas/001-hello-effect/solution.test.ts +35 -0
  5. package/katas/001-hello-effect/solution.ts +16 -0
  6. package/katas/002-transform-with-map/SENSEI.md +72 -0
  7. package/katas/002-transform-with-map/solution.test.ts +33 -0
  8. package/katas/002-transform-with-map/solution.ts +16 -0
  9. package/katas/003-generator-pipelines/SENSEI.md +72 -0
  10. package/katas/003-generator-pipelines/solution.test.ts +40 -0
  11. package/katas/003-generator-pipelines/solution.ts +29 -0
  12. package/katas/004-flatmap-and-chaining/SENSEI.md +80 -0
  13. package/katas/004-flatmap-and-chaining/solution.test.ts +34 -0
  14. package/katas/004-flatmap-and-chaining/solution.ts +18 -0
  15. package/katas/005-pipe-composition/SENSEI.md +81 -0
  16. package/katas/005-pipe-composition/solution.test.ts +41 -0
  17. package/katas/005-pipe-composition/solution.ts +19 -0
  18. package/katas/006-handle-errors/SENSEI.md +86 -0
  19. package/katas/006-handle-errors/solution.test.ts +53 -0
  20. package/katas/006-handle-errors/solution.ts +30 -0
  21. package/katas/007-tagged-errors/SENSEI.md +79 -0
  22. package/katas/007-tagged-errors/solution.test.ts +82 -0
  23. package/katas/007-tagged-errors/solution.ts +37 -0
  24. package/katas/008-error-patterns/SENSEI.md +89 -0
  25. package/katas/008-error-patterns/solution.test.ts +41 -0
  26. package/katas/008-error-patterns/solution.ts +38 -0
  27. package/katas/009-option-type/SENSEI.md +96 -0
  28. package/katas/009-option-type/solution.test.ts +49 -0
  29. package/katas/009-option-type/solution.ts +26 -0
  30. package/katas/010-either-and-exit/SENSEI.md +86 -0
  31. package/katas/010-either-and-exit/solution.test.ts +33 -0
  32. package/katas/010-either-and-exit/solution.ts +17 -0
  33. package/katas/011-services-and-context/SENSEI.md +82 -0
  34. package/katas/011-services-and-context/solution.test.ts +23 -0
  35. package/katas/011-services-and-context/solution.ts +17 -0
  36. package/katas/012-layers/SENSEI.md +73 -0
  37. package/katas/012-layers/solution.test.ts +23 -0
  38. package/katas/012-layers/solution.ts +26 -0
  39. package/katas/013-testing-effects/SENSEI.md +88 -0
  40. package/katas/013-testing-effects/solution.test.ts +41 -0
  41. package/katas/013-testing-effects/solution.ts +20 -0
  42. package/katas/014-schema-basics/SENSEI.md +81 -0
  43. package/katas/014-schema-basics/solution.test.ts +35 -0
  44. package/katas/014-schema-basics/solution.ts +25 -0
  45. package/katas/015-domain-modeling/SENSEI.md +85 -0
  46. package/katas/015-domain-modeling/solution.test.ts +46 -0
  47. package/katas/015-domain-modeling/solution.ts +42 -0
  48. package/katas/016-retry-and-schedule/SENSEI.md +72 -0
  49. package/katas/016-retry-and-schedule/solution.test.ts +26 -0
  50. package/katas/016-retry-and-schedule/solution.ts +23 -0
  51. package/katas/017-parallel-effects/SENSEI.md +70 -0
  52. package/katas/017-parallel-effects/solution.test.ts +33 -0
  53. package/katas/017-parallel-effects/solution.ts +17 -0
  54. package/katas/018-race-and-timeout/SENSEI.md +75 -0
  55. package/katas/018-race-and-timeout/solution.test.ts +30 -0
  56. package/katas/018-race-and-timeout/solution.ts +27 -0
  57. package/katas/019-ref-and-state/SENSEI.md +72 -0
  58. package/katas/019-ref-and-state/solution.test.ts +29 -0
  59. package/katas/019-ref-and-state/solution.ts +16 -0
  60. package/katas/020-fibers/SENSEI.md +80 -0
  61. package/katas/020-fibers/solution.test.ts +23 -0
  62. package/katas/020-fibers/solution.ts +23 -0
  63. package/katas/021-acquire-release/SENSEI.md +57 -0
  64. package/katas/021-acquire-release/solution.test.ts +23 -0
  65. package/katas/021-acquire-release/solution.ts +22 -0
  66. package/katas/022-scoped-layers/SENSEI.md +52 -0
  67. package/katas/022-scoped-layers/solution.test.ts +35 -0
  68. package/katas/022-scoped-layers/solution.ts +19 -0
  69. package/katas/023-resource-patterns/SENSEI.md +52 -0
  70. package/katas/023-resource-patterns/solution.test.ts +20 -0
  71. package/katas/023-resource-patterns/solution.ts +13 -0
  72. package/katas/024-streams-basics/SENSEI.md +61 -0
  73. package/katas/024-streams-basics/solution.test.ts +30 -0
  74. package/katas/024-streams-basics/solution.ts +16 -0
  75. package/katas/025-stream-operations/SENSEI.md +59 -0
  76. package/katas/025-stream-operations/solution.test.ts +26 -0
  77. package/katas/025-stream-operations/solution.ts +17 -0
  78. package/katas/026-combining-streams/SENSEI.md +54 -0
  79. package/katas/026-combining-streams/solution.test.ts +20 -0
  80. package/katas/026-combining-streams/solution.ts +16 -0
  81. package/katas/027-data-pipelines/SENSEI.md +58 -0
  82. package/katas/027-data-pipelines/solution.test.ts +22 -0
  83. package/katas/027-data-pipelines/solution.ts +16 -0
  84. package/katas/028-logging-and-spans/SENSEI.md +58 -0
  85. package/katas/028-logging-and-spans/solution.test.ts +50 -0
  86. package/katas/028-logging-and-spans/solution.ts +20 -0
  87. package/katas/029-http-client/SENSEI.md +59 -0
  88. package/katas/029-http-client/solution.test.ts +49 -0
  89. package/katas/029-http-client/solution.ts +24 -0
  90. package/katas/030-capstone/SENSEI.md +63 -0
  91. package/katas/030-capstone/solution.test.ts +67 -0
  92. package/katas/030-capstone/solution.ts +55 -0
  93. package/katas/031-config-and-environment/SENSEI.md +77 -0
  94. package/katas/031-config-and-environment/solution.test.ts +38 -0
  95. package/katas/031-config-and-environment/solution.ts +11 -0
  96. package/katas/032-cause-and-defects/SENSEI.md +90 -0
  97. package/katas/032-cause-and-defects/solution.test.ts +50 -0
  98. package/katas/032-cause-and-defects/solution.ts +23 -0
  99. package/katas/033-pattern-matching/SENSEI.md +86 -0
  100. package/katas/033-pattern-matching/solution.test.ts +36 -0
  101. package/katas/033-pattern-matching/solution.ts +28 -0
  102. package/katas/034-deferred-and-coordination/SENSEI.md +85 -0
  103. package/katas/034-deferred-and-coordination/solution.test.ts +25 -0
  104. package/katas/034-deferred-and-coordination/solution.ts +24 -0
  105. package/katas/035-queue-and-backpressure/SENSEI.md +100 -0
  106. package/katas/035-queue-and-backpressure/solution.test.ts +25 -0
  107. package/katas/035-queue-and-backpressure/solution.ts +21 -0
  108. package/katas/036-schema-advanced/SENSEI.md +81 -0
  109. package/katas/036-schema-advanced/solution.test.ts +55 -0
  110. package/katas/036-schema-advanced/solution.ts +19 -0
  111. package/katas/037-cache-and-memoization/SENSEI.md +73 -0
  112. package/katas/037-cache-and-memoization/solution.test.ts +47 -0
  113. package/katas/037-cache-and-memoization/solution.ts +24 -0
  114. package/katas/038-metrics/SENSEI.md +91 -0
  115. package/katas/038-metrics/solution.test.ts +39 -0
  116. package/katas/038-metrics/solution.ts +23 -0
  117. package/katas/039-managed-runtime/SENSEI.md +75 -0
  118. package/katas/039-managed-runtime/solution.test.ts +29 -0
  119. package/katas/039-managed-runtime/solution.ts +19 -0
  120. package/katas/040-request-batching/SENSEI.md +87 -0
  121. package/katas/040-request-batching/solution.test.ts +56 -0
  122. package/katas/040-request-batching/solution.ts +32 -0
  123. package/package.json +22 -0
  124. package/skills/effect-patterns-building-apis/SKILL.md +2393 -0
  125. package/skills/effect-patterns-building-data-pipelines/SKILL.md +1876 -0
  126. package/skills/effect-patterns-concurrency/SKILL.md +2999 -0
  127. package/skills/effect-patterns-concurrency-getting-started/SKILL.md +351 -0
  128. package/skills/effect-patterns-core-concepts/SKILL.md +3199 -0
  129. package/skills/effect-patterns-domain-modeling/SKILL.md +1385 -0
  130. package/skills/effect-patterns-error-handling/SKILL.md +1212 -0
  131. package/skills/effect-patterns-error-handling-resilience/SKILL.md +179 -0
  132. package/skills/effect-patterns-error-management/SKILL.md +1668 -0
  133. package/skills/effect-patterns-getting-started/SKILL.md +237 -0
  134. package/skills/effect-patterns-making-http-requests/SKILL.md +1756 -0
  135. package/skills/effect-patterns-observability/SKILL.md +1586 -0
  136. package/skills/effect-patterns-platform/SKILL.md +1195 -0
  137. package/skills/effect-patterns-platform-getting-started/SKILL.md +179 -0
  138. package/skills/effect-patterns-project-setup--execution/SKILL.md +233 -0
  139. package/skills/effect-patterns-resource-management/SKILL.md +827 -0
  140. package/skills/effect-patterns-scheduling/SKILL.md +451 -0
  141. package/skills/effect-patterns-scheduling-periodic-tasks/SKILL.md +763 -0
  142. package/skills/effect-patterns-streams/SKILL.md +2052 -0
  143. package/skills/effect-patterns-streams-getting-started/SKILL.md +421 -0
  144. package/skills/effect-patterns-streams-sinks/SKILL.md +1181 -0
  145. package/skills/effect-patterns-testing/SKILL.md +1632 -0
  146. package/skills/effect-patterns-tooling-and-debugging/SKILL.md +1125 -0
  147. package/skills/effect-patterns-value-handling/SKILL.md +676 -0
  148. package/tsconfig.json +20 -0
  149. package/vitest.config.ts +3 -0
@@ -0,0 +1,1586 @@
1
+ ---
2
+ name: effect-patterns-observability
3
+ description: Effect-TS patterns for Observability. Use when working with observability in Effect-TS applications.
4
+ ---
5
+ # Effect-TS Patterns: Observability
6
+ This skill provides 13 curated Effect-TS patterns for observability.
7
+ Use this skill when working on tasks related to:
8
+ - observability
9
+ - Best practices in Effect-TS applications
10
+ - Real-world patterns and solutions
11
+
12
+ ---
13
+
14
+ ## 🟢 Beginner Patterns
15
+
16
+ ### Debug Effect Programs
17
+
18
+ **Rule:** Use Effect.tap and logging to inspect values without changing program flow.
19
+
20
+ **Good Example:**
21
+
22
+ ```typescript
23
+ import { Effect, pipe } from "effect"
24
+
25
+ // ============================================
26
+ // 1. Using tap to inspect values
27
+ // ============================================
28
+
29
+ const fetchUser = (id: string) =>
30
+ Effect.succeed({ id, name: "Alice", email: "alice@example.com" })
31
+
32
+ const processUser = (id: string) =>
33
+ fetchUser(id).pipe(
34
+ // tap runs an effect for its side effect, then continues with original value
35
+ Effect.tap((user) => Effect.log(`Fetched user: ${user.name}`)),
36
+ Effect.map((user) => ({ ...user, processed: true })),
37
+ Effect.tap((user) => Effect.log(`Processed: ${JSON.stringify(user)}`))
38
+ )
39
+
40
+ // ============================================
41
+ // 2. Debug a pipeline
42
+ // ============================================
43
+
44
+ const numbers = [1, 2, 3, 4, 5]
45
+
46
+ const pipeline = Effect.gen(function* () {
47
+ yield* Effect.log("Starting pipeline")
48
+
49
+ const step1 = numbers.filter((n) => n % 2 === 0)
50
+ yield* Effect.log(`After filter (even): ${JSON.stringify(step1)}`)
51
+
52
+ const step2 = step1.map((n) => n * 10)
53
+ yield* Effect.log(`After map (*10): ${JSON.stringify(step2)}`)
54
+
55
+ const step3 = step2.reduce((a, b) => a + b, 0)
56
+ yield* Effect.log(`After reduce (sum): ${step3}`)
57
+
58
+ return step3
59
+ })
60
+
61
+ // ============================================
62
+ // 3. Debug errors
63
+ // ============================================
64
+
65
+ const riskyOperation = (shouldFail: boolean) =>
66
+ Effect.gen(function* () {
67
+ yield* Effect.log("Starting risky operation")
68
+
69
+ if (shouldFail) {
70
+ yield* Effect.log("About to fail...")
71
+ return yield* Effect.fail(new Error("Something went wrong"))
72
+ }
73
+
74
+ yield* Effect.log("Success!")
75
+ return "result"
76
+ })
77
+
78
+ const debugErrors = riskyOperation(true).pipe(
79
+ // Log when operation fails
80
+ Effect.tapError((error) => Effect.log(`Operation failed: ${error.message}`)),
81
+
82
+ // Provide a fallback
83
+ Effect.catchAll((error) => {
84
+ return Effect.succeed(`Recovered from: ${error.message}`)
85
+ })
86
+ )
87
+
88
+ // ============================================
89
+ // 4. Trace execution flow
90
+ // ============================================
91
+
92
+ const step = (name: string, value: number) =>
93
+ Effect.gen(function* () {
94
+ yield* Effect.log(`[${name}] Input: ${value}`)
95
+ const result = value * 2
96
+ yield* Effect.log(`[${name}] Output: ${result}`)
97
+ return result
98
+ })
99
+
100
+ const tracedWorkflow = Effect.gen(function* () {
101
+ const a = yield* step("Step 1", 5)
102
+ const b = yield* step("Step 2", a)
103
+ const c = yield* step("Step 3", b)
104
+ yield* Effect.log(`Final result: ${c}`)
105
+ return c
106
+ })
107
+
108
+ // ============================================
109
+ // 5. Quick debug with console
110
+ // ============================================
111
+
112
+ // Sometimes you just need console.log
113
+ const quickDebug = Effect.gen(function* () {
114
+ const value = yield* Effect.succeed(42)
115
+
116
+ // Effect.sync wraps side effects
117
+ yield* Effect.sync(() => console.log("Quick debug:", value))
118
+
119
+ return value
120
+ })
121
+
122
+ // ============================================
123
+ // 6. Run examples
124
+ // ============================================
125
+
126
+ const program = Effect.gen(function* () {
127
+ yield* Effect.log("=== Tap Example ===")
128
+ yield* processUser("123")
129
+
130
+ yield* Effect.log("\n=== Pipeline Debug ===")
131
+ yield* pipeline
132
+
133
+ yield* Effect.log("\n=== Error Debug ===")
134
+ yield* debugErrors
135
+
136
+ yield* Effect.log("\n=== Traced Workflow ===")
137
+ yield* tracedWorkflow
138
+ })
139
+
140
+ Effect.runPromise(program)
141
+ ```
142
+
143
+ **Rationale:**
144
+
145
+ Use `Effect.tap` to inspect values and `Effect.log` to trace execution without changing program behavior.
146
+
147
+ ---
148
+
149
+
150
+ Debugging Effect code differs from imperative code:
151
+
152
+ 1. **No breakpoints** - Effects are descriptions, not executions
153
+ 2. **Lazy evaluation** - Code runs later when you call `runPromise`
154
+ 3. **Composition** - Effects chain together
155
+
156
+ `tap` and logging let you see inside without breaking the chain.
157
+
158
+ ---
159
+
160
+ ---
161
+
162
+ ### Your First Logs
163
+
164
+ **Rule:** Use Effect.log and related functions for structured, contextual logging.
165
+
166
+ **Good Example:**
167
+
168
+ ```typescript
169
+ import { Effect, Logger, LogLevel } from "effect"
170
+
171
+ // ============================================
172
+ // 1. Basic logging
173
+ // ============================================
174
+
175
+ const basicLogging = Effect.gen(function* () {
176
+ // Different log levels
177
+ yield* Effect.logDebug("Debug message - for development")
178
+ yield* Effect.logInfo("Info message - normal operation")
179
+ yield* Effect.log("Default log - same as logInfo")
180
+ yield* Effect.logWarning("Warning - something unusual")
181
+ yield* Effect.logError("Error - something went wrong")
182
+ })
183
+
184
+ // ============================================
185
+ // 2. Logging with context
186
+ // ============================================
187
+
188
+ const withContext = Effect.gen(function* () {
189
+ // Add structured data to logs
190
+ yield* Effect.log("User logged in").pipe(
191
+ Effect.annotateLogs({
192
+ userId: "user-123",
193
+ action: "login",
194
+ ipAddress: "192.168.1.1",
195
+ })
196
+ )
197
+
198
+ // Add a single annotation
199
+ yield* Effect.log("Processing request").pipe(
200
+ Effect.annotateLogs("requestId", "req-456")
201
+ )
202
+ })
203
+
204
+ // ============================================
205
+ // 3. Log spans for timing
206
+ // ============================================
207
+
208
+ const withTiming = Effect.gen(function* () {
209
+ yield* Effect.log("Starting operation")
210
+
211
+ // withLogSpan adds timing information
212
+ yield* Effect.sleep("100 millis").pipe(
213
+ Effect.withLogSpan("database-query")
214
+ )
215
+
216
+ yield* Effect.log("Operation complete")
217
+ })
218
+
219
+ // ============================================
220
+ // 4. Practical example
221
+ // ============================================
222
+
223
+ interface User {
224
+ id: string
225
+ email: string
226
+ }
227
+
228
+ const processOrder = (orderId: string, userId: string) =>
229
+ Effect.gen(function* () {
230
+ yield* Effect.logInfo("Processing order").pipe(
231
+ Effect.annotateLogs({ orderId, userId })
232
+ )
233
+
234
+ // Simulate work
235
+ yield* Effect.sleep("50 millis")
236
+
237
+ yield* Effect.logInfo("Order processed successfully").pipe(
238
+ Effect.annotateLogs({ orderId, status: "completed" })
239
+ )
240
+
241
+ return { orderId, status: "completed" }
242
+ }).pipe(
243
+ Effect.withLogSpan("processOrder")
244
+ )
245
+
246
+ // ============================================
247
+ // 5. Configure log level
248
+ // ============================================
249
+
250
+ const debugProgram = basicLogging.pipe(
251
+ // Show all logs including debug
252
+ Logger.withMinimumLogLevel(LogLevel.Debug)
253
+ )
254
+
255
+ const productionProgram = basicLogging.pipe(
256
+ // Only show warnings and errors
257
+ Logger.withMinimumLogLevel(LogLevel.Warning)
258
+ )
259
+
260
+ // ============================================
261
+ // 6. Run
262
+ // ============================================
263
+
264
+ const program = Effect.gen(function* () {
265
+ yield* Effect.log("=== Basic Logging ===")
266
+ yield* basicLogging
267
+
268
+ yield* Effect.log("\n=== With Context ===")
269
+ yield* withContext
270
+
271
+ yield* Effect.log("\n=== With Timing ===")
272
+ yield* withTiming
273
+
274
+ yield* Effect.log("\n=== Process Order ===")
275
+ yield* processOrder("order-789", "user-123")
276
+ })
277
+
278
+ Effect.runPromise(program)
279
+ ```
280
+
281
+ **Rationale:**
282
+
283
+ Use Effect's built-in logging functions for structured, contextual logging that works with any logging backend.
284
+
285
+ ---
286
+
287
+
288
+ Effect's logging is superior to `console.log`:
289
+
290
+ 1. **Structured** - Logs are data, not just strings
291
+ 2. **Contextual** - Automatically includes fiber info, timestamps
292
+ 3. **Configurable** - Change log levels, formats, destinations
293
+ 4. **Type-safe** - Part of the Effect type system
294
+
295
+ ---
296
+
297
+ ---
298
+
299
+
300
+ ## 🟡 Intermediate Patterns
301
+
302
+ ### Instrument and Observe Function Calls with Effect.fn
303
+
304
+ **Rule:** Use Effect.fn to wrap functions with effectful instrumentation, such as logging, metrics, or tracing, in a composable and type-safe way.
305
+
306
+ **Good Example:**
307
+
308
+ ```typescript
309
+ import { Effect } from "effect";
310
+
311
+ // A simple function to instrument
312
+ function add(a: number, b: number): number {
313
+ return a + b;
314
+ }
315
+
316
+ // Use Effect.fn to instrument the function with observability
317
+ const addWithLogging = Effect.fn("add")(add).pipe(
318
+ Effect.withSpan("add", { attributes: { "fn.name": "add" } })
319
+ );
320
+
321
+ // Use the instrumented function in an Effect workflow
322
+ const program = Effect.gen(function* () {
323
+ yield* Effect.logInfo("Calling add function");
324
+ const sum = yield* addWithLogging(2, 3);
325
+ yield* Effect.logInfo(`Sum is ${sum}`);
326
+ return sum;
327
+ });
328
+
329
+ // Run the program
330
+ Effect.runPromise(program);
331
+ ```
332
+
333
+ **Explanation:**
334
+
335
+ - `Effect.fn("name")(fn)` wraps a function with instrumentation capabilities, enabling observability.
336
+ - You can add tracing spans, logging, metrics, and other observability logic to function boundaries.
337
+ - Keeps instrumentation separate from business logic and fully composable.
338
+ - The wrapped function integrates seamlessly with Effect's observability and tracing infrastructure.
339
+
340
+ **Anti-Pattern:**
341
+
342
+ Scattering logging, metrics, or tracing logic directly inside business functions, making code harder to test, maintain, and compose.
343
+
344
+ **Rationale:**
345
+
346
+ Use `Effect.fn` to wrap and instrument function calls with effectful logic, such as logging, metrics, or tracing.
347
+ This enables you to observe, monitor, and debug function boundaries in a composable, type-safe way.
348
+
349
+
350
+ Instrumenting function calls is essential for observability, especially in complex or critical code paths.
351
+ `Effect.fn` lets you add effectful logic (logging, metrics, tracing, etc.) before, after, or around any function call, without changing the function’s core logic.
352
+
353
+ ---
354
+
355
+ ### Leverage Effect's Built-in Structured Logging
356
+
357
+ **Rule:** Use Effect.log, Effect.logInfo, and Effect.logError to add structured, context-aware logging to your Effect code.
358
+
359
+ **Good Example:**
360
+
361
+ ```typescript
362
+ import { Effect } from "effect";
363
+
364
+ // Log a simple message
365
+ const program = Effect.gen(function* () {
366
+ yield* Effect.log("Starting the application");
367
+ });
368
+
369
+ // Log at different levels
370
+ const infoProgram = Effect.gen(function* () {
371
+ yield* Effect.logInfo("User signed in");
372
+ });
373
+
374
+ const errorProgram = Effect.gen(function* () {
375
+ yield* Effect.logError("Failed to connect to database");
376
+ });
377
+
378
+ // Log with dynamic values
379
+ const userId = 42;
380
+ const logUserProgram = Effect.gen(function* () {
381
+ yield* Effect.logInfo(`Processing user: ${userId}`);
382
+ });
383
+
384
+ // Use logging in a workflow
385
+ const workflow = Effect.gen(function* () {
386
+ yield* Effect.log("Beginning workflow");
387
+ // ... do some work
388
+ yield* Effect.logInfo("Workflow step completed");
389
+ // ... handle errors
390
+ yield* Effect.logError("Something went wrong");
391
+ });
392
+ ```
393
+
394
+ **Explanation:**
395
+
396
+ - `Effect.log` logs a message at the default level.
397
+ - `Effect.logInfo` and `Effect.logError` log at specific levels.
398
+ - Logging is context-aware and can be used anywhere in your Effect workflows.
399
+
400
+ **Anti-Pattern:**
401
+
402
+ Using `console.log` or ad-hoc logging scattered throughout your code, which is not structured, not context-aware, and harder to manage in production.
403
+
404
+ **Rationale:**
405
+
406
+ Use `Effect.log`, `Effect.logInfo`, `Effect.logError`, and related functions to add structured, context-aware logging to your Effect code.
407
+ This enables you to capture important events, errors, and business information in a consistent and configurable way.
408
+
409
+
410
+ Structured logging makes it easier to search, filter, and analyze logs in production.
411
+ Effect’s logging functions are context-aware, meaning they automatically include relevant metadata and can be configured globally.
412
+
413
+ ---
414
+
415
+ ### Add Custom Metrics to Your Application
416
+
417
+ **Rule:** Use Metric.counter, Metric.gauge, and Metric.histogram to instrument code for monitoring.
418
+
419
+ **Good Example:**
420
+
421
+ This example creates a counter to track how many times a user is created and a histogram to track the duration of the database operation.
422
+
423
+ ```typescript
424
+ import { Effect, Metric, Duration } from "effect"; // We don't need MetricBoundaries anymore
425
+
426
+ // 1. Define your metrics
427
+ const userRegisteredCounter = Metric.counter("users_registered_total", {
428
+ description: "A counter for how many users have been registered.",
429
+ });
430
+
431
+ const dbDurationTimer = Metric.timer(
432
+ "db_operation_duration",
433
+ "A timer for DB operation durations"
434
+ );
435
+
436
+ // 2. Simulated database call
437
+ const saveUserToDb = Effect.succeed("user saved").pipe(
438
+ Effect.delay(Duration.millis(Math.random() * 100))
439
+ );
440
+
441
+ // 3. Instrument the business logic
442
+ const createUser = Effect.gen(function* () {
443
+ // Time the operation
444
+ yield* saveUserToDb.pipe(Metric.trackDuration(dbDurationTimer));
445
+
446
+ // Increment the counter
447
+ yield* Metric.increment(userRegisteredCounter);
448
+
449
+ return { status: "success" };
450
+ });
451
+
452
+ // Run the Effect
453
+ const programWithLogging = Effect.gen(function* () {
454
+ const result = yield* createUser;
455
+ yield* Effect.log(`Result: ${JSON.stringify(result)}`);
456
+ return result;
457
+ });
458
+
459
+ Effect.runPromise(programWithLogging);
460
+ ```
461
+
462
+ ---
463
+
464
+ **Anti-Pattern:**
465
+
466
+ Not adding any metrics to your application. Without metrics, you are flying blind. You have no high-level overview of your application's health, performance, or business KPIs. You can't build dashboards, you can't set up alerts for abnormal behavior (e.g., "error rate is too high"), and you are forced to rely on digging through logs to
467
+ understand the state of your system.
468
+
469
+ **Rationale:**
470
+
471
+ To monitor the health and performance of your application, instrument your code with `Metric`s. The three main types are:
472
+
473
+ - **`Metric.counter("name")`**: To count occurrences of an event (e.g., `users_registered_total`). It only goes up.
474
+ - **`Metric.gauge("name")`**: To track a value that can go up or down (e.g., `active_connections`).
475
+ - **`Metric.histogram("name")`**: To track the distribution of a value (e.g., `request_duration_seconds`).
476
+
477
+ ---
478
+
479
+
480
+ While logs are for events and traces are for requests, metrics are for aggregation. They provide a high-level, numerical view of your system's health over time, which is perfect for building dashboards and setting up alerts.
481
+
482
+ Effect's `Metric` module provides a simple, declarative way to add this instrumentation. By defining your metrics upfront, you can then use operators like `Metric.increment` or `Effect.timed` to update them. This is fully integrated with Effect's context system, allowing you to provide different metric backends (like Prometheus or StatsD) via a `Layer`.
483
+
484
+ This allows you to answer questions like:
485
+
486
+ - "What is our user sign-up rate over the last 24 hours?"
487
+ - "Are we approaching our maximum number of database connections?"
488
+ - "What is the 95th percentile latency for our API requests?"
489
+
490
+ ---
491
+
492
+ ---
493
+
494
+ ### Add Custom Metrics to Your Application
495
+
496
+ **Rule:** Use Effect's Metric module to define and update custom metrics for business and performance monitoring.
497
+
498
+ **Good Example:**
499
+
500
+ ```typescript
501
+ import { Effect, Metric } from "effect";
502
+
503
+ // Define a counter metric for processed jobs
504
+ const jobsProcessed = Metric.counter("jobs_processed");
505
+
506
+ // Increment the counter when a job is processed
507
+ const processJob = Effect.gen(function* () {
508
+ // ... process the job
509
+ yield* Effect.log("Job processed");
510
+ yield* Metric.increment(jobsProcessed);
511
+ });
512
+
513
+ // Define a gauge for current active users
514
+ const activeUsers = Metric.gauge("active_users");
515
+
516
+ // Update the gauge when users sign in or out
517
+ const userSignedIn = Metric.set(activeUsers, 1); // Set to 1 (simplified example)
518
+ const userSignedOut = Metric.set(activeUsers, 0); // Set to 0 (simplified example)
519
+
520
+ // Define a histogram for request durations
521
+ const requestDuration = Metric.histogram("request_duration", [
522
+ 0.1, 0.5, 1, 2, 5,
523
+ ] as any); // boundaries in seconds
524
+
525
+ // Record a request duration
526
+ const recordDuration = (duration: number) =>
527
+ Metric.update(requestDuration, duration);
528
+ ```
529
+
530
+ **Explanation:**
531
+
532
+ - `Metric.counter` tracks counts of events.
533
+ - `Metric.gauge` tracks a value that can go up or down (e.g., active users).
534
+ - `Metric.histogram` tracks distributions (e.g., request durations).
535
+ - `Effect.updateMetric` updates the metric in your workflow.
536
+
537
+ **Anti-Pattern:**
538
+
539
+ Relying solely on logs for monitoring, or using ad-hoc counters and variables that are not integrated with your observability stack.
540
+
541
+ **Rationale:**
542
+
543
+ Use Effect's `Metric` module to define and update custom metrics such as counters, gauges, and histograms.
544
+ This allows you to track business events, performance indicators, and system health in a type-safe and composable way.
545
+
546
+
547
+ Metrics provide quantitative insight into your application's behavior and performance.
548
+ By instrumenting your code with metrics, you can monitor key events, detect anomalies, and drive business decisions.
549
+
550
+ ---
551
+
552
+ ### Trace Operations Across Services with Spans
553
+
554
+ **Rule:** Use Effect.withSpan to create and annotate tracing spans for operations, enabling distributed tracing and performance analysis.
555
+
556
+ **Good Example:**
557
+
558
+ ```typescript
559
+ import { Effect } from "effect";
560
+
561
+ // Trace a database query with a custom span
562
+ const fetchUser = Effect.sync(() => {
563
+ // ...fetch user from database
564
+ return { id: 1, name: "Alice" };
565
+ }).pipe(Effect.withSpan("db.fetchUser"));
566
+
567
+ // Trace an HTTP request with additional attributes
568
+ const fetchData = Effect.tryPromise({
569
+ try: () => fetch("https://api.example.com/data").then((res) => res.json()),
570
+ catch: (err) => `Network error: ${String(err)}`,
571
+ }).pipe(
572
+ Effect.withSpan("http.fetchData", {
573
+ attributes: { url: "https://api.example.com/data" },
574
+ })
575
+ );
576
+
577
+ // Use spans in a workflow
578
+ const program = Effect.gen(function* () {
579
+ yield* Effect.log("Starting workflow").pipe(
580
+ Effect.withSpan("workflow.start")
581
+ );
582
+ const user = yield* fetchUser;
583
+ yield* Effect.log(`Fetched user: ${user.name}`).pipe(
584
+ Effect.withSpan("workflow.end")
585
+ );
586
+ });
587
+ ```
588
+
589
+ **Explanation:**
590
+
591
+ - `Effect.withSpan` creates a tracing span around an operation.
592
+ - Spans can be named and annotated with attributes for richer context.
593
+ - Tracing enables distributed observability and performance analysis.
594
+
595
+ **Anti-Pattern:**
596
+
597
+ Relying only on logs or metrics for performance analysis, or lacking visibility into the flow of requests and operations across services.
598
+
599
+ **Rationale:**
600
+
601
+ Use `Effect.withSpan` to create custom tracing spans around important operations in your application.
602
+ This enables distributed tracing, performance analysis, and deep visibility into how requests flow through your system.
603
+
604
+
605
+ Tracing spans help you understand the flow and timing of operations, especially in distributed systems or complex workflows.
606
+ They allow you to pinpoint bottlenecks, visualize dependencies, and correlate logs and metrics with specific requests.
607
+
608
+ ---
609
+
610
+ ### Trace Operations Across Services with Spans
611
+
612
+ **Rule:** Use Effect.withSpan to create custom tracing spans for important operations.
613
+
614
+ **Good Example:**
615
+
616
+ This example shows a multi-step operation. Each step, and the overall operation, is wrapped in a span. This creates a parent-child hierarchy in the trace that is easy to visualize.
617
+
618
+ ```typescript
619
+ import { Effect, Duration } from "effect";
620
+
621
+ const validateInput = (input: unknown) =>
622
+ Effect.gen(function* () {
623
+ yield* Effect.logInfo("Starting input validation...");
624
+ yield* Effect.sleep(Duration.millis(10));
625
+ const result = { email: "paul@example.com" };
626
+ yield* Effect.logInfo(`âś… Input validated: ${result.email}`);
627
+ return result;
628
+ }).pipe(
629
+ // This creates a child span
630
+ Effect.withSpan("validateInput")
631
+ );
632
+
633
+ const saveToDatabase = (user: { email: string }) =>
634
+ Effect.gen(function* () {
635
+ yield* Effect.logInfo(`Saving user to database: ${user.email}`);
636
+ yield* Effect.sleep(Duration.millis(50));
637
+ const result = { id: 123, ...user };
638
+ yield* Effect.logInfo(`âś… User saved with ID: ${result.id}`);
639
+ return result;
640
+ }).pipe(
641
+ // This span includes useful attributes
642
+ Effect.withSpan("saveToDatabase", {
643
+ attributes: { "db.system": "postgresql", "db.user.email": user.email },
644
+ })
645
+ );
646
+
647
+ const createUser = (input: unknown) =>
648
+ Effect.gen(function* () {
649
+ yield* Effect.logInfo("=== Creating User with Tracing ===");
650
+ yield* Effect.logInfo(
651
+ "This demonstrates how spans trace operations through the call stack"
652
+ );
653
+
654
+ const validated = yield* validateInput(input);
655
+ const user = yield* saveToDatabase(validated);
656
+
657
+ yield* Effect.logInfo(
658
+ `âś… User creation completed: ${JSON.stringify(user)}`
659
+ );
660
+ yield* Effect.logInfo(
661
+ "Note: In production, spans would be sent to a tracing system like Jaeger or Zipkin"
662
+ );
663
+
664
+ return user;
665
+ }).pipe(
666
+ // This is the parent span for the entire operation
667
+ Effect.withSpan("createUserOperation")
668
+ );
669
+
670
+ // Demonstrate the tracing functionality
671
+ const program = Effect.gen(function* () {
672
+ yield* Effect.logInfo("=== Trace Operations with Spans Demo ===");
673
+
674
+ // Create multiple users to show tracing in action
675
+ const user1 = yield* createUser({ email: "user1@example.com" });
676
+
677
+ yield* Effect.logInfo("\n--- Creating second user ---");
678
+ const user2 = yield* createUser({ email: "user2@example.com" });
679
+
680
+ yield* Effect.logInfo("\n=== Summary ===");
681
+ yield* Effect.logInfo("Created users with tracing spans:");
682
+ yield* Effect.logInfo(`User 1: ID ${user1.id}, Email: ${user1.email}`);
683
+ yield* Effect.logInfo(`User 2: ID ${user2.id}, Email: ${user2.email}`);
684
+ });
685
+
686
+ // When run with a tracing SDK, this will produce traces with root spans
687
+ // "createUserOperation" and child spans: "validateInput" and "saveToDatabase".
688
+ Effect.runPromise(program);
689
+ ```
690
+
691
+ ---
692
+
693
+ **Anti-Pattern:**
694
+
695
+ Not adding custom spans to your business logic.
696
+ Without them, your traces will only show high-level information from your framework (e.g., "HTTP POST /users").
697
+ You will have no visibility into the performance of the individual steps _inside_ your request handler, making it very difficult to pinpoint bottlenecks. Your application's logic remains a "black box" in your traces.
698
+
699
+ **Rationale:**
700
+
701
+ To gain visibility into the performance and flow of your application, wrap logical units of work with `Effect.withSpan("span-name")`. You can add contextual information to these spans using the `attributes` option.
702
+
703
+ ---
704
+
705
+
706
+ While logs tell you _what_ happened, traces tell you _why it was slow_. In a complex application, a single user request might trigger calls to multiple services (authentication, database, external APIs). Tracing allows you to visualize this entire chain of events as a single, hierarchical "trace."
707
+
708
+ Each piece of work in that trace is a `span`. `Effect.withSpan` allows you to create your own custom spans. This is invaluable for answering questions like:
709
+
710
+ - "For this API request, did we spend most of our time in the database or calling the external payment gateway?"
711
+ - "Which part of our user creation logic is the bottleneck?"
712
+
713
+ Effect's tracing is built on OpenTelemetry, the industry standard, so it integrates seamlessly with tools like Jaeger, Zipkin, and Datadog.
714
+
715
+ ---
716
+
717
+ ---
718
+
719
+
720
+ ## đźź  Advanced Patterns
721
+
722
+ ### Create Observability Dashboards
723
+
724
+ **Rule:** Create focused dashboards that answer specific questions about system health.
725
+
726
+ **Rationale:**
727
+
728
+ Design dashboards that answer specific questions about system health, performance, and user experience.
729
+
730
+ ---
731
+
732
+
733
+ Good dashboards provide:
734
+
735
+ 1. **Quick health check** - See problems at a glance
736
+ 2. **Trend analysis** - Spot gradual degradation
737
+ 3. **Debugging aid** - Correlate metrics during incidents
738
+ 4. **Capacity planning** - Forecast resource needs
739
+
740
+ ---
741
+
742
+ ---
743
+
744
+ ### Set Up Alerting
745
+
746
+ **Rule:** Create alerts based on SLOs and symptoms, not causes.
747
+
748
+ **Good Example:**
749
+
750
+ ```typescript
751
+ import { Effect, Metric, Schedule, Duration, Ref } from "effect"
752
+
753
+ // ============================================
754
+ // 1. Define alertable conditions
755
+ // ============================================
756
+
757
+ interface Alert {
758
+ readonly name: string
759
+ readonly severity: "critical" | "warning" | "info"
760
+ readonly message: string
761
+ readonly timestamp: Date
762
+ readonly labels: Record<string, string>
763
+ }
764
+
765
+ interface AlertRule {
766
+ readonly name: string
767
+ readonly condition: Effect.Effect<boolean>
768
+ readonly severity: "critical" | "warning" | "info"
769
+ readonly message: string
770
+ readonly labels: Record<string, string>
771
+ readonly forDuration: Duration.DurationInput
772
+ }
773
+
774
+ // ============================================
775
+ // 2. Define alert rules
776
+ // ============================================
777
+
778
+ const createAlertRules = (metrics: {
779
+ errorRate: () => Effect.Effect<number>
780
+ latencyP99: () => Effect.Effect<number>
781
+ availability: () => Effect.Effect<number>
782
+ }): AlertRule[] => [
783
+ {
784
+ name: "HighErrorRate",
785
+ condition: metrics.errorRate().pipe(Effect.map((rate) => rate > 0.01)),
786
+ severity: "critical",
787
+ message: "Error rate exceeds 1%",
788
+ labels: { team: "backend", service: "api" },
789
+ forDuration: "5 minutes",
790
+ },
791
+ {
792
+ name: "HighLatency",
793
+ condition: metrics.latencyP99().pipe(Effect.map((p99) => p99 > 2)),
794
+ severity: "warning",
795
+ message: "P99 latency exceeds 2 seconds",
796
+ labels: { team: "backend", service: "api" },
797
+ forDuration: "10 minutes",
798
+ },
799
+ {
800
+ name: "LowAvailability",
801
+ condition: metrics.availability().pipe(Effect.map((avail) => avail < 99.9)),
802
+ severity: "critical",
803
+ message: "Availability below 99.9% SLO",
804
+ labels: { team: "backend", service: "api" },
805
+ forDuration: "5 minutes",
806
+ },
807
+ {
808
+ name: "ErrorBudgetLow",
809
+ condition: Effect.succeed(false), // Implement based on error budget calc
810
+ severity: "warning",
811
+ message: "Error budget below 25%",
812
+ labels: { team: "backend", service: "api" },
813
+ forDuration: "0 seconds",
814
+ },
815
+ ]
816
+
817
+ // ============================================
818
+ // 3. Alert manager
819
+ // ============================================
820
+
821
+ interface AlertState {
822
+ readonly firing: Map<string, { since: Date; alert: Alert }>
823
+ readonly resolved: Alert[]
824
+ }
825
+
826
+ const makeAlertManager = Effect.gen(function* () {
827
+ const state = yield* Ref.make<AlertState>({
828
+ firing: new Map(),
829
+ resolved: [],
830
+ })
831
+
832
+ const checkRule = (rule: AlertRule) =>
833
+ Effect.gen(function* () {
834
+ const isTriggered = yield* rule.condition
835
+
836
+ yield* Ref.modify(state, (s) => {
837
+ const firing = new Map(s.firing)
838
+ const resolved = [...s.resolved]
839
+ const key = rule.name
840
+
841
+ if (isTriggered) {
842
+ if (!firing.has(key)) {
843
+ // New alert
844
+ firing.set(key, {
845
+ since: new Date(),
846
+ alert: {
847
+ name: rule.name,
848
+ severity: rule.severity,
849
+ message: rule.message,
850
+ timestamp: new Date(),
851
+ labels: rule.labels,
852
+ },
853
+ })
854
+ }
855
+ } else {
856
+ if (firing.has(key)) {
857
+ // Alert resolved
858
+ const prev = firing.get(key)!
859
+ resolved.push({
860
+ ...prev.alert,
861
+ message: `[RESOLVED] ${prev.alert.message}`,
862
+ timestamp: new Date(),
863
+ })
864
+ firing.delete(key)
865
+ }
866
+ }
867
+
868
+ return [undefined, { firing, resolved }]
869
+ })
870
+ })
871
+
872
+ const getActiveAlerts = () =>
873
+ Ref.get(state).pipe(
874
+ Effect.map((s) => Array.from(s.firing.values()).map((f) => f.alert))
875
+ )
876
+
877
+ const getRecentResolved = () =>
878
+ Ref.get(state).pipe(Effect.map((s) => s.resolved.slice(-10)))
879
+
880
+ return {
881
+ checkRule,
882
+ getActiveAlerts,
883
+ getRecentResolved,
884
+ }
885
+ })
886
+
887
+ // ============================================
888
+ // 4. Alert notification
889
+ // ============================================
890
+
891
+ interface NotificationChannel {
892
+ readonly send: (alert: Alert) => Effect.Effect<void>
893
+ }
894
+
895
+ const slackChannel: NotificationChannel = {
896
+ send: (alert) =>
897
+ Effect.gen(function* () {
898
+ const emoji =
899
+ alert.severity === "critical"
900
+ ? "đź”´"
901
+ : alert.severity === "warning"
902
+ ? "🟡"
903
+ : "🔵"
904
+
905
+ yield* Effect.log(`${emoji} [${alert.severity.toUpperCase()}] ${alert.name}`).pipe(
906
+ Effect.annotateLogs({
907
+ message: alert.message,
908
+ labels: JSON.stringify(alert.labels),
909
+ })
910
+ )
911
+
912
+ // In real implementation: call Slack API
913
+ }),
914
+ }
915
+
916
+ const pagerDutyChannel: NotificationChannel = {
917
+ send: (alert) =>
918
+ Effect.gen(function* () {
919
+ if (alert.severity === "critical") {
920
+ yield* Effect.log("PagerDuty: Creating incident").pipe(
921
+ Effect.annotateLogs({ alert: alert.name })
922
+ )
923
+ // In real implementation: call PagerDuty API
924
+ }
925
+ }),
926
+ }
927
+
928
+ // ============================================
929
+ // 5. Alert evaluation loop
930
+ // ============================================
931
+
932
+ const runAlertEvaluation = (
933
+ rules: AlertRule[],
934
+ channels: NotificationChannel[],
935
+ interval: Duration.DurationInput
936
+ ) =>
937
+ Effect.gen(function* () {
938
+ const alertManager = yield* makeAlertManager
939
+ const previousAlerts = yield* Ref.make(new Set<string>())
940
+
941
+ yield* Effect.forever(
942
+ Effect.gen(function* () {
943
+ // Check all rules
944
+ for (const rule of rules) {
945
+ yield* alertManager.checkRule(rule)
946
+ }
947
+
948
+ // Get current active alerts
949
+ const active = yield* alertManager.getActiveAlerts()
950
+ const current = new Set(active.map((a) => a.name))
951
+ const previous = yield* Ref.get(previousAlerts)
952
+
953
+ // Find newly firing alerts
954
+ for (const alert of active) {
955
+ if (!previous.has(alert.name)) {
956
+ // New alert - send notifications
957
+ for (const channel of channels) {
958
+ yield* channel.send(alert)
959
+ }
960
+ }
961
+ }
962
+
963
+ yield* Ref.set(previousAlerts, current)
964
+ yield* Effect.sleep(interval)
965
+ })
966
+ )
967
+ })
968
+
969
+ // ============================================
970
+ // 6. Prometheus alerting rules (YAML)
971
+ // ============================================
972
+
973
+ const prometheusAlertRules = `
974
+ groups:
975
+ - name: effect-app-alerts
976
+ rules:
977
+ - alert: HighErrorRate
978
+ expr: |
979
+ sum(rate(http_errors_total[5m]))
980
+ /
981
+ sum(rate(http_requests_total[5m]))
982
+ > 0.01
983
+ for: 5m
984
+ labels:
985
+ severity: critical
986
+ annotations:
987
+ summary: "High error rate detected"
988
+ description: "Error rate is {{ $value | humanizePercentage }}"
989
+
990
+ - alert: HighLatency
991
+ expr: |
992
+ histogram_quantile(0.99,
993
+ sum(rate(http_request_duration_seconds_bucket[5m])) by (le)
994
+ ) > 2
995
+ for: 10m
996
+ labels:
997
+ severity: warning
998
+ annotations:
999
+ summary: "High P99 latency"
1000
+ description: "P99 latency is {{ $value }}s"
1001
+
1002
+ - alert: SLOViolation
1003
+ expr: |
1004
+ sum(rate(http_requests_total{status!~"5.."}[30m]))
1005
+ /
1006
+ sum(rate(http_requests_total[30m]))
1007
+ < 0.999
1008
+ for: 5m
1009
+ labels:
1010
+ severity: critical
1011
+ annotations:
1012
+ summary: "SLO violation"
1013
+ description: "Availability is {{ $value | humanizePercentage }}"
1014
+ `
1015
+ ```
1016
+
1017
+ **Rationale:**
1018
+
1019
+ Set up alerts based on user-facing symptoms (SLO violations) rather than system metrics (CPU usage).
1020
+
1021
+ ---
1022
+
1023
+
1024
+ Good alerting:
1025
+
1026
+ 1. **Catches real problems** - Alerts when users are affected
1027
+ 2. **Reduces noise** - Fewer false positives
1028
+ 3. **Enables response** - Actionable information
1029
+ 4. **Supports SLOs** - Tracks service level objectives
1030
+
1031
+ ---
1032
+
1033
+ ---
1034
+
1035
+ ### Export Metrics to Prometheus
1036
+
1037
+ **Rule:** Use Effect metrics and expose a /metrics endpoint for Prometheus scraping.
1038
+
1039
+ **Good Example:**
1040
+
1041
+ ```typescript
1042
+ import { Effect, Metric, MetricLabel, Duration } from "effect"
1043
+ import { HttpServerResponse } from "@effect/platform"
1044
+
1045
+ // ============================================
1046
+ // 1. Define application metrics
1047
+ // ============================================
1048
+
1049
+ // Counter - counts events
1050
+ const httpRequestsTotal = Metric.counter("http_requests_total", {
1051
+ description: "Total number of HTTP requests",
1052
+ })
1053
+
1054
+ // Counter with labels
1055
+ const httpRequestsByStatus = Metric.counter("http_requests_by_status", {
1056
+ description: "HTTP requests by status code",
1057
+ })
1058
+
1059
+ // Gauge - current value
1060
+ const activeConnections = Metric.gauge("active_connections", {
1061
+ description: "Number of active connections",
1062
+ })
1063
+
1064
+ // Histogram - distribution of values
1065
+ const requestDuration = Metric.histogram("http_request_duration_seconds", {
1066
+ description: "HTTP request duration in seconds",
1067
+ boundaries: [0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
1068
+ })
1069
+
1070
+ // Summary - percentiles
1071
+ const responseSizeBytes = Metric.summary("http_response_size_bytes", {
1072
+ description: "HTTP response size in bytes",
1073
+ maxAge: Duration.minutes(5),
1074
+ maxSize: 100,
1075
+ quantiles: [0.5, 0.9, 0.99],
1076
+ })
1077
+
1078
+ // ============================================
1079
+ // 2. Instrument code with metrics
1080
+ // ============================================
1081
+
1082
+ const handleRequest = (path: string, status: number) =>
1083
+ Effect.gen(function* () {
1084
+ const startTime = Date.now()
1085
+
1086
+ // Increment request counter
1087
+ yield* Metric.increment(httpRequestsTotal)
1088
+
1089
+ // Increment with labels
1090
+ yield* Metric.increment(
1091
+ httpRequestsByStatus.pipe(
1092
+ Metric.tagged("status", String(status)),
1093
+ Metric.tagged("path", path)
1094
+ )
1095
+ )
1096
+
1097
+ // Track active connections
1098
+ yield* Metric.increment(activeConnections)
1099
+
1100
+ // Simulate work
1101
+ yield* Effect.sleep("100 millis")
1102
+
1103
+ // Record duration
1104
+ const duration = (Date.now() - startTime) / 1000
1105
+ yield* Metric.update(requestDuration, duration)
1106
+
1107
+ // Record response size
1108
+ yield* Metric.update(responseSizeBytes, 1024)
1109
+
1110
+ // Decrement active connections
1111
+ yield* Metric.decrement(activeConnections)
1112
+ })
1113
+
1114
+ // ============================================
1115
+ // 3. Prometheus text format exporter
1116
+ // ============================================
1117
+
1118
+ interface MetricSnapshot {
1119
+ name: string
1120
+ type: "counter" | "gauge" | "histogram" | "summary"
1121
+ help: string
1122
+ values: Array<{
1123
+ labels: Record<string, string>
1124
+ value: number
1125
+ }>
1126
+ // For histograms
1127
+ buckets?: Array<{
1128
+ le: number
1129
+ count: number
1130
+ labels?: Record<string, string>
1131
+ }>
1132
+ sum?: number
1133
+ count?: number
1134
+ }
1135
+
1136
+ const formatPrometheusMetrics = (metrics: MetricSnapshot[]): string => {
1137
+ const lines: string[] = []
1138
+
1139
+ for (const metric of metrics) {
1140
+ // Help line
1141
+ lines.push(`# HELP ${metric.name} ${metric.help}`)
1142
+ lines.push(`# TYPE ${metric.name} ${metric.type}`)
1143
+
1144
+ // Values
1145
+ for (const { labels, value } of metric.values) {
1146
+ const labelStr = Object.entries(labels)
1147
+ .map(([k, v]) => `${k}="${v}"`)
1148
+ .join(",")
1149
+
1150
+ if (labelStr) {
1151
+ lines.push(`${metric.name}{${labelStr}} ${value}`)
1152
+ } else {
1153
+ lines.push(`${metric.name} ${value}`)
1154
+ }
1155
+ }
1156
+
1157
+ // Histogram buckets
1158
+ if (metric.buckets) {
1159
+ for (const bucket of metric.buckets) {
1160
+ const labelStr = Object.entries(bucket.labels || {})
1161
+ .map(([k, v]) => `${k}="${v}"`)
1162
+ .concat([`le="${bucket.le}"`])
1163
+ .join(",")
1164
+ lines.push(`${metric.name}_bucket{${labelStr}} ${bucket.count}`)
1165
+ }
1166
+ lines.push(`${metric.name}_sum ${metric.sum}`)
1167
+ lines.push(`${metric.name}_count ${metric.count}`)
1168
+ }
1169
+
1170
+ lines.push("")
1171
+ }
1172
+
1173
+ return lines.join("\n")
1174
+ }
1175
+
1176
+ // ============================================
1177
+ // 4. /metrics endpoint handler
1178
+ // ============================================
1179
+
1180
+ const metricsHandler = Effect.gen(function* () {
1181
+ // In real implementation, read from Effect's MetricRegistry
1182
+ const metrics: MetricSnapshot[] = [
1183
+ {
1184
+ name: "http_requests_total",
1185
+ type: "counter",
1186
+ help: "Total number of HTTP requests",
1187
+ values: [{ labels: {}, value: 1234 }],
1188
+ },
1189
+ {
1190
+ name: "http_requests_by_status",
1191
+ type: "counter",
1192
+ help: "HTTP requests by status code",
1193
+ values: [
1194
+ { labels: { status: "200", path: "/api/users" }, value: 1000 },
1195
+ { labels: { status: "404", path: "/api/users" }, value: 50 },
1196
+ { labels: { status: "500", path: "/api/users" }, value: 10 },
1197
+ ],
1198
+ },
1199
+ {
1200
+ name: "active_connections",
1201
+ type: "gauge",
1202
+ help: "Number of active connections",
1203
+ values: [{ labels: {}, value: 42 }],
1204
+ },
1205
+ {
1206
+ name: "http_request_duration_seconds",
1207
+ type: "histogram",
1208
+ help: "HTTP request duration in seconds",
1209
+ values: [],
1210
+ buckets: [
1211
+ { le: 0.01, count: 100 },
1212
+ { le: 0.05, count: 500 },
1213
+ { le: 0.1, count: 800 },
1214
+ { le: 0.25, count: 950 },
1215
+ { le: 0.5, count: 990 },
1216
+ { le: 1, count: 999 },
1217
+ { le: Infinity, count: 1000 },
1218
+ ],
1219
+ sum: 123.456,
1220
+ count: 1000,
1221
+ },
1222
+ ]
1223
+
1224
+ const body = formatPrometheusMetrics(metrics)
1225
+
1226
+ return HttpServerResponse.text(body, {
1227
+ headers: {
1228
+ "Content-Type": "text/plain; version=0.0.4; charset=utf-8",
1229
+ },
1230
+ })
1231
+ })
1232
+
1233
+ // ============================================
1234
+ // 5. Example output
1235
+ // ============================================
1236
+
1237
+ /*
1238
+ # HELP http_requests_total Total number of HTTP requests
1239
+ # TYPE http_requests_total counter
1240
+ http_requests_total 1234
1241
+
1242
+ # HELP http_requests_by_status HTTP requests by status code
1243
+ # TYPE http_requests_by_status counter
1244
+ http_requests_by_status{status="200",path="/api/users"} 1000
1245
+ http_requests_by_status{status="404",path="/api/users"} 50
1246
+ http_requests_by_status{status="500",path="/api/users"} 10
1247
+
1248
+ # HELP active_connections Number of active connections
1249
+ # TYPE active_connections gauge
1250
+ active_connections 42
1251
+
1252
+ # HELP http_request_duration_seconds HTTP request duration in seconds
1253
+ # TYPE http_request_duration_seconds histogram
1254
+ http_request_duration_seconds_bucket{le="0.01"} 100
1255
+ http_request_duration_seconds_bucket{le="0.05"} 500
1256
+ http_request_duration_seconds_bucket{le="0.1"} 800
1257
+ http_request_duration_seconds_bucket{le="+Inf"} 1000
1258
+ http_request_duration_seconds_sum 123.456
1259
+ http_request_duration_seconds_count 1000
1260
+ */
1261
+ ```
1262
+
1263
+ **Rationale:**
1264
+
1265
+ Create metrics with Effect's Metric API and expose them via an HTTP endpoint in Prometheus text format.
1266
+
1267
+ ---
1268
+
1269
+
1270
+ Prometheus metrics enable:
1271
+
1272
+ 1. **Real-time monitoring** - See what's happening now
1273
+ 2. **Historical analysis** - Track trends over time
1274
+ 3. **Alerting** - Get notified of issues
1275
+ 4. **Dashboards** - Visualize system health
1276
+
1277
+ ---
1278
+
1279
+ ---
1280
+
1281
+ ### Implement Distributed Tracing
1282
+
1283
+ **Rule:** Propagate trace context across service boundaries to correlate requests.
1284
+
1285
+ **Good Example:**
1286
+
1287
+ ```typescript
1288
+ import { Effect, Context, Layer } from "effect"
1289
+ import { HttpClient, HttpClientRequest, HttpServerRequest, HttpServerResponse } from "@effect/platform"
1290
+
1291
+ // ============================================
1292
+ // 1. Define trace context
1293
+ // ============================================
1294
+
1295
+ interface TraceContext {
1296
+ readonly traceId: string
1297
+ readonly spanId: string
1298
+ readonly parentSpanId?: string
1299
+ readonly sampled: boolean
1300
+ }
1301
+
1302
+ class CurrentTrace extends Context.Tag("CurrentTrace")<
1303
+ CurrentTrace,
1304
+ TraceContext
1305
+ >() {}
1306
+
1307
+ // W3C Trace Context header names
1308
+ const TRACEPARENT_HEADER = "traceparent"
1309
+ const TRACESTATE_HEADER = "tracestate"
1310
+
1311
+ // ============================================
1312
+ // 2. Generate trace IDs
1313
+ // ============================================
1314
+
1315
+ const generateTraceId = (): string =>
1316
+ Array.from(crypto.getRandomValues(new Uint8Array(16)))
1317
+ .map((b) => b.toString(16).padStart(2, "0"))
1318
+ .join("")
1319
+
1320
+ const generateSpanId = (): string =>
1321
+ Array.from(crypto.getRandomValues(new Uint8Array(8)))
1322
+ .map((b) => b.toString(16).padStart(2, "0"))
1323
+ .join("")
1324
+
1325
+ // ============================================
1326
+ // 3. Parse and format trace context
1327
+ // ============================================
1328
+
1329
+ const parseTraceparent = (header: string): TraceContext | null => {
1330
+ // Format: 00-traceId-spanId-flags
1331
+ const parts = header.split("-")
1332
+ if (parts.length !== 4) return null
1333
+
1334
+ return {
1335
+ traceId: parts[1],
1336
+ spanId: generateSpanId(), // New span for this service
1337
+ parentSpanId: parts[2],
1338
+ sampled: parts[3] === "01",
1339
+ }
1340
+ }
1341
+
1342
+ const formatTraceparent = (ctx: TraceContext): string =>
1343
+ `00-${ctx.traceId}-${ctx.spanId}-${ctx.sampled ? "01" : "00"}`
1344
+
1345
+ // ============================================
1346
+ // 4. Extract trace from incoming request
1347
+ // ============================================
1348
+
1349
+ const extractTraceContext = Effect.gen(function* () {
1350
+ const request = yield* HttpServerRequest.HttpServerRequest
1351
+
1352
+ const traceparent = request.headers[TRACEPARENT_HEADER]
1353
+
1354
+ if (traceparent) {
1355
+ const parsed = parseTraceparent(traceparent)
1356
+ if (parsed) {
1357
+ yield* Effect.log("Extracted trace context").pipe(
1358
+ Effect.annotateLogs({
1359
+ traceId: parsed.traceId,
1360
+ parentSpanId: parsed.parentSpanId,
1361
+ })
1362
+ )
1363
+ return parsed
1364
+ }
1365
+ }
1366
+
1367
+ // No incoming trace - start a new one
1368
+ const newTrace: TraceContext = {
1369
+ traceId: generateTraceId(),
1370
+ spanId: generateSpanId(),
1371
+ sampled: Math.random() < 0.1, // 10% sampling
1372
+ }
1373
+
1374
+ yield* Effect.log("Started new trace").pipe(
1375
+ Effect.annotateLogs({ traceId: newTrace.traceId })
1376
+ )
1377
+
1378
+ return newTrace
1379
+ })
1380
+
1381
+ // ============================================
1382
+ // 5. Propagate trace to outgoing requests
1383
+ // ============================================
1384
+
1385
+ const makeTracedHttpClient = Effect.gen(function* () {
1386
+ const baseClient = yield* HttpClient.HttpClient
1387
+ const trace = yield* CurrentTrace
1388
+
1389
+ return {
1390
+ get: (url: string) =>
1391
+ Effect.gen(function* () {
1392
+ // Create child span for outgoing request
1393
+ const childSpan: TraceContext = {
1394
+ traceId: trace.traceId,
1395
+ spanId: generateSpanId(),
1396
+ parentSpanId: trace.spanId,
1397
+ sampled: trace.sampled,
1398
+ }
1399
+
1400
+ yield* Effect.log("Making traced HTTP request").pipe(
1401
+ Effect.annotateLogs({
1402
+ traceId: childSpan.traceId,
1403
+ spanId: childSpan.spanId,
1404
+ url,
1405
+ })
1406
+ )
1407
+
1408
+ const request = HttpClientRequest.get(url).pipe(
1409
+ HttpClientRequest.setHeader(
1410
+ TRACEPARENT_HEADER,
1411
+ formatTraceparent(childSpan)
1412
+ )
1413
+ )
1414
+
1415
+ return yield* baseClient.execute(request)
1416
+ }),
1417
+ }
1418
+ })
1419
+
1420
+ // ============================================
1421
+ // 6. Tracing middleware for HTTP server
1422
+ // ============================================
1423
+
1424
+ const withTracing = <A, E, R>(
1425
+ handler: Effect.Effect<A, E, R | CurrentTrace>
1426
+ ): Effect.Effect<A, E, R | HttpServerRequest.HttpServerRequest> =>
1427
+ Effect.gen(function* () {
1428
+ const traceContext = yield* extractTraceContext
1429
+
1430
+ return yield* handler.pipe(
1431
+ Effect.provideService(CurrentTrace, traceContext),
1432
+ Effect.withLogSpan(`request-${traceContext.spanId}`),
1433
+ Effect.annotateLogs({
1434
+ "trace.id": traceContext.traceId,
1435
+ "span.id": traceContext.spanId,
1436
+ "parent.span.id": traceContext.parentSpanId ?? "none",
1437
+ })
1438
+ )
1439
+ })
1440
+
1441
+ // ============================================
1442
+ // 7. Example: Service A calls Service B
1443
+ // ============================================
1444
+
1445
+ // Service B handler
1446
+ const serviceBHandler = withTracing(
1447
+ Effect.gen(function* () {
1448
+ const trace = yield* CurrentTrace
1449
+ yield* Effect.log("Service B processing request")
1450
+
1451
+ // Simulate work
1452
+ yield* Effect.sleep("50 millis")
1453
+
1454
+ return HttpServerResponse.json({
1455
+ message: "Hello from Service B",
1456
+ traceId: trace.traceId,
1457
+ })
1458
+ })
1459
+ )
1460
+
1461
+ // Service A handler (calls Service B)
1462
+ const serviceAHandler = withTracing(
1463
+ Effect.gen(function* () {
1464
+ const trace = yield* CurrentTrace
1465
+ yield* Effect.log("Service A processing request")
1466
+
1467
+ // Call Service B with trace propagation
1468
+ const tracedClient = yield* makeTracedHttpClient
1469
+ const response = yield* tracedClient.get("http://service-b/api/data")
1470
+
1471
+ yield* Effect.log("Service A received response from B")
1472
+
1473
+ return HttpServerResponse.json({
1474
+ message: "Hello from Service A",
1475
+ traceId: trace.traceId,
1476
+ })
1477
+ })
1478
+ )
1479
+
1480
+ // ============================================
1481
+ // 8. Run and observe
1482
+ // ============================================
1483
+
1484
+ const program = Effect.gen(function* () {
1485
+ yield* Effect.log("=== Distributed Tracing Demo ===")
1486
+
1487
+ // Simulate incoming request with trace
1488
+ const incomingTrace: TraceContext = {
1489
+ traceId: generateTraceId(),
1490
+ spanId: generateSpanId(),
1491
+ sampled: true,
1492
+ }
1493
+
1494
+ yield* Effect.log("Processing traced request").pipe(
1495
+ Effect.provideService(CurrentTrace, incomingTrace),
1496
+ Effect.annotateLogs({
1497
+ "trace.id": incomingTrace.traceId,
1498
+ "span.id": incomingTrace.spanId,
1499
+ })
1500
+ )
1501
+ })
1502
+
1503
+ Effect.runPromise(program)
1504
+ ```
1505
+
1506
+ **Rationale:**
1507
+
1508
+ Implement distributed tracing by propagating trace context through HTTP headers and using consistent span naming across services.
1509
+
1510
+ ---
1511
+
1512
+
1513
+ Distributed tracing shows the complete request journey:
1514
+
1515
+ 1. **End-to-end visibility** - See entire request flow
1516
+ 2. **Latency analysis** - Find slow services
1517
+ 3. **Error correlation** - Link errors across services
1518
+ 4. **Dependency mapping** - Understand service relationships
1519
+
1520
+ ---
1521
+
1522
+ ---
1523
+
1524
+ ### Integrate Effect Tracing with OpenTelemetry
1525
+
1526
+ **Rule:** Integrate Effect.withSpan with OpenTelemetry to export traces and visualize request flows across services.
1527
+
1528
+ **Good Example:**
1529
+
1530
+ ```typescript
1531
+ import { Effect } from "effect";
1532
+ // Pseudocode: Replace with actual OpenTelemetry integration for your stack
1533
+ import { trace, context, SpanStatusCode } from "@opentelemetry/api";
1534
+
1535
+ // Wrap an Effect.withSpan to export to OpenTelemetry
1536
+ function withOtelSpan<T>(
1537
+ name: string,
1538
+ effect: Effect.Effect<unknown, T, unknown>
1539
+ ) {
1540
+ return Effect.gen(function* () {
1541
+ const otelSpan = trace.getTracer("default").startSpan(name);
1542
+ try {
1543
+ const result = yield* effect;
1544
+ otelSpan.setStatus({ code: SpanStatusCode.OK });
1545
+ return result;
1546
+ } catch (err) {
1547
+ otelSpan.setStatus({ code: SpanStatusCode.ERROR, message: String(err) });
1548
+ throw err;
1549
+ } finally {
1550
+ otelSpan.end();
1551
+ }
1552
+ });
1553
+ }
1554
+
1555
+ // Usage
1556
+ const program = withOtelSpan(
1557
+ "fetchUser",
1558
+ Effect.sync(() => {
1559
+ // ...fetch user logic
1560
+ return { id: 1, name: "Alice" };
1561
+ })
1562
+ );
1563
+ ```
1564
+
1565
+ **Explanation:**
1566
+
1567
+ - Start an OpenTelemetry span when entering an Effectful operation.
1568
+ - Set status and attributes as needed.
1569
+ - End the span when the operation completes or fails.
1570
+ - This enables full distributed tracing and visualization in your observability platform.
1571
+
1572
+ **Anti-Pattern:**
1573
+
1574
+ Using Effect.withSpan without exporting to OpenTelemetry, or lacking distributed tracing, which limits your ability to diagnose and visualize complex request flows.
1575
+
1576
+ **Rationale:**
1577
+
1578
+ Connect Effect's tracing spans to OpenTelemetry to enable distributed tracing, visualization, and correlation across your entire stack.
1579
+
1580
+
1581
+ OpenTelemetry is the industry standard for distributed tracing.
1582
+ By integrating Effect's spans with OpenTelemetry, you gain deep visibility into request flows, performance bottlenecks, and dependencies—across all your services and infrastructure.
1583
+
1584
+ ---
1585
+
1586
+