---
title: @memhtml/telemetry
---

`@memhtml/telemetry`: the opt-in trace exporter. It sends the spans memhtml already records to an OpenTelemetry collector over OTLP HTTP, and it does nothing unless `OTEL_EXPORTER_OTLP_ENDPOINT` is set.

The package exports one function and two interfaces. `layerTelemetry` takes a `TelemetryOptions` and returns an Effect layer that a runtime assembly provides. The CLI provides it with the service name `memhtml-cli` (`apps/cli/src/run.ts`), and the MCP server with `memhtml-mcp` (`apps/mcp/src/server.ts`).

With `OTEL_EXPORTER_OTLP_ENDPOINT` unset or blank, the layer is `Layer.empty` and no socket is opened.

With the variable set, the layer replaces Effect's tracer with Effect's own `OtlpTracer`, which batches spans and posts them as JSON to `<endpoint>/v1/traces`, with trailing slashes on the endpoint dropped. The `service.name` resource attribute is `OTEL_SERVICE_NAME` when that variable is set and not blank, and the assembly's `serviceName` otherwise.

`layerTelemetry` reads `OTEL_EXPORTER_OTLP_ENDPOINT` and `OTEL_SERVICE_NAME` when it is called, from `options.env` when given and `process.env` otherwise. `TelemetryModules` names the two Effect modules the exporter needs, `effect/http` and `effect/observability`. This package imports them only when the endpoint is set, through a loader that `options.load` replaces so a test can check that the disabled path never calls it.

A failed export does not fail the command or change the envelope on stdout, the exit code or the MCP stream. The exporter retries transient failures, then stops exporting for a while. The first failed connection to the collector logs one warning on stderr, and an error status from the collector logs none.

## Modules

The package is one module, `src/index.ts`, published as the import path `@memhtml/telemetry`, so this page is its whole exported surface.

## Interfaces

### TelemetryModules

The two Effect modules the exporter needs, imported here only when the endpoint is set.

#### Properties

##### http

```ts
readonly http: __module;
```

The `effect/http` module, for the fetch-backed HTTP client the exporter posts spans with.

##### observability

```ts
readonly observability: __module;
```

The `effect/observability` module, for `OtlpTracer` and its JSON serialization.

***

### TelemetryOptions

What a runtime assembly passes to [layerTelemetry](/api/telemetry/#layertelemetry).

#### Properties

##### env?

```ts
readonly env?: Record<string, string | undefined>;
```

Where the endpoint and `OTEL_SERVICE_NAME` are read, defaulting to `process.env`, when the
factory is CALLED rather than through `effect/Config`. Config's default provider is built once
per process and cached (`hookConfigProvider` in `apps/cli/src/hook.ts` records the probe), and
this factory runs once per invocation, so either variable set by a test after an earlier read
must still be seen.

##### load?

```ts
readonly load?: () => Promise<TelemetryModules>;
```

The module loader, injectable so a test can PROVE the disabled path loads nothing: the
acceptance bar is not "no spans exported" but "no exporter code even evaluated", and only a
loader spy can tell those apart.

###### Returns

`Promise`\<[`TelemetryModules`](/api/telemetry/#telemetrymodules)\>

##### serviceName

```ts
readonly serviceName: string;
```

The `service.name` resource attribute when `OTEL_SERVICE_NAME` is unset or blank: which of the
runtime assemblies this process is (`memhtml-cli`, `memhtml-mcp`), because one collector
typically receives both and the operator's first question is which process a trace came from.

## Functions

### layerTelemetry()

```ts
function layerTelemetry(options): Layer<never>;
```

The tracer layer for one runtime assembly.

`OTEL_EXPORTER_OTLP_ENDPOINT` unset or blank returns `Layer.empty`: the program runs as it would
without this package, with no exporter module loaded and no socket opened. Set, the layer
overrides effect's tracer reference with a batching OTLP HTTP exporter aimed at
`<endpoint>/v1/traces` (the standard per-signal path; a trailing slash on the endpoint is
tolerated because both spellings arrive from shell profiles).

A failed export never fails a command. The exporter retries transient failures, then disables
itself and drops the batch, and the program's error channel never sees it, so the envelope, the
exit code, and the MCP stream are exactly what they would have been. The final batch of a
short-lived process is flushed when the layer's scope closes, bounded by the exporter's own
shutdown timeout so a dead collector delays exit by seconds, never hangs it.

#### Parameters

##### options

[`TelemetryOptions`](/api/telemetry/#telemetryoptions)

#### Returns

`Layer`\<`never`\>