@zio.dev/zio-blocks 0.0.51 → 0.0.55

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 (164) hide show
  1. package/adr/2026-07-18-data-migration.md +123 -0
  2. package/guides/async-getting-started.md +687 -0
  3. package/guides/compile-time-resource-safety-with-scope.md +6 -0
  4. package/guides/getting-started-with-mux.md +0 -112
  5. package/guides/query-dsl-extending.md +1 -1
  6. package/guides/query-dsl-fluent-builder.md +1 -1
  7. package/guides/query-dsl-reified-optics.md +1 -1
  8. package/guides/query-dsl-sql.md +395 -1
  9. package/guides/sql-checked-interpolation.md +173 -0
  10. package/guides/sql-transactions.md +286 -0
  11. package/guides/telemetry-guide.md +131 -70
  12. package/guides/zio-schema-migration.md +6 -6
  13. package/index.md +200 -583
  14. package/package.json +1 -1
  15. package/reference/async.md +1379 -531
  16. package/reference/chunk.md +3 -3
  17. package/reference/codegen/index.md +1 -1
  18. package/reference/combinators.md +4 -4
  19. package/reference/config/config-decoder.md +460 -0
  20. package/reference/config/config-source.md +489 -0
  21. package/reference/config/errors.md +278 -0
  22. package/reference/config/flags.md +369 -0
  23. package/reference/config/formats.md +314 -0
  24. package/reference/config/index.md +304 -0
  25. package/reference/config/rollout.md +336 -0
  26. package/reference/context.md +6 -49
  27. package/reference/data-migration.md +269 -0
  28. package/reference/datastar/attributes.md +302 -0
  29. package/reference/datastar/events.md +234 -0
  30. package/reference/datastar/index.md +256 -0
  31. package/reference/datastar/signals.md +230 -0
  32. package/reference/datastar/sse.md +295 -0
  33. package/reference/datastar.md +2 -2
  34. package/reference/docs.md +2 -2
  35. package/reference/endpoint/bulk-creation.md +96 -0
  36. package/reference/endpoint/index.md +9 -89
  37. package/reference/endpoint/path-codec.md +12 -24
  38. package/reference/endpoint/route-pattern.md +4 -6
  39. package/reference/endpoint/segment-codec.md +19 -32
  40. package/reference/html.md +313 -9
  41. package/reference/htmx/index.md +4 -52
  42. package/reference/htmx/response-headers.md +240 -0
  43. package/reference/http-model/headers.md +735 -0
  44. package/reference/http-model/index.md +3 -1
  45. package/reference/http-model/model.md +107 -71
  46. package/reference/http-model/schema-codecs.md +522 -0
  47. package/reference/http-model/schema.md +6 -3
  48. package/reference/http-model/server-sent-event.md +341 -0
  49. package/reference/jwt.md +195 -0
  50. package/reference/maybe.md +128 -11
  51. package/reference/media-type.md +2 -2
  52. package/reference/mux.md +254 -0
  53. package/reference/mux.mdx +7 -2
  54. package/reference/openapi.md +3 -3
  55. package/reference/projection.md +654 -0
  56. package/reference/resource-management/resource.md +2 -98
  57. package/reference/resource-management/scope.md +1 -209
  58. package/reference/resource-management/wire.md +4 -50
  59. package/reference/ringbuffer/advanced.mdx +1 -1
  60. package/reference/ringbuffer/index.mdx +3 -3
  61. package/reference/ringbuffer/mpmc.mdx +38 -4
  62. package/reference/ringbuffer/mpsc.mdx +36 -4
  63. package/reference/ringbuffer/spmc.mdx +1 -1
  64. package/reference/ringbuffer/spsc.mdx +87 -15
  65. package/reference/schema/allows.md +0 -96
  66. package/reference/schema/binding.md +2 -2
  67. package/reference/schema/built-in-codecs/avro.md +2 -2
  68. package/reference/schema/built-in-codecs/bson.md +50 -20
  69. package/reference/schema/built-in-codecs/csv.md +2 -2
  70. package/reference/schema/built-in-codecs/index.md +3 -3
  71. package/reference/schema/built-in-codecs/json/index.md +2 -2
  72. package/reference/schema/built-in-codecs/messagepack.md +3 -3
  73. package/reference/schema/built-in-codecs/thrift.md +2 -2
  74. package/reference/schema/built-in-codecs/toon.md +3 -3
  75. package/reference/schema/built-in-codecs/yaml.md +2 -2
  76. package/reference/schema/codec.md +11 -11
  77. package/reference/schema/dynamic-optic.md +48 -3
  78. package/reference/schema/dynamic-schema.md +3 -3
  79. package/reference/schema/index.md +2 -0
  80. package/reference/schema/path-interpolator.md +2 -0
  81. package/reference/schema/reflect-transformer.md +140 -0
  82. package/reference/schema/schema-evolution/as.md +4 -4
  83. package/reference/schema/schema-evolution/into.md +2 -2
  84. package/reference/schema/schema-expr.md +2 -2
  85. package/reference/schema/schema-search.md +263 -0
  86. package/reference/schema/schema.md +10 -2
  87. package/reference/schema/type-class-derivation.md +1 -1
  88. package/reference/smithy.md +502 -3
  89. package/reference/sql/db-codec-deriver.md +3 -3
  90. package/reference/sql/db-codec.md +22 -22
  91. package/reference/sql/db-con.md +4 -4
  92. package/reference/sql/db-connection.md +1 -1
  93. package/reference/sql/db-param.md +1 -1
  94. package/reference/sql/db-result-reader.md +4 -2
  95. package/reference/sql/db-tx.md +46 -14
  96. package/reference/sql/ddl.md +1 -1
  97. package/reference/sql/frag.md +44 -10
  98. package/reference/sql/index.md +7 -7
  99. package/reference/sql/repo.md +15 -15
  100. package/reference/sql/sql-dialect.md +1 -1
  101. package/reference/sql/sql-logger.md +1 -1
  102. package/reference/sql/sql-name-mapper.md +3 -3
  103. package/reference/sql/table-metadata.md +3 -3
  104. package/reference/sql/table.md +10 -10
  105. package/reference/sql/transactor-zio.md +1 -1
  106. package/reference/sql/transactor.md +21 -11
  107. package/reference/sql-zio.md +1 -1
  108. package/reference/streams/core/index.md +32 -0
  109. package/reference/streams/{pipeline.md → core/pipeline.md} +210 -74
  110. package/reference/streams/{sink.md → core/sink.md} +331 -353
  111. package/reference/streams/{stream.md → core/stream.md} +919 -209
  112. package/reference/streams/execution-and-compatibility/async-execution.md +822 -0
  113. package/reference/streams/execution-and-compatibility/index.md +35 -0
  114. package/reference/streams/execution-and-compatibility/platform-differences.md +297 -0
  115. package/reference/streams/execution-and-compatibility/scala-2-compatibility.md +88 -0
  116. package/reference/streams/execution-and-compatibility/zero-boxing.md +393 -0
  117. package/reference/streams/index.md +140 -67
  118. package/reference/streams/primitives/index.md +30 -0
  119. package/reference/streams/primitives/reader.md +1992 -0
  120. package/reference/streams/{writer.md → primitives/writer.md} +254 -98
  121. package/reference/telemetry/common/any-value.md +90 -0
  122. package/reference/telemetry/common/attribute-key.md +87 -0
  123. package/reference/telemetry/common/attributes.md +118 -0
  124. package/reference/telemetry/common/index.md +39 -0
  125. package/reference/telemetry/common/instrumentation-scope.md +24 -0
  126. package/reference/telemetry/common/resource.md +34 -0
  127. package/reference/telemetry/index.md +311 -0
  128. package/reference/telemetry/logging/index.md +197 -0
  129. package/reference/telemetry/logging/log-enrichment.md +72 -0
  130. package/reference/telemetry/logging/log-formatter.md +100 -0
  131. package/reference/telemetry/logging/log-record-processor.md +56 -0
  132. package/reference/telemetry/logging/log-record.md +44 -0
  133. package/reference/telemetry/logging/log-writer.md +64 -0
  134. package/reference/telemetry/logging/logger-provider.md +142 -0
  135. package/reference/telemetry/logging/logger.md +83 -0
  136. package/reference/telemetry/logging/severity.md +62 -0
  137. package/reference/telemetry/metrics/index.md +150 -0
  138. package/reference/telemetry/metrics/instruments.md +183 -0
  139. package/reference/telemetry/metrics/labeled-instruments.md +74 -0
  140. package/reference/telemetry/metrics/meter-provider.md +76 -0
  141. package/reference/telemetry/metrics/meter.md +98 -0
  142. package/reference/telemetry/metrics/metric-data.md +57 -0
  143. package/reference/telemetry/otel/custom-exporter.md +216 -0
  144. package/reference/telemetry/otel/index.md +212 -0
  145. package/reference/telemetry/tracing/index.md +155 -0
  146. package/reference/telemetry/tracing/sampler.md +89 -0
  147. package/reference/telemetry/tracing/span-builder.md +57 -0
  148. package/reference/telemetry/tracing/span-context.md +39 -0
  149. package/reference/telemetry/tracing/span-data.md +32 -0
  150. package/reference/telemetry/tracing/span-kind.md +55 -0
  151. package/reference/telemetry/tracing/span-processor.md +53 -0
  152. package/reference/telemetry/tracing/span-status.md +47 -0
  153. package/reference/telemetry/tracing/span.md +117 -0
  154. package/reference/telemetry/tracing/tracer-provider.md +91 -0
  155. package/reference/telemetry/tracing/tracer.md +52 -0
  156. package/reference/typeid.md +0 -64
  157. package/sidebars.js +150 -12
  158. package/undocumented-report.md +528 -270
  159. package/reference/config.md +0 -158
  160. package/reference/streams/concurrent-operators.md +0 -106
  161. package/reference/streams/reader.md +0 -1284
  162. package/reference/streams/scala-2-compatibility.md +0 -55
  163. package/reference/streams/zero-boxing.md +0 -275
  164. package/reference/telemetry.md +0 -693
