@zio.dev/zio-blocks 0.0.33 → 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 (215) 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 +21 -16
  4. package/guides/getting-started-with-mux.md +1395 -0
  5. package/guides/query-dsl-extending.md +161 -102
  6. package/guides/query-dsl-fluent-builder.md +217 -157
  7. package/guides/query-dsl-reified-optics.md +12 -10
  8. package/guides/query-dsl-sql.md +640 -165
  9. package/guides/sql-checked-interpolation.md +173 -0
  10. package/guides/sql-transactions.md +286 -0
  11. package/guides/telemetry-guide.md +1130 -0
  12. package/guides/zio-schema-migration.md +29 -22
  13. package/index.md +248 -389
  14. package/package.json +1 -1
  15. package/plans/config-follow-up-prs.md +188 -0
  16. package/plans/config-pr-assessment-roadmap.md +310 -0
  17. package/reference/MuxDataFlow.jsx +250 -0
  18. package/reference/async.md +1499 -0
  19. package/reference/chunk.md +3533 -308
  20. package/reference/codegen/case-class.md +436 -0
  21. package/reference/codegen/emitter-config.md +383 -0
  22. package/reference/codegen/examples.md +664 -0
  23. package/reference/codegen/field.md +316 -0
  24. package/reference/codegen/index.md +317 -0
  25. package/reference/codegen/scala-emitter.md +392 -0
  26. package/reference/codegen/scala-file.md +276 -0
  27. package/reference/codegen/sealed-trait.md +408 -0
  28. package/reference/codegen/type-definition.md +340 -0
  29. package/reference/codegen/type-ref.md +201 -0
  30. package/reference/combinators.md +347 -117
  31. package/reference/config/config-decoder.md +460 -0
  32. package/reference/config/config-source.md +489 -0
  33. package/reference/config/errors.md +278 -0
  34. package/reference/config/flags.md +369 -0
  35. package/reference/config/formats.md +314 -0
  36. package/reference/config/index.md +304 -0
  37. package/reference/config/rollout.md +336 -0
  38. package/reference/context.md +9 -52
  39. package/reference/data-migration.md +269 -0
  40. package/reference/datastar/attributes.md +302 -0
  41. package/reference/datastar/events.md +234 -0
  42. package/reference/datastar/index.md +256 -0
  43. package/reference/datastar/signals.md +230 -0
  44. package/reference/datastar/sse.md +295 -0
  45. package/reference/datastar.md +346 -0
  46. package/reference/docs.md +1461 -345
  47. package/reference/endpoint/auth-type.md +146 -0
  48. package/reference/endpoint/bulk-creation.md +96 -0
  49. package/reference/endpoint/endpoint.md +297 -0
  50. package/reference/endpoint/http-codec.md +249 -0
  51. package/reference/endpoint/index.md +745 -0
  52. package/reference/endpoint/path-codec.md +225 -0
  53. package/reference/endpoint/route-pattern.md +194 -0
  54. package/reference/endpoint/route-tree.md +111 -0
  55. package/reference/endpoint/segment-codec.md +199 -0
  56. package/reference/html.md +1424 -0
  57. package/reference/htmx/attribute-values.md +359 -0
  58. package/reference/htmx/hx-encoding.md +111 -0
  59. package/reference/htmx/hx-params.md +204 -0
  60. package/reference/htmx/hx-swap.md +276 -0
  61. package/reference/htmx/hx-sync.md +251 -0
  62. package/reference/htmx/hx-target.md +314 -0
  63. package/reference/htmx/hx-trigger.md +457 -0
  64. package/reference/htmx/hx-url-update.md +239 -0
  65. package/reference/htmx/index.md +807 -0
  66. package/reference/htmx/response-headers.md +240 -0
  67. package/reference/http-model/headers.md +735 -0
  68. package/reference/http-model/index.md +49 -0
  69. package/reference/http-model/model.md +1517 -0
  70. package/reference/http-model/schema-codecs.md +522 -0
  71. package/reference/http-model/schema.md +750 -0
  72. package/reference/http-model/server-sent-event.md +341 -0
  73. package/reference/jwt.md +195 -0
  74. package/reference/maybe.md +943 -0
  75. package/reference/media-type.md +2 -2
  76. package/reference/mux.md +254 -0
  77. package/reference/mux.mdx +828 -0
  78. package/reference/openapi.md +1351 -0
  79. package/reference/projection.md +654 -0
  80. package/reference/resource-management/defer-handle.md +1 -1
  81. package/reference/resource-management/resource.md +31 -98
  82. package/reference/resource-management/scope.md +28 -220
  83. package/reference/resource-management/wire.md +5 -55
  84. package/reference/ringbuffer/MpmcDiagram.jsx +717 -0
  85. package/reference/ringbuffer/MpscDiagram.jsx +618 -0
  86. package/reference/ringbuffer/SpmcDiagram.jsx +680 -0
  87. package/reference/ringbuffer/SpscDiagram.jsx +677 -0
  88. package/reference/ringbuffer/advanced.mdx +109 -0
  89. package/reference/ringbuffer/index.mdx +145 -0
  90. package/reference/ringbuffer/mpmc.mdx +185 -0
  91. package/reference/ringbuffer/mpsc.mdx +164 -0
  92. package/reference/ringbuffer/spmc.mdx +108 -0
  93. package/reference/ringbuffer/spsc.mdx +416 -0
  94. package/reference/{allows.md → schema/allows.md} +4 -100
  95. package/reference/{binding-resolver.md → schema/binding-resolver.md} +1 -1
  96. package/reference/{binding.md → schema/binding.md} +3 -4
  97. package/reference/schema/built-in-codecs/avro.md +451 -0
  98. package/reference/schema/built-in-codecs/bson.md +510 -0
  99. package/reference/schema/built-in-codecs/csv.md +564 -0
  100. package/reference/schema/built-in-codecs/index.md +77 -0
  101. package/reference/schema/built-in-codecs/json/index.md +295 -0
  102. package/reference/schema/built-in-codecs/json/json-config.md +217 -0
  103. package/reference/{json-patch.md → schema/built-in-codecs/json/json-patch.md} +5 -5
  104. package/reference/{json-schema.md → schema/built-in-codecs/json/json-schema.md} +14 -47
  105. package/reference/schema/built-in-codecs/json/json-selection.md +322 -0
  106. package/reference/{json.md → schema/built-in-codecs/json/json.md} +32 -64
  107. package/reference/schema/built-in-codecs/messagepack.md +508 -0
  108. package/reference/schema/built-in-codecs/thrift.md +433 -0
  109. package/reference/schema/built-in-codecs/toon.md +1078 -0
  110. package/reference/{xml.md → schema/built-in-codecs/xml.md} +13 -9
  111. package/reference/schema/built-in-codecs/yaml.md +552 -0
  112. package/reference/{codec.md → schema/codec.md} +11 -11
  113. package/reference/{dynamic-optic.md → schema/dynamic-optic.md} +196 -5
  114. package/reference/{dynamic-schema.md → schema/dynamic-schema.md} +8 -8
  115. package/reference/schema/format.md +92 -0
  116. package/reference/schema/index.md +52 -0
  117. package/reference/schema/migration.md +297 -0
  118. package/reference/{modifier.md → schema/modifier.md} +58 -7
  119. package/reference/{optics.md → schema/optics.md} +2 -2
  120. package/reference/{patch.md → schema/patch.md} +1 -1
  121. package/{path-interpolator.md → reference/schema/path-interpolator.md} +167 -72
  122. package/reference/schema/reflect-transformer.md +140 -0
  123. package/reference/{schema-evolution → schema/schema-evolution}/as.md +8 -8
  124. package/reference/{schema-evolution → schema/schema-evolution}/index.md +2 -2
  125. package/reference/{schema-evolution → schema/schema-evolution}/into.md +8 -8
  126. package/reference/{schema-expr.md → schema/schema-expr.md} +110 -175
  127. package/reference/schema/schema-search.md +263 -0
  128. package/reference/{schema.md → schema/schema.md} +22 -2
  129. package/reference/{structural-types.md → schema/structural-types.md} +1 -1
  130. package/reference/{type-class-derivation.md → schema/type-class-derivation.md} +63 -1
  131. package/reference/smithy.md +1032 -0
  132. package/reference/sql/db-codec-deriver.md +71 -0
  133. package/reference/sql/db-codec.md +687 -0
  134. package/reference/sql/db-con.md +271 -0
  135. package/reference/sql/db-connection.md +153 -0
  136. package/reference/sql/db-param-writer.md +77 -0
  137. package/reference/sql/db-param.md +66 -0
  138. package/reference/sql/db-result-reader.md +148 -0
  139. package/reference/sql/db-tx.md +114 -0
  140. package/reference/sql/db-value.md +41 -0
  141. package/reference/sql/ddl.md +85 -0
  142. package/reference/sql/frag.md +288 -0
  143. package/reference/sql/index.md +341 -0
  144. package/reference/sql/repo.md +600 -0
  145. package/reference/sql/sql-dialect.md +73 -0
  146. package/reference/sql/sql-logger.md +62 -0
  147. package/reference/sql/sql-name-mapper.md +70 -0
  148. package/reference/sql/table-metadata.md +134 -0
  149. package/reference/sql/table.md +448 -0
  150. package/reference/sql/transactor-zio.md +399 -0
  151. package/reference/sql/transactor.md +363 -0
  152. package/reference/sql-zio.md +112 -0
  153. package/reference/streams/core/index.md +32 -0
  154. package/reference/streams/core/pipeline.md +854 -0
  155. package/reference/streams/core/sink.md +1404 -0
  156. package/reference/streams/core/stream.md +3236 -0
  157. package/reference/streams/execution-and-compatibility/async-execution.md +822 -0
  158. package/reference/streams/execution-and-compatibility/index.md +35 -0
  159. package/reference/streams/execution-and-compatibility/platform-differences.md +297 -0
  160. package/reference/streams/execution-and-compatibility/scala-2-compatibility.md +88 -0
  161. package/reference/streams/execution-and-compatibility/zero-boxing.md +393 -0
  162. package/reference/streams/index.md +726 -0
  163. package/reference/streams/primitives/index.md +30 -0
  164. package/reference/streams/primitives/reader.md +1992 -0
  165. package/reference/streams/primitives/writer.md +1201 -0
  166. package/reference/telemetry/common/any-value.md +90 -0
  167. package/reference/telemetry/common/attribute-key.md +87 -0
  168. package/reference/telemetry/common/attributes.md +118 -0
  169. package/reference/telemetry/common/index.md +39 -0
  170. package/reference/telemetry/common/instrumentation-scope.md +24 -0
  171. package/reference/telemetry/common/resource.md +34 -0
  172. package/reference/telemetry/index.md +311 -0
  173. package/reference/telemetry/logging/index.md +197 -0
  174. package/reference/telemetry/logging/log-enrichment.md +72 -0
  175. package/reference/telemetry/logging/log-formatter.md +100 -0
  176. package/reference/telemetry/logging/log-record-processor.md +56 -0
  177. package/reference/telemetry/logging/log-record.md +44 -0
  178. package/reference/telemetry/logging/log-writer.md +64 -0
  179. package/reference/telemetry/logging/logger-provider.md +142 -0
  180. package/reference/telemetry/logging/logger.md +83 -0
  181. package/reference/telemetry/logging/severity.md +62 -0
  182. package/reference/telemetry/metrics/index.md +150 -0
  183. package/reference/telemetry/metrics/instruments.md +183 -0
  184. package/reference/telemetry/metrics/labeled-instruments.md +74 -0
  185. package/reference/telemetry/metrics/meter-provider.md +76 -0
  186. package/reference/telemetry/metrics/meter.md +98 -0
  187. package/reference/telemetry/metrics/metric-data.md +57 -0
  188. package/reference/telemetry/otel/custom-exporter.md +216 -0
  189. package/reference/telemetry/otel/index.md +212 -0
  190. package/reference/telemetry/tracing/index.md +155 -0
  191. package/reference/telemetry/tracing/sampler.md +89 -0
  192. package/reference/telemetry/tracing/span-builder.md +57 -0
  193. package/reference/telemetry/tracing/span-context.md +39 -0
  194. package/reference/telemetry/tracing/span-data.md +32 -0
  195. package/reference/telemetry/tracing/span-kind.md +55 -0
  196. package/reference/telemetry/tracing/span-processor.md +53 -0
  197. package/reference/telemetry/tracing/span-status.md +47 -0
  198. package/reference/telemetry/tracing/span.md +117 -0
  199. package/reference/telemetry/tracing/tracer-provider.md +91 -0
  200. package/reference/telemetry/tracing/tracer.md +52 -0
  201. package/reference/typeid.md +5 -83
  202. package/sidebars.js +376 -43
  203. package/undocumented-report.md +528 -270
  204. package/reference/formats.md +0 -694
  205. package/reference/http-model.md +0 -1716
  206. package/reference/streams.md +0 -989
  207. package/ringbuffer.md +0 -249
  208. /package/reference/{json-differ.md → schema/built-in-codecs/json/json-differ.md} +0 -0
  209. /package/reference/{dynamic-value.md → schema/dynamic-value.md} +0 -0
  210. /package/reference/{lazy.md → schema/lazy.md} +0 -0
  211. /package/reference/{reflect.md → schema/reflect.md} +0 -0
  212. /package/reference/{registers.md → schema/registers.md} +0 -0
  213. /package/reference/{schema-error.md → schema/schema-error.md} +0 -0
  214. /package/reference/{syntax.md → schema/syntax.md} +0 -0
  215. /package/reference/{validation.md → schema/validation.md} +0 -0
