@smthrs/observability 0.35.0 → 1.0.0-rc.3
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.
- package/CHANGELOG.md +56 -0
- package/LICENSE +1 -1
- package/README.md +84 -0
- package/dist/cjs/BrowserOtel.d.ts +37 -0
- package/dist/cjs/BrowserOtel.d.ts.map +1 -0
- package/dist/cjs/BrowserOtel.js +47 -0
- package/dist/cjs/BrowserOtel.js.map +7 -0
- package/dist/cjs/Endpoint.d.ts +70 -0
- package/dist/cjs/Endpoint.d.ts.map +1 -0
- package/dist/cjs/Endpoint.js +85 -0
- package/dist/cjs/Endpoint.js.map +7 -0
- package/dist/cjs/JournalLogger.d.ts +232 -0
- package/dist/cjs/JournalLogger.d.ts.map +1 -0
- package/dist/cjs/JournalLogger.js +388 -0
- package/dist/cjs/JournalLogger.js.map +7 -0
- package/dist/cjs/Logger.d.ts +55 -0
- package/dist/cjs/Logger.d.ts.map +1 -0
- package/dist/cjs/Logger.js +72 -0
- package/dist/cjs/Logger.js.map +7 -0
- package/dist/cjs/Metric.d.ts +67 -0
- package/dist/cjs/Metric.d.ts.map +1 -0
- package/dist/cjs/Metric.js +68 -0
- package/dist/cjs/Metric.js.map +7 -0
- package/dist/cjs/NodeOtel.d.ts +42 -0
- package/dist/cjs/NodeOtel.d.ts.map +1 -0
- package/dist/cjs/NodeOtel.js +78 -0
- package/dist/cjs/NodeOtel.js.map +7 -0
- package/dist/cjs/Otel.d.ts +62 -0
- package/dist/cjs/Otel.d.ts.map +1 -0
- package/dist/cjs/Otel.js +75 -0
- package/dist/cjs/Otel.js.map +7 -0
- package/dist/cjs/Otlp.d.ts +200 -0
- package/dist/cjs/Otlp.d.ts.map +1 -0
- package/dist/cjs/Otlp.js +235 -0
- package/dist/cjs/Otlp.js.map +7 -0
- package/dist/cjs/Resource.d.ts +162 -0
- package/dist/cjs/Resource.d.ts.map +1 -0
- package/dist/cjs/Resource.js +186 -0
- package/dist/cjs/Resource.js.map +7 -0
- package/dist/cjs/index.d.ts +61 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +47 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/internal/schemaIssuePath.d.ts +31 -0
- package/dist/cjs/internal/schemaIssuePath.d.ts.map +1 -0
- package/dist/cjs/internal/schemaIssuePath.js +49 -0
- package/dist/cjs/internal/schemaIssuePath.js.map +7 -0
- package/dist/cjs/package.json +1 -0
- package/dist/esm/BrowserOtel.d.ts +37 -0
- package/dist/esm/BrowserOtel.d.ts.map +1 -0
- package/dist/esm/BrowserOtel.js +24 -0
- package/dist/esm/BrowserOtel.js.map +1 -0
- package/dist/esm/Endpoint.d.ts +70 -0
- package/dist/esm/Endpoint.d.ts.map +1 -0
- package/dist/esm/Endpoint.js +110 -0
- package/dist/esm/Endpoint.js.map +1 -0
- package/dist/esm/JournalLogger.d.ts +232 -0
- package/dist/esm/JournalLogger.d.ts.map +1 -0
- package/dist/esm/JournalLogger.js +482 -0
- package/dist/esm/JournalLogger.js.map +1 -0
- package/dist/esm/Logger.d.ts +55 -0
- package/dist/esm/Logger.d.ts.map +1 -0
- package/dist/esm/Logger.js +60 -0
- package/dist/esm/Logger.js.map +1 -0
- package/dist/esm/Metric.d.ts +67 -0
- package/dist/esm/Metric.d.ts.map +1 -0
- package/dist/esm/Metric.js +80 -0
- package/dist/esm/Metric.js.map +1 -0
- package/dist/esm/NodeOtel.d.ts +42 -0
- package/dist/esm/NodeOtel.d.ts.map +1 -0
- package/dist/esm/NodeOtel.js +56 -0
- package/dist/esm/NodeOtel.js.map +1 -0
- package/dist/esm/Otel.d.ts +62 -0
- package/dist/esm/Otel.d.ts.map +1 -0
- package/dist/esm/Otel.js +51 -0
- package/dist/esm/Otel.js.map +1 -0
- package/dist/esm/Otlp.d.ts +200 -0
- package/dist/esm/Otlp.d.ts.map +1 -0
- package/dist/esm/Otlp.js +350 -0
- package/dist/esm/Otlp.js.map +1 -0
- package/dist/esm/Resource.d.ts +162 -0
- package/dist/esm/Resource.d.ts.map +1 -0
- package/dist/esm/Resource.js +237 -0
- package/dist/esm/Resource.js.map +1 -0
- package/dist/esm/index.d.ts +61 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +61 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal/schemaIssuePath.d.ts +31 -0
- package/dist/esm/internal/schemaIssuePath.d.ts.map +1 -0
- package/dist/esm/internal/schemaIssuePath.js +59 -0
- package/dist/esm/internal/schemaIssuePath.js.map +1 -0
- package/docs/README.md +162 -0
- package/docs/api.md +768 -0
- package/docs/concepts/instrumentation-and-export.md +68 -0
- package/docs/concepts/layer-map.md +72 -0
- package/docs/concepts/validated-acquisition.md +105 -0
- package/docs/guides/export-to-a-collector.md +167 -0
- package/docs/guides/forward-logs-to-the-journal.md +226 -0
- package/docs/guides/install-a-logger.md +111 -0
- package/docs/guides/read-runtime-metrics.md +83 -0
- package/docs/guides/testing.md +134 -0
- package/docs/guides/wire-an-opentelemetry-sdk.md +220 -0
- package/docs/installation.md +98 -0
- package/docs/quickstart.md +145 -0
- package/docs/troubleshooting.md +168 -0
- package/package.json +221 -32
- package/src/BrowserOtel.ts +51 -0
- package/src/Endpoint.ts +131 -0
- package/src/JournalLogger.ts +596 -0
- package/src/Logger.ts +106 -0
- package/src/Metric.ts +88 -0
- package/src/NodeOtel.ts +90 -0
- package/src/Otel.ts +117 -0
- package/src/Otlp.ts +477 -0
- package/src/Resource.ts +324 -0
- package/src/index.ts +67 -0
- package/src/internal/schemaIssuePath.ts +65 -0
- package/docker-compose.otel.yml +0 -104
- package/grafana/dashboards/smithers-dashboard.json +0 -453
- package/grafana/provisioning/dashboards/dashboards.yml +0 -12
- package/grafana/provisioning/datasources/datasources.yml +0 -49
- package/loki/loki-config.yaml +0 -35
- package/otel-collector-config.yml +0 -38
- package/prometheus/prometheus.yml +0 -8
- package/src/MetricName.ts +0 -1
- package/src/MetricsServiceLive.js +0 -9
- package/src/README.md +0 -28
- package/src/ResolvedSmithersObservabilityOptions.ts +0 -12
- package/src/SmithersEvent.ts +0 -843
- package/src/SmithersLogFormat.ts +0 -1
- package/src/SmithersMetricDefinition.ts +0 -15
- package/src/SmithersMetricType.ts +0 -1
- package/src/SmithersMetricUnit.ts +0 -1
- package/src/SmithersObservability.js +0 -15
- package/src/SmithersObservabilityOptions.ts +0 -12
- package/src/SmithersObservabilityService.ts +0 -13
- package/src/_coreCorrelation/CorrelationContext.ts +0 -10
- package/src/_coreCorrelation/CorrelationContextLive.js +0 -11
- package/src/_coreCorrelation/CorrelationContextService.js +0 -15
- package/src/_coreCorrelation/CorrelationContextServiceShape.ts +0 -12
- package/src/_coreCorrelation/CorrelationPatch.ts +0 -3
- package/src/_coreCorrelation/README.md +0 -20
- package/src/_coreCorrelation/_correlationStorage.js +0 -4
- package/src/_coreCorrelation/correlationContextFiberRef.js +0 -5
- package/src/_coreCorrelation/correlationContextToLogAnnotations.js +0 -18
- package/src/_coreCorrelation/getCurrentCorrelationContext.js +0 -9
- package/src/_coreCorrelation/getCurrentCorrelationContextEffect.js +0 -11
- package/src/_coreCorrelation/index.js +0 -14
- package/src/_coreCorrelation/mergeCorrelationContext.js +0 -51
- package/src/_coreCorrelation/runWithCorrelationContext.js +0 -14
- package/src/_coreCorrelation/updateCurrentCorrelationContext.js +0 -17
- package/src/_coreCorrelation/withCorrelationContext.js +0 -38
- package/src/_coreCorrelation/withCurrentCorrelationContext.js +0 -11
- package/src/_coreMetrics.js +0 -14
- package/src/_coreMetricsShape.ts +0 -44
- package/src/_corePrometheus.js +0 -91
- package/src/_corePrometheusShape.ts +0 -11
- package/src/_coreTracing.js +0 -132
- package/src/_coreTracingShape.ts +0 -17
- package/src/_otelLogBuilders.js +0 -102
- package/src/_sessionFileResolvers.js +0 -164
- package/src/_smithersSpanAttributeAliases.js +0 -19
- package/src/_smithersTraceSpanStorage.js +0 -3
- package/src/_traceEventNormalizers.js +0 -499
- package/src/_traceRedaction.js +0 -162
- package/src/agentSessionEventToOtelLogRecord.js +0 -55
- package/src/agentTrace.ts +0 -144
- package/src/agentTraceCapabilities.js +0 -151
- package/src/annotateSmithersTrace.js +0 -11
- package/src/canonicalTraceEventToOtelLogRecord.js +0 -55
- package/src/correlation.js +0 -56
- package/src/createSmithersObservabilityLayer.js +0 -59
- package/src/createSmithersOtelLayer.js +0 -23
- package/src/createSmithersRuntimeLayer.js +0 -2
- package/src/detectAgentFamily.js +0 -26
- package/src/detectCaptureMode.js +0 -29
- package/src/emitOtelLogRecord.js +0 -19
- package/src/getCurrentSmithersTraceAnnotations.js +0 -14
- package/src/getCurrentSmithersTraceSpan.js +0 -7
- package/src/index.d.ts +0 -1392
- package/src/index.js +0 -137
- package/src/kindPhase.js +0 -17
- package/src/logging.js +0 -264
- package/src/makeSmithersSpanAttributes.js +0 -20
- package/src/metrics/README.md +0 -22
- package/src/metrics/SmithersMetricDefinition.ts +0 -17
- package/src/metrics/SmithersMetricType.ts +0 -1
- package/src/metrics/SmithersMetricUnit.ts +0 -1
- package/src/metrics/_asyncExternalWaitCounts.js +0 -4
- package/src/metrics/_buckets.js +0 -37
- package/src/metrics/_incrementGauge.js +0 -11
- package/src/metrics/_processStartMs.js +0 -1
- package/src/metrics/activeNodes.js +0 -2
- package/src/metrics/activeRuns.js +0 -2
- package/src/metrics/agentActionsTotal.js +0 -2
- package/src/metrics/agentDurationMs.js +0 -3
- package/src/metrics/agentErrorsTotal.js +0 -2
- package/src/metrics/agentEventsTotal.js +0 -2
- package/src/metrics/agentInvocationsTotal.js +0 -2
- package/src/metrics/agentRetriesTotal.js +0 -2
- package/src/metrics/agentSessionsTotal.js +0 -2
- package/src/metrics/agentTokensTotal.js +0 -2
- package/src/metrics/alertDeliveriesAttempted.js +0 -2
- package/src/metrics/alertDeliveriesSuppressed.js +0 -2
- package/src/metrics/alertsAcknowledgedTotal.js +0 -2
- package/src/metrics/alertsActive.js +0 -2
- package/src/metrics/alertsEscalatedTotal.js +0 -2
- package/src/metrics/alertsFiredTotal.js +0 -2
- package/src/metrics/alertsReopenedTotal.js +0 -2
- package/src/metrics/alertsResolvedTotal.js +0 -2
- package/src/metrics/alertsSilencedTotal.js +0 -2
- package/src/metrics/approvalPending.js +0 -2
- package/src/metrics/approvalWaitDuration.js +0 -5
- package/src/metrics/approvalsDenied.js +0 -2
- package/src/metrics/approvalsGranted.js +0 -2
- package/src/metrics/approvalsRequested.js +0 -2
- package/src/metrics/attemptDuration.js +0 -3
- package/src/metrics/attentionBacklog.js +0 -2
- package/src/metrics/cacheHits.js +0 -2
- package/src/metrics/cacheMisses.js +0 -2
- package/src/metrics/dbQueryDuration.js +0 -3
- package/src/metrics/dbRetries.js +0 -2
- package/src/metrics/dbTransactionDuration.js +0 -3
- package/src/metrics/dbTransactionRetries.js +0 -2
- package/src/metrics/dbTransactionRollbacks.js +0 -2
- package/src/metrics/devtoolsActiveSubscribers.js +0 -2
- package/src/metrics/devtoolsBackpressureDisconnectTotal.js +0 -2
- package/src/metrics/devtoolsDeltaBuildMs.js +0 -3
- package/src/metrics/devtoolsEventBytes.js +0 -3
- package/src/metrics/devtoolsEventTotal.js +0 -2
- package/src/metrics/devtoolsSnapshotBuildMs.js +0 -5
- package/src/metrics/devtoolsSubscribeTotal.js +0 -2
- package/src/metrics/errorsTotal.js +0 -2
- package/src/metrics/eventsEmittedTotal.js +0 -2
- package/src/metrics/externalWaitAsyncPending.js +0 -2
- package/src/metrics/gatewayApprovalDecisionsTotal.js +0 -2
- package/src/metrics/gatewayAuthEventsTotal.js +0 -2
- package/src/metrics/gatewayConnectionsActive.js +0 -2
- package/src/metrics/gatewayConnectionsClosedTotal.js +0 -2
- package/src/metrics/gatewayConnectionsTotal.js +0 -2
- package/src/metrics/gatewayCronTriggersTotal.js +0 -2
- package/src/metrics/gatewayErrorsTotal.js +0 -2
- package/src/metrics/gatewayHeartbeatTicksTotal.js +0 -2
- package/src/metrics/gatewayMessagesReceivedTotal.js +0 -2
- package/src/metrics/gatewayMessagesSentTotal.js +0 -2
- package/src/metrics/gatewayRpcCallsTotal.js +0 -2
- package/src/metrics/gatewayRpcDuration.js +0 -3
- package/src/metrics/gatewayRunEventBackpressureDisconnectTotal.js +0 -4
- package/src/metrics/gatewayRunsCompletedTotal.js +0 -2
- package/src/metrics/gatewayRunsStartedTotal.js +0 -2
- package/src/metrics/gatewaySignalsTotal.js +0 -2
- package/src/metrics/gatewayWebhooksReceivedTotal.js +0 -2
- package/src/metrics/gatewayWebhooksRejectedTotal.js +0 -2
- package/src/metrics/gatewayWebhooksVerifiedTotal.js +0 -2
- package/src/metrics/heartbeatDataSizeBytes.js +0 -5
- package/src/metrics/heartbeatIntervalMs.js +0 -3
- package/src/metrics/hotReloadDuration.js +0 -3
- package/src/metrics/hotReloadFailures.js +0 -2
- package/src/metrics/hotReloads.js +0 -2
- package/src/metrics/httpRequestDuration.js +0 -3
- package/src/metrics/httpRequests.js +0 -2
- package/src/metrics/index.d.ts +0 -1222
- package/src/metrics/index.js +0 -168
- package/src/metrics/memoryFactReads.js +0 -3
- package/src/metrics/memoryFactWrites.js +0 -3
- package/src/metrics/memoryMessageSaves.js +0 -3
- package/src/metrics/memoryRecallDuration.js +0 -11
- package/src/metrics/memoryRecallQueries.js +0 -3
- package/src/metrics/metricsServiceAdapter.js +0 -181
- package/src/metrics/nodeDuration.js +0 -3
- package/src/metrics/nodeRetriesTotal.js +0 -2
- package/src/metrics/nodesFailed.js +0 -2
- package/src/metrics/nodesFinished.js +0 -2
- package/src/metrics/nodesStarted.js +0 -2
- package/src/metrics/openApiToolCallErrorsTotal.js +0 -3
- package/src/metrics/openApiToolCallsTotal.js +0 -3
- package/src/metrics/openApiToolDuration.js +0 -9
- package/src/metrics/processHeapUsedBytes.js +0 -2
- package/src/metrics/processMemoryRssBytes.js +0 -2
- package/src/metrics/processUptimeSeconds.js +0 -2
- package/src/metrics/promptSizeBytes.js +0 -3
- package/src/metrics/replaysStarted.js +0 -3
- package/src/metrics/responseSizeBytes.js +0 -3
- package/src/metrics/rewindDurationMs.js +0 -3
- package/src/metrics/rewindFramesDeleted.js +0 -3
- package/src/metrics/rewindRollbackTotal.js +0 -2
- package/src/metrics/rewindSandboxesReverted.js +0 -3
- package/src/metrics/rewindTotal.js +0 -2
- package/src/metrics/runDuration.js +0 -3
- package/src/metrics/runForksCreated.js +0 -3
- package/src/metrics/runsAncestryDepth.js +0 -3
- package/src/metrics/runsCancelledTotal.js +0 -2
- package/src/metrics/runsCarriedStateBytes.js +0 -5
- package/src/metrics/runsContinuedTotal.js +0 -2
- package/src/metrics/runsFailedTotal.js +0 -2
- package/src/metrics/runsFinishedTotal.js +0 -2
- package/src/metrics/runsResumedTotal.js +0 -2
- package/src/metrics/runsTotal.js +0 -2
- package/src/metrics/sandboxActive.js +0 -2
- package/src/metrics/sandboxBundleSizeBytes.js +0 -5
- package/src/metrics/sandboxCompletedTotal.js +0 -2
- package/src/metrics/sandboxCreatedTotal.js +0 -2
- package/src/metrics/sandboxDurationMs.js +0 -3
- package/src/metrics/sandboxPatchCount.js +0 -3
- package/src/metrics/sandboxTransportDurationMs.js +0 -5
- package/src/metrics/schedulerConcurrencyUtilization.js +0 -2
- package/src/metrics/schedulerQueueDepth.js +0 -2
- package/src/metrics/schedulerWaitDuration.js +0 -5
- package/src/metrics/scorerDuration.js +0 -9
- package/src/metrics/scorerEventsFailed.js +0 -2
- package/src/metrics/scorerEventsFinished.js +0 -2
- package/src/metrics/scorerEventsStarted.js +0 -2
- package/src/metrics/scorersFailed.js +0 -3
- package/src/metrics/scorersFinished.js +0 -3
- package/src/metrics/scorersStarted.js +0 -3
- package/src/metrics/smithersMetricCatalog.js +0 -893
- package/src/metrics/smithersMetricCatalogByKey.js +0 -2
- package/src/metrics/smithersMetricCatalogByName.js +0 -2
- package/src/metrics/smithersMetricCatalogByPrometheusName.js +0 -4
- package/src/metrics/snapshotDuration.js +0 -9
- package/src/metrics/snapshotsCaptured.js +0 -3
- package/src/metrics/supervisorPollDuration.js +0 -5
- package/src/metrics/supervisorPollsTotal.js +0 -2
- package/src/metrics/supervisorResumeLag.js +0 -5
- package/src/metrics/supervisorResumedTotal.js +0 -2
- package/src/metrics/supervisorSkippedTotal.js +0 -2
- package/src/metrics/supervisorStaleDetected.js +0 -2
- package/src/metrics/taskHeartbeatTimeoutTotal.js +0 -2
- package/src/metrics/taskHeartbeatsTotal.js +0 -2
- package/src/metrics/timerDelayDuration.js +0 -3
- package/src/metrics/timersCancelled.js +0 -2
- package/src/metrics/timersCreated.js +0 -2
- package/src/metrics/timersFired.js +0 -2
- package/src/metrics/timersPending.js +0 -2
- package/src/metrics/toPrometheusMetricName.js +0 -8
- package/src/metrics/tokensCacheReadTotal.js +0 -2
- package/src/metrics/tokensCacheWriteTotal.js +0 -2
- package/src/metrics/tokensContextWindowBucketTotal.js +0 -2
- package/src/metrics/tokensContextWindowPerCall.js +0 -5
- package/src/metrics/tokensInputPerCall.js +0 -3
- package/src/metrics/tokensInputTotal.js +0 -2
- package/src/metrics/tokensOutputPerCall.js +0 -3
- package/src/metrics/tokensOutputTotal.js +0 -2
- package/src/metrics/tokensReasoningTotal.js +0 -2
- package/src/metrics/toolCallErrorsTotal.js +0 -2
- package/src/metrics/toolCallsTotal.js +0 -2
- package/src/metrics/toolDuration.js +0 -3
- package/src/metrics/toolOutputTruncatedTotal.js +0 -2
- package/src/metrics/trackEvent.js +0 -608
- package/src/metrics/updateAsyncExternalWaitPending.js +0 -18
- package/src/metrics/updateProcessMetrics.js +0 -20
- package/src/metrics/vcsDuration.js +0 -3
- package/src/normalizeStructuredEvent.js +0 -15
- package/src/prometheusContentType.js +0 -1
- package/src/renderPrometheusMetrics.js +0 -212
- package/src/resolveAgentTraceCapabilities.js +0 -59
- package/src/resolveSmithersObservabilityOptions.js +0 -109
- package/src/smithersMetrics.js +0 -2
- package/src/smithersSpanNames.js +0 -6
- package/src/unsupportedKindsForCapabilities.js +0 -32
- package/src/withSmithersSpan.js +0 -17
- package/tempo/tempo.yml +0 -17
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Install a logger"
|
|
3
|
+
description: "Pick a Logger layer for a development shell, a production process, or a silent test, and understand the two rules every one of them follows: the ambient set is replaced, and the minimum level is left alone."
|
|
4
|
+
sidebar:
|
|
5
|
+
order: 4
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
`Logger` ships four layers over Effect's own logger machinery. They differ in
|
|
9
|
+
the sink; they agree on the two rules that decide what installing one does to
|
|
10
|
+
the rest of the application.
|
|
11
|
+
|
|
12
|
+
## Pick a layer
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import * as Logger from "@smthrs/observability/Logger"
|
|
16
|
+
|
|
17
|
+
// A development shell: colored, human-readable lines.
|
|
18
|
+
const development = Logger.layerPrettyDev()
|
|
19
|
+
|
|
20
|
+
// A production process: one structured JSON object per line.
|
|
21
|
+
const production = Logger.layerStructuredJson()
|
|
22
|
+
|
|
23
|
+
// A test that should print nothing.
|
|
24
|
+
const silent = Logger.layerNoop()
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`Logger.layerPrettyDev` chooses colors and mode from the terminal it finds, so
|
|
28
|
+
it degrades to plain text when the output is not a TTY.
|
|
29
|
+
`Logger.layerStructuredJson` is the one to ship: a log shipper can parse it, and
|
|
30
|
+
it is what a collector's log pipeline expects.
|
|
31
|
+
|
|
32
|
+
To install a logger you wrote, or one from another package, use `Logger.layer`:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import * as EffectLogger from "effect/Logger"
|
|
36
|
+
|
|
37
|
+
const custom = Logger.layer(
|
|
38
|
+
EffectLogger.make((options) => {
|
|
39
|
+
/* your sink */
|
|
40
|
+
})
|
|
41
|
+
)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Rule one: the ambient set is replaced
|
|
45
|
+
|
|
46
|
+
`mergeWithExisting` defaults to `false`, matching Effect's own `Logger.layer`,
|
|
47
|
+
so installing one of these layers replaces the ambient logger set rather than
|
|
48
|
+
adding to it. That is what you want for a process that has exactly one sink,
|
|
49
|
+
and it is the usual explanation for logs disappearing after a layer was added.
|
|
50
|
+
|
|
51
|
+
Pass `true` to keep the loggers already installed:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
const both = Logger.layerStructuredJson({ mergeWithExisting: true })
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The same option is on
|
|
58
|
+
[`JournalLogger.layerJournalForwarding`](./forward-logs-to-the-journal.md), and
|
|
59
|
+
merging is how a control-plane run gets console output and durable
|
|
60
|
+
`telemetry.log` rows from one log call.
|
|
61
|
+
|
|
62
|
+
## Rule two: the minimum level is left alone
|
|
63
|
+
|
|
64
|
+
A layer that adds a sink has no business overriding the level an application
|
|
65
|
+
chose elsewhere, so `References.MinimumLogLevel` is provided only when you name
|
|
66
|
+
`minimumLogLevel`. Omitted, whatever the application set stands, and Effect's
|
|
67
|
+
own `Info` default applies when nothing set it.
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
// Leaves the application's chosen level in place.
|
|
71
|
+
const inherits = Logger.layerStructuredJson()
|
|
72
|
+
|
|
73
|
+
// Pins Warn for the whole application.
|
|
74
|
+
const pinned = Logger.layerStructuredJson({ minimumLogLevel: "Warn" })
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`Logger.layerNoop` is the deliberate exception. Silence is its purpose, so it
|
|
78
|
+
pins `None` unless you name another level. Passing one is how a test silences
|
|
79
|
+
output while keeping a level assertion meaningful:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
const quietButFiltered = Logger.layerNoop({ minimumLogLevel: "Debug" })
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Merge when you install more than one sink
|
|
86
|
+
|
|
87
|
+
Installing a logger decides what is rendered locally. It exports nothing on its
|
|
88
|
+
own: log records reach a collector because [`Otlp`](./export-to-a-collector.md)
|
|
89
|
+
adds its own log exporter to the same ambient set, and they reach a run's
|
|
90
|
+
history because
|
|
91
|
+
[`JournalLogger`](./forward-logs-to-the-journal.md) adds a forwarder to it.
|
|
92
|
+
|
|
93
|
+
That shared set is why rule one matters here. A `Logger` layer with the default
|
|
94
|
+
`mergeWithExisting: false` discards every logger the set already held, and
|
|
95
|
+
whether that includes the OTLP exporter depends on which layer the composition
|
|
96
|
+
builds last. Pass `mergeWithExisting: true` on the `Logger` layer whenever
|
|
97
|
+
another sink is installed, and the ordering question does not arise:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import * as Effect from "effect/Effect"
|
|
101
|
+
|
|
102
|
+
const outcome = program.pipe(
|
|
103
|
+
Effect.provide(Logger.layerStructuredJson({ mergeWithExisting: true })),
|
|
104
|
+
Effect.provide(telemetry),
|
|
105
|
+
Effect.scoped
|
|
106
|
+
)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
One `Effect.logInfo` call then renders locally, posts to the collector's
|
|
110
|
+
`/v1/logs` path, and, with the journal forwarder installed, lands on the run's
|
|
111
|
+
durable history.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Read the runtime metrics"
|
|
3
|
+
description: "The four runtime metric handles this package declares, the exact series names a dashboard binds to, which package advances each one, and how to read a counter in process without an exporter."
|
|
4
|
+
sidebar:
|
|
5
|
+
order: 5
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
`Metric` declares the runtime signals that cross package boundaries: the
|
|
9
|
+
handles a producer updates and a dashboard reads. Every other metric in
|
|
10
|
+
Smithers belongs to the package that owns the behavior it measures.
|
|
11
|
+
|
|
12
|
+
## The six handles
|
|
13
|
+
|
|
14
|
+
| Handle | Series name | Kind | Advances when |
|
|
15
|
+
| ---------------------------- | ------------------------------------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
16
|
+
| `runThroughput` | `flows_run_throughput` | counter | A run's terminal transition commits in [`@smthrs/run-store`](/api/run-store). |
|
|
17
|
+
| `activeSeats` | `flows_seat_active` | gauge | Held for the lifetime of a production `Agent.run` stream in [`@smthrs/agent`](/api/agent), and released on success, failure, or interruption. |
|
|
18
|
+
| `quotaParks` | `flows_quota_park` | counter | A sealed quota decision is first executed, and not when that decision is replayed after a wake or a process restart. |
|
|
19
|
+
| `structuredOutputRejections` | `flows_agent_structured_output_rejections` | counter | An agent answer is refused by its declared output schema, in the same session or a fresh one. |
|
|
20
|
+
| `structuredOutputExhausted` | `flows_agent_structured_output_exhausted` | counter | An agent step fails after spending every output correction and its repair. |
|
|
21
|
+
| `droppedLogRecords` | `flows_observability_log_dropped` | counter | A log record is lost before durable delivery: once per queue overflow, once per journal delivery failure, and once per defect the forwarding worker recovers from. |
|
|
22
|
+
|
|
23
|
+
`Metric.registry` is the same six as one object, so a host can enumerate them.
|
|
24
|
+
The series names are the dashboard contract; treat them as public API.
|
|
25
|
+
|
|
26
|
+
Step-cache lookup and write counters belong to
|
|
27
|
+
[`@smthrs/step-cache`](/api/step-cache), and the store packages keep their own
|
|
28
|
+
handles. This package does not duplicate any of them.
|
|
29
|
+
|
|
30
|
+
## Read a counter in process
|
|
31
|
+
|
|
32
|
+
A metric handle is readable without an exporter, which makes it a fine
|
|
33
|
+
assertion target in a test and a fine health check in a host:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import * as ObservabilityMetric from "@smthrs/observability/Metric"
|
|
37
|
+
import * as Effect from "effect/Effect"
|
|
38
|
+
import * as Metric from "effect/Metric"
|
|
39
|
+
|
|
40
|
+
const dropped = Effect.gen(function*() {
|
|
41
|
+
const value = yield* Metric.value(ObservabilityMetric.droppedLogRecords)
|
|
42
|
+
return value.count
|
|
43
|
+
})
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
A counter's value carries `count`; a gauge's carries `value`.
|
|
47
|
+
|
|
48
|
+
## Read a dimensioned counter through its attribute view
|
|
49
|
+
|
|
50
|
+
Some packages update a counter through a tagged view rather than the bare
|
|
51
|
+
handle. The dispatch counter in [`@smthrs/engine-store`](/api/engine-store) is
|
|
52
|
+
the common example: it updates only the series tagged with the outcome, so
|
|
53
|
+
reading the bare handle reads the attribute-less series and always sees zero.
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { EngineStoreMetrics } from "@smthrs/engine-store"
|
|
57
|
+
|
|
58
|
+
const dispatches = Effect.gen(function*() {
|
|
59
|
+
const value = yield* Metric.value(EngineStoreMetrics.dispatch.Success)
|
|
60
|
+
return value.count
|
|
61
|
+
})
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Read the exported view, not the parent handle, whenever the owning package
|
|
65
|
+
exports one.
|
|
66
|
+
|
|
67
|
+
## Isolate the registry in a test
|
|
68
|
+
|
|
69
|
+
Effect keeps metric state in a `MetricRegistry` reference with a
|
|
70
|
+
process-default value, so counters accumulate across tests in one process.
|
|
71
|
+
Provide a fresh map to isolate one run:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
Effect.provideService(Metric.MetricRegistry, new Map())
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Where the series go
|
|
78
|
+
|
|
79
|
+
Every registered series exports with the rest of the telemetry: with
|
|
80
|
+
[`Otlp` provided](./export-to-a-collector.md), they post to the collector's
|
|
81
|
+
`/v1/metrics` path under the `service.name` you configured. With no exporter
|
|
82
|
+
installed, they are still collected and still readable in process, and nothing
|
|
83
|
+
leaves the machine.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Test telemetry without a collector"
|
|
3
|
+
description: "Assert on what would have been exported: a recording fetch for OTLP payloads, TestJournal for forwarded log records, a fresh metric registry, and the noop layers that stand in for absent telemetry."
|
|
4
|
+
sidebar:
|
|
5
|
+
order: 6
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Telemetry is testable because every outbound edge is a service. Replace the
|
|
9
|
+
edge, keep the wiring, and the assertions are about the payloads a collector
|
|
10
|
+
would have received.
|
|
11
|
+
|
|
12
|
+
## Record what the exporter posts
|
|
13
|
+
|
|
14
|
+
`Otlp.layerFetch` delivers over the `FetchHttpClient.Fetch` service, so
|
|
15
|
+
providing your own function captures every export request and touches no
|
|
16
|
+
network:
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import * as Otlp from "@smthrs/observability/Otlp"
|
|
20
|
+
import * as Effect from "effect/Effect"
|
|
21
|
+
import * as Metric from "effect/Metric"
|
|
22
|
+
import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient"
|
|
23
|
+
|
|
24
|
+
const recordingFetch = () => {
|
|
25
|
+
const requests: Array<{ url: string; body: string }> = []
|
|
26
|
+
const fetch: typeof globalThis.fetch = (input, init) => {
|
|
27
|
+
const body = typeof init?.body === "string"
|
|
28
|
+
? init.body
|
|
29
|
+
: new TextDecoder().decode(init?.body as Uint8Array)
|
|
30
|
+
requests.push({ url: String(input), body })
|
|
31
|
+
return Promise.resolve(new Response("{}", { status: 200 }))
|
|
32
|
+
}
|
|
33
|
+
return { requests, fetch }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const collector = recordingFetch()
|
|
37
|
+
|
|
38
|
+
await Effect.runPromise(
|
|
39
|
+
program.pipe(
|
|
40
|
+
Effect.provide(Otlp.layerFetch({ baseUrl: "http://collector.invalid:4318" })),
|
|
41
|
+
Effect.provideService(FetchHttpClient.Fetch, collector.fetch),
|
|
42
|
+
Effect.provideService(Metric.MetricRegistry, new Map()),
|
|
43
|
+
Effect.orDie,
|
|
44
|
+
Effect.scoped
|
|
45
|
+
)
|
|
46
|
+
)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Assert after the scope closes, because the shutdown flush is what delivers a
|
|
50
|
+
short program's signals. Filter `collector.requests` by URL suffix to separate
|
|
51
|
+
the three signals, and parse a body to read span or series names. An
|
|
52
|
+
unresolvable host such as `collector.invalid` is a deliberate choice: if the
|
|
53
|
+
service is ever missed, the test fails rather than reaching the network.
|
|
54
|
+
|
|
55
|
+
The fresh `Metric.MetricRegistry` map keeps one test's counters out of the
|
|
56
|
+
next test's export.
|
|
57
|
+
|
|
58
|
+
## Test the journal forwarder
|
|
59
|
+
|
|
60
|
+
`TestJournal.layer()` from [`@smthrs/journal`](/api/journal) provides the
|
|
61
|
+
production SQLite journal over an in-memory database, so a forwarding test
|
|
62
|
+
exercises the real write path:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import * as Journal from "@smthrs/journal/Journal"
|
|
66
|
+
import * as TestJournal from "@smthrs/journal/test/TestJournal"
|
|
67
|
+
import * as JournalLogger from "@smthrs/observability/JournalLogger"
|
|
68
|
+
import * as Effect from "effect/Effect"
|
|
69
|
+
import * as Layer from "effect/Layer"
|
|
70
|
+
|
|
71
|
+
// `runId` is a decoded JournalEvent.RunId, as in the forwarding guide.
|
|
72
|
+
const entries = Effect.gen(function*() {
|
|
73
|
+
const journal = yield* Journal.Journal
|
|
74
|
+
yield* Effect.logInfo("hello")
|
|
75
|
+
const page = yield* journal.entries({ runId, limit: 100 })
|
|
76
|
+
return page.entries
|
|
77
|
+
}).pipe(
|
|
78
|
+
Effect.provide(Layer.provideMerge(JournalLogger.layerJournalForwarding({ runId }), TestJournal.layer())),
|
|
79
|
+
Effect.scoped
|
|
80
|
+
)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Forwarding is asynchronous, so poll until the expected number of entries has
|
|
84
|
+
landed rather than sleeping a fixed interval. To assert on a loss instead,
|
|
85
|
+
read `Metric.value(ObservabilityMetric.droppedLogRecords)`.
|
|
86
|
+
|
|
87
|
+
## Control time for the retry window
|
|
88
|
+
|
|
89
|
+
Export retries and the 60-second disable window run on Effect's clock, so
|
|
90
|
+
`TestClock` makes them deterministic:
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
import { TestClock } from "effect/testing"
|
|
94
|
+
|
|
95
|
+
const exported = Effect.gen(function*() {
|
|
96
|
+
yield* Effect.void.pipe(Effect.withSpan("exported"))
|
|
97
|
+
// One initial request plus three transient retries completes within four seconds.
|
|
98
|
+
yield* TestClock.adjust("5 seconds")
|
|
99
|
+
})
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Provide `TestClock.layer()` alongside the exporter, then advance the clock and
|
|
103
|
+
count the recorded requests.
|
|
104
|
+
|
|
105
|
+
## Stand telemetry down explicitly
|
|
106
|
+
|
|
107
|
+
A test that is not about telemetry should say so rather than leave the layer
|
|
108
|
+
out and let a type error decide:
|
|
109
|
+
|
|
110
|
+
| Layer | What it does |
|
|
111
|
+
| -------------------- | --------------------------------------------------------------- |
|
|
112
|
+
| `Otlp.layerNoop` | Provides nothing and exports nothing. |
|
|
113
|
+
| `Otel.layerNoop` | The empty provider-neutral OTEL slot. |
|
|
114
|
+
| `Logger.layerNoop()` | Removes the logger set and pins `None` unless you name a level. |
|
|
115
|
+
|
|
116
|
+
`Journal.layerNoop` from [`@smthrs/journal`](/api/journal) is the matching stub
|
|
117
|
+
for the forwarder when the assertion is about queue behavior rather than about
|
|
118
|
+
persistence.
|
|
119
|
+
|
|
120
|
+
## Assert on refusals at acquisition
|
|
121
|
+
|
|
122
|
+
Both configuration refusals happen while the layer is built, so a test builds
|
|
123
|
+
the layer and inspects the exit rather than running a program:
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
import * as Layer from "effect/Layer"
|
|
127
|
+
|
|
128
|
+
const exit = await Effect.runPromiseExit(
|
|
129
|
+
Effect.scoped(Layer.build(Otlp.layerFetch({ baseUrl: "collector:4318" })))
|
|
130
|
+
)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The failure is an `Endpoint.InvalidExporterEndpoint` whose `path` is `baseUrl`.
|
|
134
|
+
See [Troubleshooting](../troubleshooting.md) for the catalog.
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Wire an OpenTelemetry SDK"
|
|
3
|
+
description: "Use NodeOtel, BrowserOtel, or Otel when a collector endpoint is not enough: the Node OTLP/HTTP layer, browser processors you construct, and bridging providers an application already built."
|
|
4
|
+
sidebar:
|
|
5
|
+
order: 2
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
[`Otlp`](./export-to-a-collector.md) covers a collector endpoint and nothing
|
|
9
|
+
else. Three builders exist for the cases it does not cover: existing
|
|
10
|
+
OpenTelemetry instrumentation you must feed, a vendor exporter that is not
|
|
11
|
+
OTLP/HTTP JSON, and a provider a framework hands you already constructed.
|
|
12
|
+
|
|
13
|
+
All three validate `Resource.Configuration`. `NodeOtel` and `BrowserOtel`
|
|
14
|
+
construct providers with that resource. `Otel` supplies it to provider factories
|
|
15
|
+
and metrics; already-created providers retain their own resource.
|
|
16
|
+
|
|
17
|
+
## Node: build the SDK for me
|
|
18
|
+
|
|
19
|
+
`NodeOtel.layerOtel` allocates the whole Node OpenTelemetry SDK: a
|
|
20
|
+
`BatchSpanProcessor`, a `BatchLogRecordProcessor`, and a
|
|
21
|
+
`PeriodicExportingMetricReader`, each behind its own OTLP/HTTP exporter.
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import * as NodeOtel from "@smthrs/observability/NodeOtel"
|
|
25
|
+
import * as Effect from "effect/Effect"
|
|
26
|
+
|
|
27
|
+
const telemetry = NodeOtel.layerOtel({
|
|
28
|
+
endpoint: "http://localhost:4318",
|
|
29
|
+
resource: { serviceName: "deploy-status", serviceVersion: "2.4.1" },
|
|
30
|
+
exportIntervalMillis: 60_000,
|
|
31
|
+
shutdownTimeout: "10 seconds"
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const outcome = program.pipe(Effect.provide(telemetry), Effect.scoped)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The exporters are created when the scoped layer is built, not when the module
|
|
38
|
+
is imported, so holding the builder costs nothing. Closing the scope
|
|
39
|
+
force-flushes both batch processors and collects the metric reader once, which
|
|
40
|
+
makes release, rather than the interval, the deterministic flush.
|
|
41
|
+
|
|
42
|
+
The endpoint option is named `endpoint` here and `baseUrl` on `Otlp`; each
|
|
43
|
+
refusal names the option it arrived on, so the message points at your own
|
|
44
|
+
field.
|
|
45
|
+
|
|
46
|
+
`NodeOtel` takes no `headers` option. The OpenTelemetry exporters read the
|
|
47
|
+
standard `OTEL_EXPORTER_OTLP_HEADERS`, `_TIMEOUT`, `_COMPRESSION`, and TLS
|
|
48
|
+
certificate variables from the process environment and send them to your
|
|
49
|
+
explicit `endpoint`, which always wins over `OTEL_EXPORTER_OTLP_ENDPOINT`.
|
|
50
|
+
Set those variables only for this collector. `Otlp` reads none of them.
|
|
51
|
+
|
|
52
|
+
Import `NodeOtel` only from code that runs on Node. See
|
|
53
|
+
[the layer map](../concepts/layer-map.md) for why it is absent from the root
|
|
54
|
+
entry point.
|
|
55
|
+
|
|
56
|
+
## Browser: bring your own processors
|
|
57
|
+
|
|
58
|
+
`BrowserOtel.layerOtel` builds the web SDK around processors and readers the
|
|
59
|
+
application constructs. That keeps browser exporter policy, and every exporter
|
|
60
|
+
package a bundle would have to carry, in the application rather than in this
|
|
61
|
+
one.
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"
|
|
65
|
+
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base"
|
|
66
|
+
import * as BrowserOtel from "@smthrs/observability/BrowserOtel"
|
|
67
|
+
|
|
68
|
+
const telemetry = BrowserOtel.layerOtel({
|
|
69
|
+
resource: { serviceName: "console-ui" },
|
|
70
|
+
spanProcessor: () =>
|
|
71
|
+
new BatchSpanProcessor(
|
|
72
|
+
new OTLPTraceExporter({ url: "https://otlp.example.com/v1/traces" })
|
|
73
|
+
)
|
|
74
|
+
})
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Every processor field is optional, and a layer with none still builds: it
|
|
78
|
+
provides the resource and bridges nothing. Return an array to install several
|
|
79
|
+
processors for one signal. Each field is a factory because the layer owns what
|
|
80
|
+
it returns: the factory runs once at acquisition, and the web SDK shuts down
|
|
81
|
+
every processor and reader when the scope closes.
|
|
82
|
+
|
|
83
|
+
If all you need in a browser is OTLP delivery, use `Otlp.layerFetch` instead.
|
|
84
|
+
It is smaller, needs no SDK, and is browser-safe by construction.
|
|
85
|
+
|
|
86
|
+
## Bridge providers you control
|
|
87
|
+
|
|
88
|
+
`Otel.layerOtel` allocates no exporter at all. It takes an OpenTelemetry
|
|
89
|
+
`TracerProvider` and `LoggerProvider`, plus a factory that builds the metric
|
|
90
|
+
readers, and bridges Effect's tracer, logger, and metrics onto them. Provider
|
|
91
|
+
fields also accept synchronous factories. Every factory runs during layer
|
|
92
|
+
acquisition, after resource validation, and the provider factories receive the
|
|
93
|
+
same resource as the metric producer. Pass it to the SDK constructor to export
|
|
94
|
+
all signals with the configured service identity:
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import { LoggerProvider } from "@opentelemetry/sdk-logs"
|
|
98
|
+
import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics"
|
|
99
|
+
import { BasicTracerProvider } from "@opentelemetry/sdk-trace-base"
|
|
100
|
+
import * as Otel from "@smthrs/observability/Otel"
|
|
101
|
+
import * as Effect from "effect/Effect"
|
|
102
|
+
|
|
103
|
+
let tracerProvider: BasicTracerProvider | undefined
|
|
104
|
+
let loggerProvider: LoggerProvider | undefined
|
|
105
|
+
const telemetry = Otel.layerOtel({
|
|
106
|
+
resource: {
|
|
107
|
+
serviceName: "deploy-status",
|
|
108
|
+
serviceVersion: "2.4.1",
|
|
109
|
+
attributes: { region: "us-west" }
|
|
110
|
+
},
|
|
111
|
+
tracerProvider: (resource) =>
|
|
112
|
+
tracerProvider = new BasicTracerProvider({
|
|
113
|
+
resource,
|
|
114
|
+
spanProcessors: [spanProcessor]
|
|
115
|
+
}),
|
|
116
|
+
loggerProvider: (resource) =>
|
|
117
|
+
loggerProvider = new LoggerProvider({
|
|
118
|
+
resource,
|
|
119
|
+
processors: [logRecordProcessor]
|
|
120
|
+
}),
|
|
121
|
+
metricReader: () => new PeriodicExportingMetricReader({ exporter: metricExporter }),
|
|
122
|
+
metricTemporality: "cumulative"
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
await Effect.runPromise(program.pipe(Effect.provide(telemetry), Effect.scoped))
|
|
127
|
+
} finally {
|
|
128
|
+
await Promise.all([tracerProvider?.shutdown(), loggerProvider?.shutdown()])
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Ownership splits between the two kinds of field. Providers are borrowed: the
|
|
133
|
+
application owns their flushing and shutdown, including providers returned by
|
|
134
|
+
factories, and this layer never shuts one down. Readers are owned: the
|
|
135
|
+
`metricReader` factory runs once during acquisition, and closing the scope shuts
|
|
136
|
+
down every reader it returned. Build the readers inside the factory rather than
|
|
137
|
+
handing over one a longer-lived composition still exports through, which that
|
|
138
|
+
shutdown would disable. Construct `spanProcessor` and `logRecordProcessor` with
|
|
139
|
+
your exporters and give them to the provider you build.
|
|
140
|
+
|
|
141
|
+
You can still pass existing providers directly. The layer cannot change the
|
|
142
|
+
resource they captured at construction. In that form, the resource option
|
|
143
|
+
controls only metrics and tracer instrumentation scope, not log or span resource
|
|
144
|
+
attributes. Construct both providers with the intended resource before injection,
|
|
145
|
+
or use the factories above to receive the validated resource.
|
|
146
|
+
|
|
147
|
+
Each of the three is optional and each is bridged only when supplied, so a
|
|
148
|
+
composition with a tracer and no meter installs only the tracer bridge. A
|
|
149
|
+
`metricReader` factory that returns an empty array registers no reader at all
|
|
150
|
+
rather than a misconfigured one.
|
|
151
|
+
|
|
152
|
+
Two options control merge behavior: `loggerMergeWithExisting` keeps the ambient
|
|
153
|
+
Effect loggers alongside the OpenTelemetry logger, and `metricTemporality`
|
|
154
|
+
selects the temporality preference the metric bridge reports.
|
|
155
|
+
|
|
156
|
+
`Otel.layerNoop` is the explicit empty slot for a composition that wants the
|
|
157
|
+
option without the wiring.
|
|
158
|
+
|
|
159
|
+
## Provide only the resource
|
|
160
|
+
|
|
161
|
+
`Resource.layer` provides the validated OpenTelemetry resource by itself, for a
|
|
162
|
+
composition that assembles the rest of the SDK on its own:
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
import * as Resource from "@smthrs/observability/Resource"
|
|
166
|
+
|
|
167
|
+
const resource = Resource.layer({
|
|
168
|
+
serviceName: "deploy-status",
|
|
169
|
+
attributes: { "deployment.environment.name": "production" }
|
|
170
|
+
})
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
`Resource.configToAttributes` is the pure projection of the same configuration
|
|
174
|
+
into OpenTelemetry attributes, for code that needs the attribute record rather
|
|
175
|
+
than a layer. It reads no environment variables: attributes come from your
|
|
176
|
+
configuration and the SDK's fixed `telemetry.sdk.name` and
|
|
177
|
+
`telemetry.sdk.language` fields, which are included in the admission budget.
|
|
178
|
+
|
|
179
|
+
## Default OTLP export limits
|
|
180
|
+
|
|
181
|
+
`Otlp.layer` and `Otlp.layerFetch` share a limit of four active HTTP requests
|
|
182
|
+
across logs, traces, and metrics within each layer acquisition. Each request has
|
|
183
|
+
a ten-second timeout that interrupts the transport and aborts a stalled fetch.
|
|
184
|
+
`shutdownTimeout` separately bounds the final scope flush.
|
|
185
|
+
|
|
186
|
+
Logs and traces flush at 1,000 records or the export interval. The transport has
|
|
187
|
+
no waiting queue: a batch arriving while all four slots are occupied is dropped.
|
|
188
|
+
Serialized JSON payloads larger than 1 MiB are also dropped, limiting active
|
|
189
|
+
request payloads to 4 MiB. This limit applies after serialization; it does not
|
|
190
|
+
bound individual application records, temporary serialization allocations, or
|
|
191
|
+
the application's metric registry.
|
|
192
|
+
|
|
193
|
+
Resource admission caps cumulative encoded metadata at 128 KiB and bounds
|
|
194
|
+
arrays at 256 elements. The byte budget includes SDK-added telemetry fields,
|
|
195
|
+
reserved even for the default Effect exporter. Both the default exporter and
|
|
196
|
+
the Node SDK layer isolate ambient resource configuration after validation.
|
|
197
|
+
The default exporter also budgets the request envelope
|
|
198
|
+
at 8 KiB and reserves `Otlp.reservedBatchBytes` (888 KiB) for signals: roughly
|
|
199
|
+
900 bytes per record
|
|
200
|
+
in a 1,000-record log or span batch. Every layer validates these resource limits
|
|
201
|
+
before allocating exporters, including injected SDK layers.
|
|
202
|
+
|
|
203
|
+
The Effect counter `flows_observability_otlp_dropped` increments once per batch
|
|
204
|
+
discarded for saturation, payload size, or timeout. Discards are terminal and
|
|
205
|
+
are not retried. Read the counter locally
|
|
206
|
+
with `Metric.value(Metric.counter("flows_observability_otlp_dropped"))` during an
|
|
207
|
+
outage. Its exported value becomes available when the collector recovers.
|
|
208
|
+
|
|
209
|
+
Because that counter shares the export path, each acquisition also emits at
|
|
210
|
+
most one warning every 60 seconds through the ambient loggers captured before
|
|
211
|
+
installing the OTLP logger. The warning has code `otlp_export_discarded`, reason
|
|
212
|
+
`oversized`, `saturated`, or `stalled`, encoded bytes, request limit, and total
|
|
213
|
+
discarded batches. It includes no payload, URL, or inherited annotations and
|
|
214
|
+
cannot re-enter this exporter even during an application-triggered flush. Keep
|
|
215
|
+
an ambient logger installed if loss diagnostics should remain visible during
|
|
216
|
+
an export outage.
|
|
217
|
+
|
|
218
|
+
Ordinary HTTP and network failures retain Effect's retry policy. These limits
|
|
219
|
+
apply to the default `Otlp` layers; SDK processors supplied to the other builders
|
|
220
|
+
retain their own export policies.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Installation"
|
|
3
|
+
description: "Install @smthrs/observability, its runtime requirements and Effect version, its import forms, and the rule that keeps the root entry point bundling for a browser."
|
|
4
|
+
sidebar:
|
|
5
|
+
order: 1
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Install the package
|
|
9
|
+
|
|
10
|
+
Not on npm yet; see [Installation](https://github.com/smithersai/smithers/blob/main/packages/smithers/flows/flow/docs/installation.md#use-the-libraries).
|
|
11
|
+
|
|
12
|
+
The package requires Node.js 26.4.0 or later and ships as both ESM and
|
|
13
|
+
CommonJS with TypeScript declarations.
|
|
14
|
+
|
|
15
|
+
## Pin the same Effect release
|
|
16
|
+
|
|
17
|
+
Effect services are identified by module identity, so a composition that
|
|
18
|
+
resolves two copies of `effect` splits its context and a provided layer stops
|
|
19
|
+
satisfying a requirement. Pin `effect` at exactly `4.0.0-rc.115`.
|
|
20
|
+
|
|
21
|
+
`Otlp` is written entirely against `effect`, including its HTTP client and its
|
|
22
|
+
`effect/unstable/observability/Otlp` exporters. The required peers
|
|
23
|
+
`@effect/opentelemetry@4.0.0-rc.115` and `@opentelemetry/api@1.9.1`, plus
|
|
24
|
+
the logs and metrics dependencies, support
|
|
25
|
+
the root `Otel` and `Resource` modules. They install with ordinary peer-aware
|
|
26
|
+
package managers. The default install includes no HTTP exporters or trace SDK.
|
|
27
|
+
|
|
28
|
+
`NodeOtel` selects the optional Node trace SDK and three HTTP exporters:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pnpm add @opentelemetry/exporter-logs-otlp-http@0.222.0 @opentelemetry/exporter-metrics-otlp-http@0.222.0 @opentelemetry/exporter-trace-otlp-http@0.222.0 @opentelemetry/sdk-trace-base@2.11.0 @opentelemetry/sdk-trace-node@2.11.0
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`BrowserOtel` selects the optional browser trace SDK instead:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pnpm add @opentelemetry/sdk-trace-base@2.11.0 @opentelemetry/sdk-trace-web@2.11.0
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The browser adapter accepts the exporters you supply; those HTTP exporter
|
|
41
|
+
packages are not prerequisites for constructing its layer.
|
|
42
|
+
|
|
43
|
+
## Import forms
|
|
44
|
+
|
|
45
|
+
The root entry point re-exports seven modules as namespaces:
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { Endpoint, JournalLogger, Logger, Metric, Otel, Otlp, Resource } from "@smthrs/observability"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Each is also importable from its own subpath, which is the form the
|
|
52
|
+
[API reference](./api.md) uses:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import * as JournalLogger from "@smthrs/observability/JournalLogger"
|
|
56
|
+
import * as Otlp from "@smthrs/observability/Otlp"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`@smthrs/observability/package.json` is exported.
|
|
60
|
+
|
|
61
|
+
## The two subpath-only modules
|
|
62
|
+
|
|
63
|
+
`NodeOtel` and `BrowserOtel` are not re-exported from the root, and importing
|
|
64
|
+
them by subpath is the only way to reach them:
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
import * as BrowserOtel from "@smthrs/observability/BrowserOtel"
|
|
68
|
+
import * as NodeOtel from "@smthrs/observability/NodeOtel"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`NodeOtel` resolves Node-only host modules: `@effect/opentelemetry/NodeSdk` and
|
|
72
|
+
`@opentelemetry/sdk-trace-node`, which reaches the bare `async_hooks` specifier
|
|
73
|
+
through `@opentelemetry/context-async-hooks`. Re-exporting it from the root
|
|
74
|
+
would put a module a browser bundler cannot resolve in the entry point, and
|
|
75
|
+
break the browser bundle the root guarantees. Nothing the root entry point
|
|
76
|
+
reaches resolves a `node:` built-in, so it bundles for a browser as it is.
|
|
77
|
+
|
|
78
|
+
Import `NodeOtel` only from code that runs on Node, and `BrowserOtel` only from
|
|
79
|
+
code that runs in a browser. `Otlp` is safe in both, because it exports over
|
|
80
|
+
Effect's `HttpClient` and never touches a `node:` built-in.
|
|
81
|
+
|
|
82
|
+
## What a real composition adds
|
|
83
|
+
|
|
84
|
+
The exporter alone needs nothing else. Two features have a dependency:
|
|
85
|
+
|
|
86
|
+
- `Otlp.layer` requires an Effect `HttpClient` from
|
|
87
|
+
`effect/unstable/http/HttpClient`. `Otlp.layerFetch` supplies the host's
|
|
88
|
+
global `fetch` and is the usual choice; a Node host that wants Undici instead
|
|
89
|
+
provides `NodeHttpClient.layerUndici`, re-exported by
|
|
90
|
+
[`@smthrs/platform-node`](/api/platform-node) as `NodeHost.NodeHttpClient`.
|
|
91
|
+
- `JournalLogger.layerJournalForwarding` requires a `Journal.Journal` service
|
|
92
|
+
from [`@smthrs/journal`](/api/journal). A durable engine composition already
|
|
93
|
+
provides one.
|
|
94
|
+
|
|
95
|
+
## Next step
|
|
96
|
+
|
|
97
|
+
Export your first trace, log record, and metric series in the
|
|
98
|
+
[Quickstart](./quickstart.md).
|