@@ -0,0 +1,311 @@
1
+ ---
2
+ id: index
3
+ title: "Telemetry"
4
+ description: "Module reference index for the telemetry module: tracing, logging, and metrics with a unified provider API."
5
+ keywords:
6
+ - "Observability"
7
+ - "Distributed Tracing"
8
+ - "Structured Logging"
9
+ - "Application Metrics"
10
+ - "Trace Correlation"
11
+ ---
12
+
13
+ The telemetry module provides the three pillars of modern observability — distributed tracing, structured logging, and dimensional metrics — with a single coherent API aligned to the OpenTelemetry data model.
14
+
15
+ Three global entry points (`trace`, `log`, `metric`) work without any configuration. `trace` and `metric` delegate to an installed provider (`TracerProvider`, `MeterProvider`) that produces scope-specific instances (`Tracer`, `Meter`); `log` holds an installed `Logger` directly, which a `LoggerProvider` is one way to build.
16
+
17
+ The same metadata types — `Attributes`, `AttributeKey`, `Resource`, and `InstrumentationScope` — describe signals in all three pillars, and a shared `ContextStorage` enables automatic trace–log correlation out of the box.
18
+
19
+ The three-pillar structure mirrors the following shape:
20
+
21
+ ```scala
22
+ // Global entry points — work without any setup
23
+ object trace { /* span creation, provider install */ }
24
+ object log { /* structured log emission, writer config */ }
25
+ object metric { /* instrument factories, reader access */ }
26
+
27
+ // Provider layer — configure and install once at application startup
28
+ final class TracerProvider private[telemetry] (...) extends AutoCloseable
29
+ final class LoggerProvider private[telemetry] (...) extends AutoCloseable
30
+ final class MeterProvider private[telemetry] (...) extends AutoCloseable
31
+
32
+ // Scope-specific instances — created by providers on demand
33
+ final class Tracer private[telemetry] (...)
34
+ final class Logger private[telemetry] (...)
35
+ final class Meter private[telemetry] (...)
36
+ ```
37
+
38
+ ## Motivation
39
+
40
+ Modern services produce three distinct kinds of observability data. Traces capture the causal shape of a request as it traverses services and threads. Logs capture structured events at discrete points in time, ideally correlated to the active trace. Metrics capture aggregated numerical measurements — request counts, latencies, queue depths — that accumulate over time. The telemetry module covers all three in one library that pulls in no third-party dependencies and cross-compiles for the JVM and Scala.js.
41
+
42
+ ## Installation
43
+
44
+ ```scala
45
+ // Core: logging, tracing, metrics
46
+ libraryDependencies += "dev.zio" %% "zio-blocks-telemetry" % "0.0.55"
47
+
48
+ // Optional: OTLP JSON export over HTTP (JVM only)
49
+ libraryDependencies += "dev.zio" %% "zio-blocks-telemetry-otel" % "0.0.55"
50
+ ```
51
+
52
+ For Scala.js (telemetry core only):
53
+
54
+ ```scala
55
+ libraryDependencies += "dev.zio" %%% "zio-blocks-telemetry" % "0.0.55"
56
+ ```
57
+
58
+ Supports Scala 2.13.x and 3.x.
59
+
60
+
61
+ ## Overview
62
+
63
+ The module is organized into three areas — tracing, logging, and metrics — that map directly to the OTel signal types, plus the metadata types all three rely on.
64
+
65
+ ### Tracing
66
+
67
+ [Tracing](./tracing/index.md) covers the full span lifecycle. `TracerProvider` is a factory that shares a `Resource`, a `Sampler`, and a set of `SpanProcessor` hooks across all `Tracer` instances it creates. A `Tracer` opens and closes `Span` scopes, consulting the `Sampler` on each new span and calling `SpanProcessor.onStart` and `SpanProcessor.onEnd` for exporting or in-memory collection. Supporting types — `SpanContext`, `SpanData`, `SpanKind`, `SpanStatus`, `SpanId`, `TraceId`, and `TraceFlags` — capture the identity and state of a span.
68
+
69
+ ### Logging
70
+
71
+ [Logging](./logging/index.md) covers structured, severity-leveled log emission. `LoggerProvider` is a factory that shares a `Resource` and a set of `LogRecordProcessor` hooks across all `Logger` instances it creates. A `Logger` emits `LogRecord` snapshots through configured processors; processors can write formatted output via a `LogFormatter` and `LogWriter`, export records to an external system, or gate emission by `Severity`. The macro-generated methods on the global `log` object capture source location at compile time and automatically stamp the active span context into every emitted record.
72
+
73
+ ### Metrics
74
+
75
+ [Metrics](./metrics/index.md) covers dimensional cumulative and instantaneous measurements. `MeterProvider` is a factory for `Meter` instances; each `Meter` creates and registers instruments — `Counter`, `UpDownCounter`, `Histogram`, and `Gauge` — and each instrument keeps one series per `Attributes` set it sees. Instruments themselves are not cached by name; build each once and hold it. A `MetricReader` collects all registered instruments into `MetricData` snapshots on demand for export or inspection.
76
+
77
+ ### Common Types
78
+
79
+ A span, a log record, and a measurement are different kinds of data, but they answer the same three questions: what happened, which service produced it, and which component inside that service. Four types carry those answers, and because all three pillars use the same four, whatever you learn about them applies everywhere:
80
+
81
+ - [`Attributes`](./common/attributes.md) — the detail on a signal, as typed key-value pairs: the route on a span, the order id on a log record, the labels on a measurement.
82
+ - [`AttributeKey`](./common/attribute-key.md) — a name paired with the type of its value, so reading an attribute back gives you a `String` or a `Long` rather than something to cast.
83
+ - [`Resource`](./common/resource.md) — which service produced the signal, attached to every one the provider emits. Give all three providers the same one.
84
+ - [`InstrumentationScope`](./common/instrumentation-scope.md) — which library or component produced it, taken from the name you pass to `TracerProvider.get`, `LoggerProvider.get`, or `MeterProvider.get`.
85
+
86
+ ## How They Work Together
87
+
88
+ All three pillars follow the same structural pattern: a global singleton wraps a provider that acts as a factory for lightweight, scope-specific instances. The provider holds the shared configuration — `Resource`, processor lists, and `ContextStorage` — and creates instances on demand. Signals are enriched by `Attributes` and labeled by `InstrumentationScope`. The `ContextStorage[Option[SpanContext]]` instance that `TracerProvider` and `LoggerProvider` share is what makes trace–log correlation automatic. Give all three providers the *same* `Resource` rather than one each — identical service-identity attributes on every signal are what let a backend line up traces, logs, and metrics as one service.
89
+
90
+ ```
91
+ Global entry points Provider layer Scope instances
92
+ ┌─────────────────────────┐ ┌──────────────────┐
93
+ │ TracerProvider │──▶│ Tracer │──▶ Span
94
+ ┌──────────────────────┐ │ · Resource │ └──────────────────┘ │ SpanProcessor
95
+ │ trace (object) │──▶│ · Sampler │ ContextStorage ◀╴╴┤
96
+ └──────────────────────┘ │ · SpanProcessors │ │ (shared)
97
+ │ · ContextStorage ╶╶╶╶╶╶┼╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╯
98
+ └─────────────────────────┘ ┌──────────────────┐
99
+ ┌─────────────────────────┐ │ Logger │
100
+ ┌──────────────────────┐ │ LoggerProvider │ │ (traceId+spanId │──▶ LogRecord
101
+ │ log (object) │──▶│ · Resource │──▶│ from storage) │ LogRecordProcessor
102
+ └──────────────────────┘ │ · LogRecordProcessor │ └──────────────────┘
103
+ │ · ContextStorage ╶╶╶╶╶╶┼╶╶╶╶╶╶╶(same instance)
104
+ └─────────────────────────┘
105
+ ┌─────────────────────────┐ ┌──────────────────┐
106
+ ┌──────────────────────┐ │ MeterProvider │ │ Meter │──▶ Counter
107
+ │ metric (object) │──▶│ · Resource │──▶│ · counterBuilder│ Histogram
108
+ └──────────────────────┘ │ · MeterRegistry │ │ · histogramBld. │ Gauge
109
+ └─────────────────────────┘ └──────────────────┘ UpDownCounter
110
+ │
111
+ MetricReader ──▶ MetricData
112
+
113
+ ──────────────── Shared across all three pillars ─────────────────────────────────
114
+ Attributes · AttributeKey · Resource · InstrumentationScope
115
+ ```
116
+
117
+ The following sub-sections walk through the data flow for each pillar and explain the correlation mechanism.
118
+
119
+ ### Tracing Data Flow
120
+
121
+ When application code calls `trace.span("operation")`, the following sequence occurs:
122
+
123
+ 1. The global `trace` object delegates to the active `TracerProvider`, which creates or retrieves a `Tracer` for the `"default"` instrumentation scope.
124
+ 2. The `Tracer` consults its `Sampler` via `shouldSample`. If the decision is `RecordAndSample`, a `RecordingSpan` is constructed via `SpanBuilder.startSpan` and `SpanProcessor.onStart` is called; if `Drop`, a `Span.NoOp` is returned — the span records nothing and reaches no processor, though a `SpanContext` is still allocated and scoped so nested spans and log records stay on the same trace.
125
+ 3. The active `SpanContext` is stored into `ContextStorage` for the duration of the user block so that nested spans can read it as their parent.
126
+ 4. On exit, `Span.end()` is called, `SpanData` is snapshotted, and `SpanProcessor.onEnd` fires — delivering the completed span to exporters or the in-memory test processor.
127
+
128
+ The following example shows a minimal production tracing configuration:
129
+
130
+ ```scala
131
+ import zio.blocks.telemetry._
132
+
133
+ val tracerProvider = TracerProvider.builder
134
+ .setResource(Resource.create(Attributes.of(Attributes.ServiceName, "order-service")))
135
+ .setSampler(AlwaysOnSampler)
136
+ .build()
137
+
138
+ trace.install(tracerProvider)
139
+
140
+ trace.span("process-order", SpanKind.Server) { span =>
141
+ span.setAttribute("order.id", "ord-123")
142
+ span.addEvent("validation-passed")
143
+ }
144
+ ```
145
+
146
+ ### Logging Data Flow
147
+
148
+ When application code calls `log.info("message", ...)`, the macro captures the call site's source file, enclosing class, method name, and line number at compile time. At runtime the severity is compared against the configured minimum; if the call passes the threshold, the captured source-location attributes are written into an `AttributesBuilder`, the current `SpanContext` is read from the shared `ContextStorage`, and `traceIdHi`, `traceIdLo`, `spanId`, and `traceFlags` are stamped into the record as unboxed primitives before being dispatched to all configured processors.
149
+
150
+ The following example routes log output to standard output in human-readable text format:
151
+
152
+ ```scala
153
+ import zio.blocks.telemetry._
154
+
155
+ log.writer(TextLogFormatter, StdoutWriter)
156
+ log.info("order placed", "orderId" -> "ord-123", "amount" -> 99L)
157
+ ```
158
+
159
+ ### Metrics Data Flow
160
+
161
+ When a `Counter`, `Histogram`, `Gauge`, or `UpDownCounter` is created via `Meter.counterBuilder("name").build()`, the instrument is registered in the owning `Meter`'s internal instrument list. The `MeterProvider.reader` property returns a `MetricReader` that, on each `collectAllMetrics()` call, iterates every registered `Meter` and invokes `collect()` on each instrument, aggregating the results into a sequence of `MetricData` variants for export or inspection.
162
+
163
+ The following example records request counts and then reads the aggregated snapshot:
164
+
165
+ ```scala
166
+ import zio.blocks.telemetry._
167
+
168
+ val requests = metric.counter("http.requests")
169
+ requests.add(1, "method" -> "GET", "status" -> "200")
170
+
171
+ val snapshots = metric.reader.collectAllMetrics()
172
+ snapshots.foreach {
173
+ case MetricData.SumData(points) => points.foreach(p => println(p.value))
174
+ case MetricData.HistogramData(points) => points.foreach(p => println(p.count))
175
+ case MetricData.GaugeData(points) => points.foreach(p => println(p.value))
176
+ }
177
+ ```
178
+
179
+ ### Trace–Log Correlation
180
+
181
+ `TracerProvider` and `LoggerProvider` share the same `ContextStorage[Option[SpanContext]]` instance. When a `Tracer` opens a span it stores the `SpanContext` in that storage; when `Logger` emits a record it reads the same storage and stamps `traceIdHi`, `traceIdLo`, `spanId`, and `traceFlags` into the `LogRecord` fields as primitives — no boxing, no formatting until a `LogFormatter` runs. Because both providers default to the same internal `defaultSpanContextStorage` singleton, correlation is automatic when both are installed with their default settings.
182
+
183
+ To use an explicit shared `ContextStorage` — for example, to isolate correlation in tests — create one instance and pass it to both builders:
184
+
185
+ ```scala
186
+ import zio.blocks.telemetry._
187
+
188
+ val sharedStorage = ContextStorage.create[Option[SpanContext]](None)
189
+
190
+ val tp = TracerProvider.builder.setContextStorage(sharedStorage).build()
191
+ val lp = LoggerProvider.builder.setContextStorage(sharedStorage).build()
192
+
193
+ trace.install(tp)
194
+ log.install(lp.get("com.example"))
195
+ ```
196
+
197
+ ## Common Patterns
198
+
199
+ The following patterns appear consistently across services that use this module. Each fits into a shared startup and request-handling lifecycle.
200
+
201
+ ### Zero-Setup Global API
202
+
203
+ All three global entry points work immediately after import: `trace` buffers spans in memory, `log` prints readable text to stdout, and `metric` writes to an in-process `MeterProvider`. The following import is the only requirement for development and testing:
204
+
205
+ ```scala
206
+ import zio.blocks.telemetry._
207
+
208
+ trace.span("bootstrap") { span =>
209
+ span.setAttribute("step", "init")
210
+ }
211
+
212
+ log.writer(TextLogFormatter, StdoutWriter)
213
+ log.info("server started", "port" -> 8080L)
214
+
215
+ metric.counter("startup.count").add(1)
216
+ ```
217
+
218
+ ### Production Provider Installation
219
+
220
+ At application startup we configure all three providers once and install them before serving any requests. Sharing a common `Resource` propagates the service identity through all signals:
221
+
222
+ ```scala
223
+ import zio.blocks.telemetry._
224
+
225
+ val serviceResource = Resource.default.merge(
226
+ Resource.create(Attributes.of(Attributes.ServiceName, "payments"))
227
+ )
228
+
229
+ val tp = TracerProvider.builder
230
+ .setResource(serviceResource)
231
+ .setSampler(ParentBasedSampler(AlwaysOnSampler))
232
+ .build()
233
+
234
+ val lp = LoggerProvider.builder.setResource(serviceResource).build()
235
+ val mp = MeterProvider.builder.setResource(serviceResource).build()
236
+
237
+ trace.install(tp)
238
+ log.install(lp.get("com.example.payments"))
239
+ metric.install(mp)
240
+ ```
241
+
242
+ ### Library Dependency-Injection Pattern
243
+
244
+ Library code should accept `Tracer` and `Logger` as constructor parameters rather than using the global singletons. This makes the library testable in isolation and avoids coupling to global state. The `Logger.info` method on the class itself takes `(String, AttributeValue)` pairs rather than the macro enrichment tuples used by the global `log` object:
245
+
246
+ ```scala
247
+ import zio.blocks.telemetry._
248
+
249
+ final class OrderService(tracer: Tracer, logger: Logger) {
250
+ def placeOrder(id: String): Unit =
251
+ tracer.span("orders.place") { span =>
252
+ span.setAttribute("order.id", id)
253
+ logger.info("order placed", "orderId" -> AttributeValue.StringValue(id))
254
+ }
255
+ }
256
+ ```
257
+
258
+ Application startup code injects instances by calling `trace.get("com.example.orders")` and `loggerProvider.get("com.example.orders")`.
259
+
260
+ ### Scoped Log Annotations
261
+
262
+ `log.annotated` propagates key-value pairs to every `log.*` call within the block. Annotations are scoped to the enclosing thread and do not leak outside it:
263
+
264
+ ```scala
265
+ import zio.blocks.telemetry._
266
+
267
+ def handleRequest(requestId: String): Unit =
268
+ log.annotated("requestId" -> requestId, "env" -> "prod") {
269
+ log.info("processing started")
270
+ log.info("processing done")
271
+ }
272
+ ```
273
+
274
+ ### Rate-Limited Logging
275
+
276
+ The `*Every` family of log methods emits at most once every N invocations per call site. The counter is keyed by the call site's file and line, so sites with the same interval value are normally independent — though the keys index a fixed 4096-slot table, so two sites can collide and share one counter:
277
+
278
+ ```scala
279
+ import zio.blocks.telemetry._
280
+
281
+ def heartbeat(): Unit =
282
+ log.infoEvery(100, "heartbeat tick")
283
+ ```
284
+
285
+ ### Labeled Instruments
286
+
287
+ `Meter.labeledCounter`, `Meter.labeledHistogram`, and `Meter.labeledGauge` declare label names once and accept positional string values at recording time, matching the Prometheus-style label pattern:
288
+
289
+ ```scala
290
+ import zio.blocks.telemetry._
291
+
292
+ val meter = metric.get("com.example")
293
+ val requests = meter.labeledCounter("http.requests", "method", "status")
294
+
295
+ requests.add(1, "GET", "200")
296
+ requests.add(1, "POST", "500")
297
+ ```
298
+
299
+ ## Integration Points
300
+
301
+ Scoped, thread-safe propagation of `SpanContext` through call stacks is handled by the module's own `ContextStorage`, which is backed by JDK `ScopedValue` on the JVM and by a saved-and-restored variable on Scala.js. At the build level the module depends only on the `context` and `chunk` blocks and requires no external libraries.
302
+
303
+ `SpanProcessor` and `LogRecordProcessor` are open traits. External exporters — for example, an OpenTelemetry SDK bridge — implement these traits to receive span and log data and forward it to collection infrastructure such as OTLP endpoints. Plug a custom `SpanProcessor` into `TracerProvider.builder.addSpanProcessor(...)` or a custom `LogRecordProcessor` into `LoggerProvider.builder.addLogRecordProcessor(...)`.
304
+
305
+ `LogEnrichment` is a typeclass resolved at compile time by the macro-generated `log.*` methods. Built-in instances cover `Throwable`, `Attributes`, `Severity`, and `(String, A)` pairs for `A ∈ {String, Long, Int, Double, Boolean}`. Adding support for a custom enrichment type requires only a new `implicit val LogEnrichment[MyType]` in scope at the `log.*` call site.
306
+
307
+ ## See Also
308
+
309
+ - [Telemetry Guide](../../guides/telemetry-guide.md) — architecture, design trade-offs, and real-world usage patterns
310
+ - [Common Types](./common/index.md) — `Attributes`, `AttributeKey`, `Resource`, and `InstrumentationScope` shared across all three pillars
311
+ - [Context](../context.md) — `Context[R]`, which the otel module's `OtelContext` carries an active `SpanContext` through
@@ -0,0 +1,197 @@
1
+ ---
2
+ id: index
3
+ title: "Logging"
4
+ description: "Logging index: the log entry point, LoggerProvider, Logger, LogRecord, and supporting types for structured logging in the telemetry module."
5
+ keywords:
6
+ - "Structured Logging"
7
+ - "Trace Correlation"
8
+ - "Logging Overview"
9
+ - "LoggerProvider"
10
+ sidebar_label: "Logging"
11
+ ---
12
+
13
+ Logging records what your application did as it runs, so you can understand its behavior and diagnose problems after the fact. Unlike a plain `println`, these logs are **structured** — each entry carries typed key/value fields (an order id, a duration) you can search and filter on, not just a line of text — and **severity-leveled**, tagged `trace`, `debug`, `info`, `warn`, `error`, or `fatal` so you can keep production quiet and turn up detail only when debugging. Each log is also tied back to the trace that produced it, so from one log line you can pull up the whole request it belongs to.
14
+
15
+ You write logs through the `log` object: call `log.info("order placed")` (or `debug`, `warn`, `error`, …) anywhere in your code and it works immediately, with no setup. Behind each call, `log` automatically records where the log came from — the file, class, method, and line — and stamps it with the trace and span of whatever [`trace.span`](../tracing/index.md) you happen to be inside. That stamping is what lets logs and traces line up later: no need to thread a request id through your code by hand.
16
+
17
+ Out of the box those records print to stdout as readable text, so `log.info(...)` shows up on your console with nothing configured. When you want a different destination you add one once, at application startup: `log.writer(...)` adds a channel with a formatter of your choosing — JSON, say — through a [`LogWriter`](./log-writer.md), while `log.addProcessor(...)` wires up a pipeline that ships records to a backend for storage and search. Those two are additive, so the default console output stays alongside whatever you add. `log.install(...)` is the replacing move: it swaps in a whole logger, dropping the default console processor, any writers added earlier, and any per-package severity overrides.
18
+
19
+ After the message you attach context, and this is where structured logging pays off. The common case is key/value pairs — `log.info("order placed", "orderId" -> "ord-123", "amount" -> 99L)` — which become searchable fields on that entry instead of being buried in the text, so later you can query "all logs where `orderId = ord-123`". Pass a `Throwable` and its type, message, and stack trace are captured for you.
20
+
21
+ A few other values mean something specific rather than becoming a field: a [`Severity`](./severity.md) overrides the entry's level, an [`Attributes`](../common/attributes.md) set adds many fields at once, and a plain `String` replaces the message body. And when you want to log one of your own types directly, give it a [`LogEnrichment`](./log-enrichment.md) instance that tells `log` how to turn it into fields.
22
+
23
+
24
+ ## Example Usage
25
+
26
+ Logging's core job is to **emit structured, correlated logs**. Point `log` at a writer once, then emit records inside your spans; each record carries its typed key-value context and the active trace and span IDs automatically.
27
+
28
+ ```scala
29
+ import zio.blocks.telemetry._
30
+
31
+ trace.install(TracerProvider.builder.build())
32
+ log.writer(TextLogFormatter, StdoutWriter)
33
+
34
+ trace.span("checkout") { _ =>
35
+ log.info("order placed", "orderId" -> "ord-123", "amount" -> 99L)
36
+ log.warn("inventory low", "sku" -> "sku-42", "remaining" -> 3L)
37
+ }
38
+ ```
39
+
40
+ ## Emit Structured, Severity-Leveled Records
41
+
42
+ Six severities — `trace`, `debug`, `info`, `warn`, `error`, `fatal` — cover the whole scale.
43
+
44
+ ```scala
45
+ object log {
46
+ def trace(message: String, enrichments: Any*): Unit
47
+ def debug(message: String, enrichments: Any*): Unit
48
+ def info(message: String, enrichments: Any*): Unit
49
+ def warn(message: String, enrichments: Any*): Unit
50
+ def error(message: String, enrichments: Any*): Unit
51
+ def fatal(message: String, enrichments: Any*): Unit
52
+ }
53
+ ```
54
+
55
+ Pass typed key/value pairs for structured attributes, a `Throwable` to capture an exception, or an `Attributes` set to merge many values at once.
56
+
57
+ ```scala
58
+ import zio.blocks.telemetry._
59
+
60
+ log.info("order placed", "orderId" -> "ord-123", "amount" -> 99L, "express" -> true)
61
+ log.debug("cache lookup", "hit" -> false)
62
+
63
+ try throw new RuntimeException("payment declined")
64
+ catch { case e: Throwable => log.error("charge failed", "orderId" -> "ord-123", e) }
65
+ ```
66
+
67
+ ## Limit Log Volume at Hot Call Sites
68
+
69
+ Two rate-limiting families, each spanning all six severities, keep high-frequency sites quiet.
70
+
71
+ ```scala
72
+ object log {
73
+ def <level>Every(every: Int, message: String, enrichments: Any*): Unit
74
+ def <level>AtMost(intervalMillis: Long, message: String, enrichments: Any*): Unit
75
+ }
76
+ ```
77
+
78
+ The `Every` family is count-based — `<level>Every(every, message, enrichments*)` emits on every Nth call at that site. The `AtMost` family is time-based — `<level>AtMost(intervalMillis, message, enrichments*)` emits at most once per interval at that site. Each site gets its own counter and clock, keyed by its file and line into a fixed table of 4096 slots; two distant sites can land in the same slot, in which case they share a counter and one of them logs less often than its `every` suggests.
79
+
80
+ ```scala
81
+ import zio.blocks.telemetry._
82
+
83
+ // Count-based: one line for every 100th retry
84
+ log.warnEvery(100, "retrying upstream call", "endpoint" -> "/inventory")
85
+
86
+ // Time-based: at most one line per 5 seconds, whatever the call rate
87
+ log.infoAtMost(5000L, "processing batch", "size" -> 512L)
88
+ log.errorAtMost(1000L, "connection pool exhausted")
89
+ ```
90
+
91
+ ## Attach Scoped Annotations
92
+
93
+ Attach key/value pairs to every record emitted inside a block with `annotated`.
94
+
95
+ ```scala
96
+ object log {
97
+ def annotated[A](annotations: (String, String)*)(f: => A): A
98
+ }
99
+ ```
100
+
101
+ The pairs reach every record from the block, including calls in nested methods, without threading them through each `log.*` call.
102
+
103
+ ```scala
104
+ import zio.blocks.telemetry._
105
+
106
+ log.annotated("requestId" -> "req-42", "tenant" -> "acme") {
107
+ log.info("started") // both annotations attached
108
+ log.info("finished") // both annotations attached
109
+ }
110
+ ```
111
+
112
+ ## Correlate Logs with the Active Span
113
+
114
+ When a `log.*` call runs inside a `trace.span`, the record is stamped with the enclosing span's trace and span IDs automatically, because logging and tracing share the same `ContextStorage`. No extra wiring is required.
115
+
116
+ ```scala
117
+ import zio.blocks.telemetry._
118
+
119
+ trace.span("checkout") { _ =>
120
+ log.info("order validated", "orderId" -> "ord-123") // carries the checkout span's IDs
121
+ }
122
+ ```
123
+
124
+ ## Filter by Severity
125
+
126
+ Every log carries a `Severity`, and a **minimum-severity floor** decides which ones actually get recorded: anything below the floor is dropped — cheaply, before the record is even built. The floor starts at `Trace`, so everything passes until you raise it. You use this to control noise: run production at `Info` (dropping the `trace`/`debug` chatter) and turn detail back up only where and when you need it.
127
+
128
+ ```scala
129
+ object log {
130
+ def setMinSeverity(severity: Severity): Unit
131
+ def setMinSeverity(prefix: String, severity: Severity): Unit
132
+ def clearMinSeverity(prefix: String): Unit
133
+ def clearAllOverrides(): Unit
134
+ def withMinSeverity[A](severity: Severity)(f: => A): A
135
+ }
136
+ ```
137
+
138
+ `setMinSeverity(severity)` sets one floor for the whole application. `setMinSeverity(prefix, severity)` overrides it for a package — matched against the call site's namespace — and works both ways: raise the floor on a chatty dependency to quiet it, or lower it on the package you're debugging to see more, without touching the rest of the app. `clearMinSeverity(prefix)` removes one override and `clearAllOverrides()` removes them all, back to the global floor. Set the global floor first: `setMinSeverity(severity)` also discards every prefix override, so calling it after the overrides silently wipes them.
139
+
140
+ When several prefixes match a call site, the longest one wins — so `"com.example"` at `Warn` plus `"com.example.orders"` at `Debug` gives you debug logs from the orders package and warnings from everything else under `com.example`. Matching is a plain string prefix on the enclosing class name, not a package-boundary check, so `"com.acme"` also matches `com.acmecorp`.
141
+
142
+ ```scala
143
+ import zio.blocks.telemetry._
144
+
145
+ log.setMinSeverity(Severity.Info) // drop trace/debug globally
146
+ log.setMinSeverity("com.acme.noisy", Severity.Warn) // quiet a chatty dependency
147
+ log.setMinSeverity("com.example.orders", Severity.Debug) // more detail where you're debugging
148
+ ```
149
+
150
+ These floors stay in effect until you change them. When you only need extra detail around a specific operation, `withMinSeverity(severity) { … }` lowers the floor for just that block and restores it afterward — no cleanup needed.
151
+
152
+ ```scala
153
+ import zio.blocks.telemetry._
154
+
155
+ log.withMinSeverity(Severity.Trace) {
156
+ log.trace("visible only inside this block")
157
+ }
158
+ ```
159
+
160
+ ## Route Output
161
+
162
+ A record isn't useful until it leaves the process. Routing decides two things: how each record becomes text — a [`LogFormatter`](./log-formatter.md), plain lines or JSON — and where those bytes go — a `LogWriter`, such as stdout, stderr, or a file. There are two ways to wire this up: a simple console writer for local development, or a processor pipeline for production.
163
+
164
+ ```scala
165
+ object log {
166
+ def writer(formatter: LogFormatter, logWriter: LogWriter): Unit
167
+ def clearWriters(): Unit
168
+ def install(logger: Logger, minSeverity: Severity = Severity.Trace): Unit
169
+ def addProcessor(processor: LogRecordProcessor): Unit
170
+ def removeAll(): Unit
171
+ }
172
+ ```
173
+
174
+ `log.writer(formatter, writer)` is the simple path: pair a formatter with a writer to add one console sink. It's additive — call it again to send the same records to a second destination (say, human-readable text to stdout and JSON to stderr) — and `log.clearWriters()` removes them all.
175
+
176
+ For production you usually want a pipeline that batches records and ships them to a backend for storage and search. `log.install(logger)` swaps in a fully configured [`Logger`](./logger.md) (built from a `LoggerProvider` with its export processors) in place of whatever was registered before — including the default console output, any writers, and any prefix overrides — `log.addProcessor(processor)` appends a single [`LogRecordProcessor`](./log-record-processor.md) to the current backend without disturbing the rest, and `log.removeAll()` detaches everything — `log.*` calls become no-ops until you add an output again.
177
+
178
+ ```scala
179
+ import zio.blocks.telemetry._
180
+
181
+ // Human-readable text to stdout, JSON to stderr
182
+ log.writer(TextLogFormatter, StdoutWriter)
183
+ log.writer(JsonLogFormatter, StderrWriter)
184
+
185
+ // Or install a processor-based backend
186
+ val logger = LoggerProvider.builder
187
+ .addLogRecordProcessor(new ConsoleLogRecordProcessor)
188
+ .build()
189
+ .get("com.example")
190
+ log.install(logger, Severity.Info)
191
+ ```
192
+
193
+ ## See Also
194
+
195
+ - [Telemetry Guide](../../../guides/telemetry-guide.md) — logging data flow, rate limiting, and production patterns
196
+ - [Telemetry Reference](../index.md) — module overview and all three pillars
197
+ - [Common Types](../common/index.md) — `Attributes`, `AttributeKey`, `Resource`, and `InstrumentationScope`
@@ -0,0 +1,72 @@
1
+ ---
2
+ id: log-enrichment
3
+ title: "LogEnrichment"
4
+ description: "Typeclass that lets a log.* call accept a domain value directly"
5
+ keywords:
6
+ - "Structured Logging"
7
+ - "Log Enrichment"
8
+ - "Compile-Time Typeclass"
9
+ - "LogEnrichment"
10
+ sidebar_label: "LogEnrichment"
11
+ ---
12
+
13
+ `LogEnrichment[A]` is what lets you pass a value of your own type straight into a [`log`](./index.md) call and have it become part of the [`LogRecord`](./log-record.md). When the macro behind `log.info("msg", value)` meets an argument whose type it does not handle natively, it looks for an implicit `LogEnrichment[A]`, calls `enrich` to fold the value into the record, and fails to compile if none is in scope. You rarely name the typeclass directly — the built-in instances below cover the everyday types — you define one only to log a domain value without unpacking it by hand at every call site.
14
+
15
+ ```scala
16
+ trait LogEnrichment[A] {
17
+ def enrich(record: LogRecord, value: A): LogRecord
18
+ }
19
+ ```
20
+
21
+ `enrich` takes the record built so far and returns a copy with the value folded in — adding attributes, replacing the body, or setting the severity, depending on `A`.
22
+
23
+ ## Built-in Instances
24
+
25
+ | Type | Effect on the record |
26
+ |----------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------|
27
+ | `String` | Replaces the message body. |
28
+ | `Throwable` | Adds `exception.type` and `exception.message` attributes and stores the throwable for stack-trace rendering. |
29
+ | `Attributes` | Merges the whole set into the record's attributes. |
30
+ | `Severity` | Overrides the recorded severity — but not the one used for filtering (see below). |
31
+ | `(String, String)` / `(String, Long)` / `(String, Int)` / `(String, Double)` / `(String, Boolean)` | Adds one typed key-value attribute (`Int` is widened to `Long`). |
32
+
33
+ These common types are also recognized directly by the `log.*` macro, so passing them costs nothing at runtime:
34
+
35
+ ```scala
36
+ import zio.blocks.telemetry._
37
+
38
+ val ex: Throwable = new RuntimeException("connection refused")
39
+
40
+ log.error(
41
+ "payment failed",
42
+ ex, // Throwable — attaches type, message, stack trace
43
+ "orderId" -> "ord-123", // (String, String)
44
+ "amount" -> 99L, // (String, Long)
45
+ "retryable" -> false // (String, Boolean)
46
+ )
47
+ ```
48
+
49
+ Passing a `Severity` is the one case that behaves unexpectedly. It changes the severity written into the record, but filtering already happened using the severity of the method you called — so `log.warn("slow query", Severity.Debug)` is recorded as DEBUG even under an `Info` floor, and `log.info("charge failed", Severity.Error)` is dropped by a `Warn` floor despite arriving as an error. Call the method matching the level you want filtered on.
50
+
51
+ Types outside the table are rejected at compile time, which is what a missing `LogEnrichment` error means. `Float`, `Short`, `Byte`, `Char`, `UUID`, and `Instant` are the ones people hit first: convert them at the call site (`.toDouble`, `.toLong`, `.toString`) or give the type an instance of its own.
52
+
53
+ ## Custom Instances
54
+
55
+ Define a `LogEnrichment[MyType]` in implicit scope to accept your own type at a `log.*` call. The macro resolves it at that call site and threads the record through your `enrich`:
56
+
57
+ ```scala
58
+ import zio.blocks.telemetry._
59
+
60
+ final case class RequestId(value: String)
61
+
62
+ implicit val requestIdEnrichment: LogEnrichment[RequestId] = new LogEnrichment[RequestId] {
63
+ def enrich(record: LogRecord, value: RequestId): LogRecord =
64
+ record.copy(attributes = record.attributes ++ Attributes.of(AttributeKey.string("request.id"), value.value))
65
+ }
66
+
67
+ log.info("handling request", RequestId("req-001")) // adds request.id="req-001"
68
+ ```
69
+
70
+ ## Integration
71
+
72
+ Resolution happens entirely at compile time, so there is no runtime cost for type dispatch. An argument whose type has neither native macro handling nor an implicit `LogEnrichment[A]` is a compile error, which keeps unloggable values out of the call. A custom instance must be in implicit scope wherever the `log.*` call appears.