@@ -0,0 +1,150 @@
1
+ ---
2
+ id: index
3
+ title: "Metrics"
4
+ description: "Record how your application behaves over time — request rates, latencies, queue depths — as labeled counters, histograms, and gauges."
5
+ keywords:
6
+ - "Application Metrics"
7
+ - "Dimensional Metrics"
8
+ - "Metrics Overview"
9
+ - "MeterProvider"
10
+ sidebar_label: "Metrics"
11
+ ---
12
+
13
+ Metrics are the running numbers that tell you how your application is behaving over time — request rate, error count, latency, queue depth — the data you put on dashboards and alerts. They're **dimensional**: each measurement carries labels (like `method=GET`, `status=200`), and a metric splits into a separate series per label combination, so you can break "requests" down by endpoint or status. You record through four [instruments](./instruments.md), each for a different shape of number — a `Counter` for a total that only climbs, an `UpDownCounter` for one that rises and falls, a `Histogram` for a distribution of values, and a `Gauge` for the latest reading.
14
+
15
+ You record through the `metric` object: `metric.counter("http.requests").add(1)` works immediately, with no setup — it creates the named instrument and records the measurement. Pick the factory that matches what you're measuring — `metric.counter`, `metric.upDownCounter`, `metric.histogram`, or `metric.gauge` — and pass dimension labels as you record.
16
+
17
+ Measurements accumulate in memory, and reading them out is a pull: `metric.reader.collectAllMetrics()` returns [`MetricData`](./metric-data.md) snapshots — handy for tests and assertions, and the same call an exporter makes. Metrics have no push pipeline the way tracing and logging do: there is no processor hook on `MeterProvider`, so shipping them means scheduling that pull yourself and handing the snapshots to your backend. `metric.install(provider)` swaps in a provider configured with your own `Resource`.
18
+
19
+ Day to day you only need `metric.*`, but it helps to know what sits underneath, because each piece is where you go when you want more control. A [`MeterProvider`](./meter-provider.md) is what you build at startup: it holds your service identity as a `Resource` and vends everything else. (Unlike spans and log records, collected metric data does not carry that `Resource`, so an exporter has to attach the service identity itself.) A [`Meter`](./meter.md) is a named handle you take with `metric.get("com.example.orders")` when measurements should be attributed to one component, or when you want to declare an instrument's unit and description, or pre-declare label names for a hot path.
20
+
21
+ The other two are the recording and reading ends. The four [instruments](./instruments.md) are what you actually record through, however you obtained them. And the `MetricReader` behind `metric.reader` is what turns everything recorded so far into `MetricData` snapshots — the thing you call in tests, and the thing an exporter pulls from in production.
22
+
23
+ ## Usage
24
+
25
+ Metrics' core job is to **record and export application metrics**. Create instruments from `metric`, record measurements with dimension labels as work happens, then read aggregated snapshots (or `metric.install(...)` a provider to export them):
26
+
27
+ ```scala
28
+ import zio.blocks.telemetry._
29
+
30
+ val requests = metric.counter("http.requests")
31
+ requests.add(1, "method" -> "GET", "status" -> "200")
32
+
33
+ val latency = metric.histogram("http.latency.ms")
34
+ latency.record(12.5, "route" -> "/orders")
35
+
36
+ val snapshots = metric.reader.collectAllMetrics()
37
+ snapshots.foreach {
38
+ case MetricData.SumData(points) => points.foreach(p => println(p.value))
39
+ case MetricData.HistogramData(points) => points.foreach(p => println(p.count))
40
+ case MetricData.GaugeData(points) => points.foreach(p => println(p.value))
41
+ }
42
+ ```
43
+
44
+ ## Record Measurements with the Four Instruments
45
+
46
+ Each factory creates a new instrument on the default meter. Nothing is cached by name, so build each one once and hold it — calling `metric.counter("http.requests")` twice gives you two independent counters and two entries in the collected snapshot.
47
+
48
+ ```scala
49
+ object metric {
50
+ def counter(name: String): Counter
51
+ def upDownCounter(name: String): UpDownCounter
52
+ def histogram(name: String): Histogram
53
+ def gauge(name: String): Gauge
54
+ }
55
+ ```
56
+
57
+ The four instruments differ by what they measure: a `Counter` sums a monotonic total, an `UpDownCounter` tracks a value that rises and falls, a `Histogram` builds a distribution of recorded values, and a `Gauge` holds the latest instantaneous reading. Every recording call accepts dimension labels as `(String, Any)*` pairs, which split the metric into separately-aggregated series.
58
+
59
+ ```scala
60
+ import zio.blocks.telemetry._
61
+
62
+ metric.counter("http.requests").add(1, "method" -> "GET", "status" -> "200")
63
+ metric.upDownCounter("queue.depth").add(-1, "queue" -> "orders")
64
+ metric.histogram("http.latency.ms").record(12.5, "route" -> "/orders")
65
+ metric.gauge("cache.entries").record(4096.0, "cache" -> "sessions")
66
+ ```
67
+
68
+ Note the two verbs. `add(delta: Long)` takes a *change* and the instrument keeps the running total — a `Counter` ignores a negative delta, while an `UpDownCounter` accepts one, so `add(1)`/`add(-1)` tracks queue depth. `record(value: Double)` takes the *measurement itself*: a `Histogram` files each value into buckets, and a `Gauge` keeps only the newest.
69
+
70
+ ## Scope a Meter and Pre-Declare Labels
71
+
72
+ `metric.get(name)` returns a `Meter` bound to a named instrumentation scope.
73
+
74
+ ```scala
75
+ object metric {
76
+ def get(name: String): Meter
77
+ }
78
+ ```
79
+
80
+ Its builders attach a description and unit, and its labeled variants pre-declare label names so a hot path records positionally instead of naming a key at every call.
81
+
82
+ ```scala
83
+ import zio.blocks.telemetry._
84
+
85
+ val meter: Meter = metric.get("com.example.orders")
86
+
87
+ val payments: Counter =
88
+ meter.counterBuilder("payments.total").setUnit("{payment}").setDescription("Payments processed").build()
89
+ payments.add(1, "currency" -> "USD")
90
+
91
+ val byRoute: LabeledCounter = meter.labeledCounter("http.requests", "method", "status")
92
+ byRoute.add(1, "GET", "200")
93
+ ```
94
+
95
+ `setUnit` labels what the numbers mean, so a dashboard can axis-label `http.latency` as milliseconds rather than guessing. Use the UCUM codes OpenTelemetry expects — `"ms"`, `"s"`, `"By"` for bytes — and for a plain count of things, the thing in braces: `"{payment}"`, `"{request}"`. It's metadata attached to the instrument, not part of any measurement, and it stays on the instrument: the snapshots `metric.reader` returns carry only data points, so a unit reaches a backend through an exporter, not through `MetricData`.
96
+
97
+ ## Read Aggregated Snapshots
98
+
99
+ Read every registered instrument as a `MetricData` snapshot — a fresh value each call, though a histogram point hands you its `boundaries` and `bucketCounts` arrays directly, so treat them as read-only.
100
+
101
+ ```scala
102
+ object metric {
103
+ def reader: MetricReader
104
+ }
105
+ ```
106
+
107
+ `metric.reader.collectAllMetrics()` aggregates them — `SumData` for counters, `HistogramData` for histograms, `GaugeData` for gauges — for inspection in tests or manual export.
108
+
109
+ ```scala
110
+ import zio.blocks.telemetry._
111
+
112
+ val snapshots: Seq[MetricData] = metric.reader.collectAllMetrics()
113
+ snapshots.foreach {
114
+ case MetricData.SumData(points) => points.foreach(p => println(p.value))
115
+ case MetricData.HistogramData(points) => points.foreach(p => println(p.count))
116
+ case MetricData.GaugeData(points) => points.foreach(p => println(p.value))
117
+ }
118
+ ```
119
+
120
+ ## Install a Provider and Reset
121
+
122
+ `metric.install(provider)` swaps in a `MeterProvider` configured with your own [`Resource`](../common/resource.md). The builder takes nothing else — there is no exporter or reader hook — so instruments created after the swap register on the new provider's meters and are read back through `metric.reader`.
123
+
124
+ ```scala
125
+ object metric {
126
+ def install(provider: MeterProvider): Unit
127
+ def removeAll(): Unit
128
+ }
129
+ ```
130
+
131
+ Install before creating any instruments. Both calls replace the provider along with its registry, so an instrument built beforehand stays registered with the old one and won't appear in `metric.reader` snapshots. `metric.removeAll()` restores a fresh default provider.
132
+
133
+ ```scala
134
+ import zio.blocks.telemetry._
135
+
136
+ metric.install(
137
+ MeterProvider.builder
138
+ .setResource(Resource.create(Attributes.of(Attributes.ServiceName, "order-service")))
139
+ .build()
140
+ )
141
+
142
+ // later, at shutdown
143
+ metric.removeAll()
144
+ ```
145
+
146
+ ## See Also
147
+
148
+ - [Telemetry Guide](../../../guides/telemetry-guide.md) — metrics data flow and labeled instrument patterns
149
+ - [Telemetry Reference](../index.md) — module overview and all three pillars
150
+ - [Common Types](../common/index.md) — `Attributes` used as metric dimension labels
@@ -0,0 +1,183 @@
1
+ ---
2
+ id: instruments
3
+ title: "Metric Instruments"
4
+ description: "The four synchronous metric instruments: monotonic Counter, bidirectional UpDownCounter, bucketed Histogram, and last-value Gauge."
5
+ keywords:
6
+ - "Application Metrics"
7
+ - "Synchronous Instruments"
8
+ - "Measurement Recording"
9
+ - "Metric Instruments"
10
+ sidebar_label: "Instruments"
11
+ ---
12
+
13
+ The four synchronous instruments are what application code actually calls to record measurements. They differ by what they measure: a `Counter` sums a monotonic total, an `UpDownCounter` tracks a value that rises and falls, a `Histogram` builds a distribution of observations, and a `Gauge` holds the latest reading. Reach for them through [`metric`](./index.md) (`metric.counter("…")`) or a [`Meter`](./meter.md) builder — either path registers the instrument so `MetricReader.collectAllMetrics()` collects it into a [`MetricData`](./metric-data.md) snapshot.
14
+
15
+ All four share the same recording shape: `add` (counters) or `record` (histogram, gauge) takes a value plus optional dimension labels, either as an [`Attributes`](../common/attributes.md) set or as `(String, Any)*` tuples — a label value may be a `String`, `Long`, `Int` (widened to `Long`), `Double`, or `Boolean`, and anything else is recorded as its `toString`; `bind` pre-attaches a label set for hot-path reuse; and `collect` snapshots the accumulated data into a [`MetricData`](./metric-data.md) variant.
16
+
17
+ ## Counter
18
+
19
+ A `Counter` records monotonically increasing values — negative deltas are ignored, so it only ever climbs. Use it for totals like requests served, errors, or bytes sent.
20
+
21
+ ```scala
22
+ final class Counter private[telemetry] (
23
+ val name: String, val description: String, val unit: String
24
+ ) {
25
+ def add(value: Long, attributes: Attributes): Unit
26
+ def add(value: Long, attrs: (String, Any)*): Unit // convenience vararg overload
27
+ def bind(attributes: Attributes): BoundCounter // pre-attributed for hot-path reuse
28
+ def collect(): MetricData // snapshot → MetricData.SumData
29
+ }
30
+ ```
31
+
32
+ Record with labels, then read the per-label totals from the collected `SumData`:
33
+
34
+ ```scala
35
+ import zio.blocks.telemetry._
36
+
37
+ val calls = metric.counter("db.calls")
38
+ calls.add(1L, "table" -> "orders")
39
+ calls.add(2L, "table" -> "items")
40
+
41
+ val tableKey = AttributeKey.string("table")
42
+ metric.reader.collectAllMetrics().foreach {
43
+ case MetricData.SumData(points) =>
44
+ points.foreach(p => println(s"${p.attributes.get(tableKey)}: ${p.value}"))
45
+ case _ => ()
46
+ }
47
+ ```
48
+
49
+ ## UpDownCounter
50
+
51
+ An `UpDownCounter` records bidirectional deltas — the same API as `Counter`, but negative values count. Use it for a running total that both rises and falls, such as active connections or queue depth.
52
+
53
+ ```scala
54
+ final class UpDownCounter private[telemetry] (
55
+ val name: String, val description: String, val unit: String
56
+ ) {
57
+ def add(value: Long, attributes: Attributes): Unit
58
+ def add(value: Long, attrs: (String, Any)*): Unit
59
+ def bind(attributes: Attributes): BoundUpDownCounter
60
+ def collect(): MetricData
61
+ }
62
+ ```
63
+
64
+ Add positive and negative deltas; the running total nets out:
65
+
66
+ ```scala
67
+ import zio.blocks.telemetry._
68
+
69
+ val active = metric.upDownCounter("active.connections")
70
+ active.add(1L) // new connection
71
+ active.add(-1L) // connection closed
72
+
73
+ metric.reader.collectAllMetrics().foreach {
74
+ case MetricData.SumData(points) => println(points.head.value) // 0
75
+ case _ => ()
76
+ }
77
+ ```
78
+
79
+ ## Histogram
80
+
81
+ A `Histogram` distributes `Double` observations into buckets and accumulates their count, sum, min, and max per label set. Use it for value distributions like request latency or payload size. Observations fall into a fixed set of bucket boundaries, defaulting to `[0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000]`.
82
+
83
+ Each boundary is the **inclusive** upper bound of its bucket, so with boundaries `[5, 10]` a value of exactly `5` lands in the first bucket, not the second, and anything above the last boundary falls into one final overflow bucket. Note that the default set starts at `0`, so its first bucket holds only values at or below zero.
84
+
85
+ For different boundaries, construct the histogram directly — the builder has no setter for them:
86
+
87
+ ```scala
88
+ import zio.blocks.telemetry._
89
+
90
+ val latency = Histogram("http.latency.ms", "Request latency", "ms", Array(10.0, 50.0, 100.0, 500.0))
91
+ latency.record(42.0, "route" -> "/orders")
92
+
93
+ val snapshot = latency.collect()
94
+ ```
95
+
96
+ That path trades away registration: an instrument you build yourself belongs to no meter, so `collectAllMetrics()` never returns it and you have to call `collect()` on the instrument yourself.
97
+
98
+ ```scala
99
+ final class Histogram private[telemetry] (
100
+ val name: String, val description: String, val unit: String,
101
+ val boundaries: Array[Double]
102
+ ) {
103
+ def record(value: Double, attributes: Attributes): Unit
104
+ def record(value: Double, attrs: (String, Any)*): Unit
105
+ def bind(attributes: Attributes): BoundHistogram
106
+ def collect(): MetricData // snapshot → MetricData.HistogramData
107
+ }
108
+ ```
109
+
110
+ Record observations, then read `count` and `sum` from the collected `HistogramData`:
111
+
112
+ ```scala
113
+ import zio.blocks.telemetry._
114
+
115
+ val latency = metric.histogram("request.latency")
116
+ latency.record(42.5, "endpoint" -> "/api/orders")
117
+ latency.record(1500.0, "endpoint" -> "/api/reports")
118
+
119
+ metric.reader.collectAllMetrics().foreach {
120
+ case MetricData.HistogramData(points) =>
121
+ points.foreach(p => println(s"count=${p.count} sum=${p.sum}"))
122
+ case _ => ()
123
+ }
124
+ ```
125
+
126
+ ## Gauge
127
+
128
+ A `Gauge` holds the most recent `Double` value per label set — each `record` overwrites the previous one. Use it for an instantaneous reading like CPU temperature or a current queue-depth snapshot.
129
+
130
+ ```scala
131
+ final class Gauge private[telemetry] (
132
+ val name: String, val description: String, val unit: String
133
+ ) {
134
+ def record(value: Double, attributes: Attributes): Unit
135
+ def record(value: Double, attrs: (String, Any)*): Unit
136
+ def bind(attributes: Attributes): BoundGauge
137
+ def collect(): MetricData // snapshot → MetricData.GaugeData
138
+ }
139
+ ```
140
+
141
+ Each `record` overwrites the last; the snapshot holds the latest value:
142
+
143
+ ```scala
144
+ import zio.blocks.telemetry._
145
+
146
+ val temp = metric.gauge("cpu.temperature")
147
+ temp.record(72.5)
148
+ temp.record(74.1) // overwrites the previous value
149
+
150
+ metric.reader.collectAllMetrics().foreach {
151
+ case MetricData.GaugeData(points) => println(points.head.value) // 74.1
152
+ case _ => ()
153
+ }
154
+ ```
155
+
156
+ ## Choosing an Instrument
157
+
158
+ | Instrument | Delta constraint | Use when |
159
+ |-----------------|------------------|-----------------------------------------|
160
+ | `Counter` | Non-negative | Counting requests, errors, events |
161
+ | `UpDownCounter` | Any | Active connections, queue-depth changes |
162
+ | `Histogram` | Any `Double` | Latency, payload-size distributions |
163
+ | `Gauge` | Any `Double` | CPU temperature, queue-depth snapshot |
164
+
165
+ ## Bound Instruments
166
+
167
+ When one label combination is recorded repeatedly on a hot path, `bind(attrs)` returns a `Bound*` instrument pre-associated with that label set, so recording skips rebuilding `Attributes` on every call:
168
+
169
+ ```scala
170
+ import zio.blocks.telemetry._
171
+
172
+ val bound = metric.counter("rpc.calls").bind(Attributes.of(AttributeKey.string("method"), "OrderService.place"))
173
+ bound.add(1L)
174
+ bound.add(1L) // no Attributes construction per call
175
+ ```
176
+
177
+ One caveat on a histogram: `bind` creates the per-label state immediately, so a label set you bind but never record collects as `count = 0` with `min` and `max` at their sentinel extremes (`Double.MaxValue` and `Double.MinValue`). Skip zero-count points when plotting, or bind only where you will record.
178
+
179
+ For a name-based label API — declare label names once, then pass values positionally — see [Labeled Instruments](./labeled-instruments.md).
180
+
181
+ ## Collection
182
+
183
+ `MetricReader.collectAllMetrics()` calls each registered instrument's `collect()`, producing one `MetricData` per instrument: `SumData` for `Counter` and `UpDownCounter`, `HistogramData` for `Histogram`, and `GaugeData` for `Gauge`. Pattern-match to read the data points — see [MetricData](./metric-data.md) for the point structure. Only instruments obtained from `metric.*` or a `Meter` builder are registered; do not construct an instrument directly, as an unregistered one never reaches `collectAllMetrics()`.
@@ -0,0 +1,74 @@
1
+ ---
2
+ id: labeled-instruments
3
+ title: "Labeled Instruments"
4
+ description: "Metric instruments with pre-declared label names. Record values positionally; runtime arity validation prevents label mismatches."
5
+ keywords:
6
+ - "Application Metrics"
7
+ - "Dimensional Metrics"
8
+ - "Prometheus-Style Labels"
9
+ - "Labeled Instruments"
10
+ sidebar_label: "Labeled Instruments"
11
+ ---
12
+
13
+ A labeled instrument is an ordinary counter, histogram, or gauge that already knows its label *names*. You declare them once — `"method"`, `"status"` — and then pass only values: `add(1, "GET", "200")`.
14
+
15
+ It exists to keep call sites short and consistent: the names live in one place, so every recording site is guaranteed to use the same label keys in the same order, and reading `add(1, "GET", "200")` is quicker than reading a line of repeated pairs.
16
+
17
+ Do not reach for it as an optimization. The pair form (`add(1, "method" -> "GET", …)`) builds its `Attributes` through a pooled, thread-local builder, while the labeled form allocates a fresh builder and a fresh `Attributes` on every call — so labeled recording allocates more, not less. What you give up is safety: passing the wrong *number* of values throws right at the recording call, but passing them in the wrong *order* records silently — swap two and you get a plausible-looking series that's simply wrong. When a label combination really is hot, `bind` is the answer: it resolves the attribute set once and hands back a `Bound*` that writes straight to the accumulator. A [`Meter`](./meter.md) creates labeled instruments, through `labeledCounter`, `labeledHistogram`, and `labeledGauge`.
18
+
19
+ ```scala
20
+ final class LabeledCounter private[telemetry] (...) {
21
+ val labelNames: Array[String]
22
+ def add(value: Long, labelValues: Any*): Unit
23
+ def bind(labelValues: Any*): BoundCounter
24
+ def collect(): MetricData
25
+ }
26
+
27
+ final class LabeledHistogram private[telemetry] (...) {
28
+ val labelNames: Array[String]
29
+ def record(value: Double, labelValues: Any*): Unit
30
+ def bind(labelValues: Any*): BoundHistogram
31
+ def collect(): MetricData
32
+ }
33
+
34
+ final class LabeledGauge private[telemetry] (...) {
35
+ val labelNames: Array[String]
36
+ def record(value: Double, labelValues: Any*): Unit
37
+ def bind(labelValues: Any*): BoundGauge
38
+ def collect(): MetricData
39
+ }
40
+ ```
41
+
42
+ ## Usage
43
+
44
+ Declare the label names once, then supply values in that same order on each call. The count of `labelValues` must match the declared `labelNames`; a mismatch throws `IllegalArgumentException` at the recording call site (not at construction), so a wrong arity surfaces immediately in tests.
45
+
46
+ ```scala
47
+ import zio.blocks.telemetry._
48
+
49
+ val meter = MeterProvider.builder.build().get("com.example")
50
+
51
+ // "GET" → "method", "200" → "status"
52
+ val reqs = meter.labeledCounter("http.requests", "method", "status")
53
+ reqs.add(1L, "GET", "200")
54
+ reqs.add(1L, "POST", "500")
55
+
56
+ val lat = meter.labeledHistogram("request.latency", "route")
57
+ lat.record(15.0, "/orders")
58
+
59
+ val depth = meter.labeledGauge("queue.depth", "queue")
60
+ depth.record(7.0, "orders")
61
+ ```
62
+
63
+ For a set of label values recorded over and over, `bind(labelValues*)` builds the `Attributes` once and returns a bound instrument that skips that work on each later call:
64
+
65
+ ```scala
66
+ import zio.blocks.telemetry._
67
+
68
+ val reqs = MeterProvider.builder.build().get("com.example").labeledCounter("rpc.calls", "service", "method")
69
+ val bound = reqs.bind("OrderService", "place")
70
+ bound.add(1L)
71
+ bound.add(1L)
72
+ ```
73
+
74
+ Values flow through the wrapped `Counter`, `Histogram`, or `Gauge` and are collected by `MetricReader.collectAllMetrics()` like any other instrument — see [MetricData](./metric-data.md).
@@ -0,0 +1,76 @@
1
+ ---
2
+ id: meter-provider
3
+ title: "MeterProvider"
4
+ description: "Configure metrics once for a service: name what produced them, hand out meters per component, and read every measurement back through one reader."
5
+ keywords:
6
+ - "Application Metrics"
7
+ - "Metrics Configuration"
8
+ - "Meter Factory"
9
+ - "MeterProvider"
10
+ ---
11
+
12
+ A `MeterProvider` is where metrics get configured, once, at startup. You build one, and it hands out the [`Meter`](./meter.md)s your components take.
13
+
14
+ It exists because two things can't be decided per instrument. The first is **identity**: an exported measurement has to say what produced it, or a backend shows a number with no owner — and repeating that on every counter would be both tedious and inconsistent. You set it once as a [`Resource`](../common/resource.md) on the provider. Unlike spans and log records, collected metric data does not carry it, so read `provider.resource` when you export and attach it there.
15
+
16
+ The second is **one place to read from**. Instruments end up scattered across components, and nothing useful happens until something collects them all together. The provider keeps the registry every meter joins, so a single `reader.collectAllMetrics()` returns every measurement from every scope beneath it — you never assemble a list of instruments yourself.
17
+
18
+ Most applications never hold one: they hand a provider to `metric.install(...)` at startup and use the global [`metric`](./index.md) everywhere after. Build one yourself when you want a `Resource` of your own, or a scope you can collect and shut down independently of the global one.
19
+
20
+ ## Configuring Metrics for Your Service
21
+
22
+ Build the provider with a `Resource` naming your service:
23
+
24
+ ```scala
25
+ import zio.blocks.telemetry._
26
+
27
+ val provider = MeterProvider.builder
28
+ .setResource(Resource.create(Attributes.of(Attributes.ServiceName, "payments")))
29
+ .build()
30
+ ```
31
+
32
+ Do set the name. Skipping it doesn't fail — the default `Resource` supplies `service.name = "unknown_service"` alongside SDK version attributes — but that placeholder is what a backend will show for every measurement you export.
33
+
34
+ ## Getting a Meter per Component
35
+
36
+ Ask for a meter by the component's name; that name is what attributes its measurements:
37
+
38
+ ```scala
39
+ import zio.blocks.telemetry._
40
+
41
+ val provider = MeterProvider.builder.build()
42
+
43
+ val meter: Meter = provider.get("com.example.payments")
44
+ ```
45
+
46
+ The full signature is `get(name: String, version: String = "")`; passing a version records it on the scope, which is how you tell measurements from two releases of the same library apart. Asking twice for the same name *and* version returns the same meter, so components take theirs independently without coordinating. The provider also exposes the `Resource` you built it with as `provider.resource`. See [Meter](./meter.md) for what to do with a meter.
47
+
48
+ ## Reading Everything Back
49
+
50
+ `reader` is a single [`MetricReader`](./metric-data.md) for the provider's whole lifetime, created at `build()` — there is nothing to register, and no second reader to keep in sync:
51
+
52
+ ```scala
53
+ import zio.blocks.telemetry._
54
+
55
+ val provider = MeterProvider.builder.build()
56
+ provider.get("com.example").counterBuilder("ops").build().add(1L)
57
+
58
+ val snapshots: Seq[MetricData] = provider.reader.collectAllMetrics()
59
+ ```
60
+
61
+ Each call aggregates what has accumulated so far into [`MetricData`](./metric-data.md) — one value per registered instrument, across every meter. This is a pull: nothing is sent anywhere until you or an exporter asks.
62
+
63
+ ## Shutting Down
64
+
65
+ A provider is `AutoCloseable`, with `close()` calling `shutdown()`, so it fits `scala.util.Using.resource`:
66
+
67
+ ```scala
68
+ import zio.blocks.telemetry._
69
+ import scala.util.Using
70
+
71
+ Using.resource(MeterProvider.builder.build()) { provider =>
72
+ provider.reader.collectAllMetrics()
73
+ }
74
+ ```
75
+
76
+ Unlike logging, there is nothing buffered here waiting to be flushed — metrics are read on demand, so the built-in reader's shutdown does no work today. Call it anyway at exit: it costs nothing, and it's what an export pipeline would hook into.
@@ -0,0 +1,98 @@
1
+ ---
2
+ id: meter
3
+ title: "Meter"
4
+ description: "Create the counters, histograms, and gauges one component records through — scoped to that component, and registered so collection finds them."
5
+ keywords:
6
+ - "Application Metrics"
7
+ - "Instrument Registration"
8
+ - "Metric Instruments"
9
+ - "Meter"
10
+ ---
11
+
12
+ A `Meter` is where one component's [instruments](./instruments.md) come from. You take a meter under your component's name, create counters and histograms from it, and record through those.
13
+
14
+ It exists to answer a question a bare instrument name can't: *whose measurement is this?* A meter carries a scope name, so the instruments one component builds are grouped under it, and a reader can tell your `requests` counter from the one a library you depend on registered. (The two were never merged into one series — each instrument aggregates on its own — but without scopes there is nothing in the code that says which is which.)
15
+
16
+ The second problem it solves is reachability. Recording a number is useless if nothing can read it back, and an instrument only reaches collection if something registered it. A meter is that something: it is registered with its [`MeterProvider`](./meter-provider.md) when you obtain it, and it registers every instrument you build from it, so a measurement's path to `reader.collectAllMetrics()` is complete the moment you call `build()` — with no wiring step you could forget.
17
+
18
+ ## Taking a Meter
19
+
20
+ Ask a provider for one by name, or `metric.get(name)` in application code. Use the component's package as the name:
21
+
22
+ ```scala
23
+ import zio.blocks.telemetry._
24
+
25
+ val meter: Meter = MeterProvider.builder.build().get("com.example.server")
26
+ ```
27
+
28
+ Asking twice for the same name gives you back the *same* meter, not a second one — meters are cached per scope. So separate call sites in one component can each take their meter without coordinating, and their instruments still land under a single scope.
29
+
30
+ ## Creating an Instrument
31
+
32
+ Each of the four instrument kinds has a builder. Name it, optionally describe it and give it a unit, then `build()`:
33
+
34
+ ```scala
35
+ import zio.blocks.telemetry._
36
+
37
+ val meter = MeterProvider.builder.build().get("com.example.server")
38
+
39
+ val requests = meter.counterBuilder("http.requests")
40
+ .setDescription("Total HTTP requests")
41
+ .setUnit("1")
42
+ .build()
43
+ requests.add(1L, "method" -> "GET", "status" -> "200")
44
+
45
+ val latency = meter.histogramBuilder("request.latency").setUnit("ms").build()
46
+ latency.record(42.5, "route" -> "/api/orders")
47
+ ```
48
+
49
+ The description and unit stay on the instrument — `collect()` returns data points only — so they reach a dashboard through whatever exporter reads the instrument, which is what lets it label an axis in milliseconds instead of showing bare numbers. Build each instrument once and hold the result — a `val` on the component that records through it. Building the same name twice gives you two registered instruments, which splits one logical metric into two series that no consumer can merge back together.
50
+
51
+ ## Recording on a Hot Path
52
+
53
+ When the same label *names* repeat on every call, declaring them once keeps every call site consistent and short. `labeledCounter`, `labeledHistogram`, and `labeledGauge` fix the names at construction so callers pass just the values, positionally:
54
+
55
+ ```scala
56
+ import zio.blocks.telemetry._
57
+
58
+ val meter = MeterProvider.builder.build().get("com.example")
59
+
60
+ val byRoute = meter.labeledCounter("http.requests", "method", "status")
61
+ byRoute.add(1L, "GET", "200")
62
+ ```
63
+
64
+ See [Labeled Instruments](./labeled-instruments.md) for the trade-offs; for ordinary recording the tuple form above is simpler.
65
+
66
+ ## Reporting a Value You Don't Push
67
+
68
+ Some numbers aren't events you count — they're state you can read at any time, like a cache's size or a pool's idle connections. Instead of pushing an update whenever it changes, `buildWithCallback` on the counter, up-down counter, and gauge builders makes an instrument that asks *you* for the value at collection time:
69
+
70
+ ```scala
71
+ import zio.blocks.telemetry._
72
+
73
+ val meter = MeterProvider.builder.build().get("com.example")
74
+ val cache = scala.collection.mutable.Map("k" -> "v")
75
+
76
+ meter.gaugeBuilder("cache.entries").buildWithCallback { observer =>
77
+ observer.record(cache.size.toDouble, Attributes.empty)
78
+ }
79
+ ```
80
+
81
+ The block doesn't run when you build it — it runs once per collection, reading `cache.size` fresh each time, so the value can't go stale and you need no hook at every mutation. Keep it cheap and side-effect-free; it runs on the collecting thread. You can discard the returned instrument — the meter registered it, so collection finds it — and hold it only if you want to call its own `collect()` directly.
82
+
83
+ `buildWithCallback` returns an `ObservableCounter`, `ObservableUpDownCounter`, or `ObservableGauge` depending on the builder, and hands your block an `ObservableCallback` to record through. Histograms have no callback form, since a distribution has to see every observation as it happens.
84
+
85
+ Watch the type on the counters: both observable counters round what you report to a whole number, so a callback recording `1.5` is collected as `2`. Only `ObservableGauge` keeps the `Double` as given.
86
+
87
+ ## Two Ways to Lose Measurements
88
+
89
+ Both come from an instrument that records into nothing:
90
+
91
+ 1. **Constructing an instrument directly.** `Counter("http.requests", "", "")` compiles, because the companion `apply` is public. It records perfectly well into an object no meter registered, so `collectAllMetrics()` never sees it. Go through a meter or `metric.*` — the one case that justifies direct construction is a [histogram with custom bucket boundaries](./instruments.md), where you accept the loss of registration and call `collect()` yourself.
92
+ 2. **Crossing providers.** An instrument reaches only the reader of the provider whose meter built it. `metric.install(...)` swaps in a provider with an empty registry, so take your meters and build your instruments *after* installing.
93
+
94
+ ## See Also
95
+
96
+ - [Instruments](./instruments.md) — the recording API of each instrument kind
97
+ - [MeterProvider](./meter-provider.md) — where meters come from, and what configures them
98
+ - [MetricData](./metric-data.md) — what collection hands back
@@ -0,0 +1,57 @@
1
+ ---
2
+ id: metric-data
3
+ title: "MetricData"
4
+ description: "Aggregated metric snapshot produced by MetricReader.collectAllMetrics(). Three variants: SumData, HistogramData, GaugeData."
5
+ keywords:
6
+ - "Application Metrics"
7
+ - "Metric Export"
8
+ - "Aggregated Snapshot"
9
+ - "MetricData"
10
+ ---
11
+
12
+ `MetricData` is an aggregated snapshot of one instrument at one collect cycle — its accumulated points, keyed by label set. You rarely build it directly: it is what `MetricReader.collectAllMetrics()` returns, one value per registered instrument, for a test to assert on or an export pipeline to serialize. Each collection builds fresh points, though a histogram point exposes its `boundaries` and `bucketCounts` arrays as-is, so read them and don't write to them.
13
+
14
+ One thing it does *not* carry is the instrument's name, description, or unit — a snapshot is only points and their label sets. So `collectAllMetrics()` hands back a `Seq[MetricData]` with no way to tell which instrument each entry came from, which is why an export pipeline pairs them with names of its own before serializing. In a test, collect from a single instrument with its own `collect()` when you need to know the source.
15
+
16
+ Its three variants line up with the instrument that produced them:
17
+
18
+ - `SumData` — from a [`Counter`](./instruments.md) or an `UpDownCounter`
19
+ - `HistogramData` — from a `Histogram`
20
+ - `GaugeData` — from a `Gauge`
21
+
22
+ So a `match` on `MetricData` tells you both the shape of the numbers and the kind of instrument they came from.
23
+
24
+ ```scala
25
+ sealed trait MetricData
26
+
27
+ object MetricData {
28
+ final case class SumData(points: List[SumDataPoint]) extends MetricData
29
+ final case class HistogramData(points: List[HistogramDataPoint]) extends MetricData
30
+ final case class GaugeData(points: List[GaugeDataPoint]) extends MetricData
31
+ }
32
+
33
+ final case class SumDataPoint(
34
+ attributes: Attributes,
35
+ startTimeNanos: Long,
36
+ timeNanos: Long,
37
+ value: Long // cumulative sum
38
+ )
39
+
40
+ final case class HistogramDataPoint(
41
+ attributes: Attributes,
42
+ startTimeNanos: Long,
43
+ timeNanos: Long,
44
+ count: Long, // total number of observations
45
+ sum: Double, // sum of all observations
46
+ min: Double, // smallest observation
47
+ max: Double, // largest observation
48
+ bucketCounts: Array[Long], // count per bucket (length == boundaries.length + 1)
49
+ boundaries: Array[Double] // the bucket upper boundaries
50
+ )
51
+
52
+ final case class GaugeDataPoint(
53
+ attributes: Attributes,
54
+ timeNanos: Long,
55
+ value: Double // most recently recorded value
56
+ )
57
+ ```