# Trace operations where they mean something

> For agents: start with the [agent guide](https://ratstack.sh/llms.txt). Every page is Markdown by default; add `Accept: text/html` for HTML.

Free workshop: [how to burn a trillion tokens and get good results](/tokenmaxx#interested).

Name the operation a person needs to understand. Add only context safe for that operation's telemetry.

A tracing span records an operation's timing and outcome. Export is a separate delivery step.

## Name the operation, not each expression

`FileInspector.inspect` uses a named `Effect.fn`:

packages/core/src/file-inspector.ts at 65e9465f38092e24486392f22c08f45d61230c20; lines 8-25; highlighted none.

```typescript
  {
    readonly inspect: (
      path: string
    ) => Effect.Effect<FileStats, FileStatsError>;
  }
>()("@rat-stack/core/FileInspector", {
  make: Effect.gen(function* makeFileInspector() {
    const fileSystem = yield* FileSystem.FileSystem;

    const inspect = Effect.fn("FileInspector.inspect")(function* inspect(
      path: string
    ) {
      const content = yield* fileSystem
        .readFile(path)
        .pipe(
          Effect.mapError(
            (error) => new FileStatsError({ path, reason: error.message })
          )
```

Use a named `fn` for a reusable operation that deserves a span.
Use `withSpan` around an existing Effect when that boundary carries useful meaning.
Use `fnUntraced` for reusable functions that do not need tracing, especially small library functions and hot paths.

Do not create a span merely because a helper exists.
Choose boundaries that answer a question about the job's duration, dependencies or failure.

## Add safe attributes deliberately

A span name should remain stable across requests.
Put safe variation in attributes instead of generating a new name for every input.
Keep unrestricted input, credentials, request bodies and private identities out of telemetry.

| Useful context                             | Avoid                                  |
| ------------------------------------------ | -------------------------------------- |
| Operation phase or bounded outcome name    | Full request or response bodies        |
| A deliberate public category               | Credentials and authorization headers  |
| A measured count relevant to the operation | Unrestricted input or private identity |

Structured attributes can outlive the request. Structure does not make a private value safe.

Use `annotateCurrentSpan` when adding attributes inside the operation.
Use `annotateSpans` when supplying annotations through an enclosing Effect.
Review the resulting payload, not only the annotation call.

## Supply an exporter, serializer and transport

The checked fixture composes the OTLP tracer with JSON serialization and an in-memory HTTP client:

packages/core/test/trace-export.test.ts at 4019cffbcbdb282d6f006854ff5b1f26d3b407e0; lines 51-65; highlighted none.

```typescript
      const tracing = OtlpTracer.layer({
        resource: { serviceName: "wiki-fixture" },
        url: "https://collector.example.test/v1/traces",
      }).pipe(
        Layer.provide(OtlpSerialization.layerJson),
        Layer.provide(Layer.succeed(HttpClient.HttpClient, client))
      );

      yield* Effect.scoped(
        Effect.void.pipe(
          Effect.withSpan("fixture.read"),
          Effect.annotateSpans({ phase: "read" }),
          Effect.provide(tracing)
        )
      );
```

These observability modules are marked unstable in the exact 4.0.0 source.
A stable package version does not make every exported module's API stable.
Keep the exact pin and check its source before changing exporter composition.

## Check the delivery boundary you actually exercised

The fixture's transport decodes outbound JSON through a small Schema.
It records the payload in a Ref instead of contacting a collector.
After shutdown, the test requires the operation name and the annotation key.

packages/core/test/trace-export.test.ts at 4019cffbcbdb282d6f006854ff5b1f26d3b407e0; lines 66-79; highlighted none.

```typescript

      const spans = (yield* Ref.get(deliveries)).flatMap((delivery) =>
        delivery.resourceSpans.flatMap((resource) =>
          resource.scopeSpans.flatMap((scope) => scope.spans)
        )
      );

      expect(spans.map((span) => span.name)).toContain("fixture.read");
      expect(
        spans.flatMap((span) =>
          span.attributes.map((attribute) => attribute.key)
        )
      ).toContain("phase");
    })
```

The fixture observes a serialized export request. It does not prove backend acceptance, storage, or a production exporter configuration.

| Evidence                                | What it establishes                                    |
| --------------------------------------- | ------------------------------------------------------ |
| Named `fn` in source                    | An instrumentation boundary exists                     |
| Decoded outbound request in the fixture | This composed exporter emits the expected trace fields |
| Collector receipt                       | That collector accepted the payload                    |
| A queried stored trace                  | The backend retained a trace you can retrieve          |

None of these receipts substitutes for the next delivery boundary.

## Keep other observers distinct

[Devtools](/systems/devtools) records capability calls and watched actor lifecycles.
[Request analytics](/systems/analytics) captures request events through its own sink.
Neither system substitutes for trace-export evidence.

Logs also have their own logger and export configuration.
Do not claim a log was delivered because it appeared in a local console.

## Common mistakes

- Assume naming a span installs a collector.
- Treat a test transport as a live backend.
- Put every argument into telemetry for convenience.
- Copy exporter settings from a different Effect version without checking its dependencies.

## Effect idiom and house rule

- Instrument meaningful operations and compose explicit observability Layers at the runtime edge.
- In rat-stack, keep private values scoped and report each observed delivery boundary honestly.
- In rat-stack, actor watching and capability recording remain their own devtools paths.

Next, [run Effect at the integration boundary](/lore/run-effect-at-the-boundary) and connect observability lifetime to its runtime owner.
Return to the [Effect reading order](/lore/effect-basics#read-next).

## Sources

1. [Effect contributors. 2026. Effect tracing operators. Effect 4.0.0.](https://github.com/Effect-TS/effect/blob/67ba4e46a11ccda0b6761578bfd22c04ae00167d/packages/effect/src/Effect.ts)
   Effect-TS. Named fn creates tracing spans; fnUntraced avoids that tracing boundary; withSpan and annotation operators add useful context. Accessed 2026-10-06.

2. [Effect contributors. 2026. Setting up tracing with Otlp modules.](https://github.com/Effect-TS/effect/blob/67ba4e46a11ccda0b6761578bfd22c04ae00167d/ai-docs/src/08_observability/20_otlp-tracing.ts)
   Effect-TS. Exporters require explicit serialization and HTTP transport Layers; creating spans and exporting them are distinct operations. Accessed 2026-10-06.

3. [Effect contributors. 2026. OtlpTracer.](https://github.com/Effect-TS/effect/blob/67ba4e46a11ccda0b6761578bfd22c04ae00167d/packages/effect/src/observability/OtlpTracer.ts)
   Effect-TS. The tracer batches completed sampled spans, sends them through an exporter, and supplies scoped flushing. This module is marked unstable in the 4.0.0 source. Accessed 2026-10-06.

4. [rat-stack contributors. 2026. FileInspector operation tracing.](https://github.com/joelhooks/rat-stack/blob/65e9465f38092e24486392f22c08f45d61230c20/packages/core/src/file-inspector.ts)
   rat-stack. FileInspector.inspect names a useful operation with Effect.fn rather than tracing every expression. Accessed 2026-10-06.

5. [rat-stack contributors. 2026. Trace export fixture.](https://github.com/joelhooks/rat-stack/blob/4019cffbcbdb282d6f006854ff5b1f26d3b407e0/packages/core/test/trace-export.test.ts)
   rat-stack. A supplied in-memory HTTP transport decodes the emitted OTLP JSON and checks the operation name and attribute key after scope shutdown. Accessed 2026-10-06.

6. [Langton, Kit. 2026. Observability. Effect Solutions.](https://github.com/kitlangton/effect-solutions/blob/09f82e6c5c928e7232cd32daf04d7c6a830b63f7/packages/website/docs/12-observability.md)
   Kit Langton. The draft frames OTLP composition. Its dependency and HTTP-import examples are not this page's version authority. Accessed 2026-10-06.

## Linked from

- Agent guide → An Effect stack so pure (aspirational) Kit Langton will blush. → [Read page](https://ratstack.sh/llms.txt)

- Change log → What changed in the files served here, newest first. → [Read page](https://ratstack.sh/log)

- Effect basics → An Effect describes work, expected failures, and required services before a runtime executes it. → [Read page](https://ratstack.sh/lore/effect-basics)

- Full agent guide → An Effect stack so pure (aspirational) Kit Langton will blush. → [Read page](https://ratstack.sh/llms-full.txt)

- HTTP success needs a status check and a decoder → Supply the client, check the response status, decode accepted data, and name failures at the adapter boundary. → [Read page](https://ratstack.sh/lore/http-responses-need-validation)

- Rat Stack lore | rat-stack → Short, source-grounded notes on the ideas and decisions behind rat-stack. → [Read page](https://ratstack.sh/lore)

- Source change history (log.md) → The generated change log as Markdown. → [Read page](https://ratstack.sh/log.md)
