# How OpenTelemetry Tracing Is Configured in Paperclip and Which Spans Are Auto-Instrumented

> Configure OpenTelemetry tracing in Paperclip for HTTP, PostgreSQL, and custom spans. Discover which spans are auto-instrumented to enhance observability.

- Repository: [Paperclip/paperclip](https://github.com/paperclipai/paperclip)
- Tags: internals
- Published: 2026-08-18

---

**Paperclip's tracing is optional and activates only when OpenTelemetry packages are installed and `OTEL_EXPORTER_OTLP_ENDPOINT` is set, with automatic instrumentation for HTTP, PostgreSQL, and custom startup spans.**

OpenTelemetry tracing in [Paperclip](https://github.com/paperclipai/paperclip) provides observability for the control-plane service without forcing a hard dependency on the OpenTelemetry SDK. This article explains the bootstrap mechanism, exporter configuration, and which operations generate spans based on the source code in `paperclipai/paperclip`.

## Bootstrap Architecture in instrumentation.ts

The tracing lifecycle begins in **[[`server/src/instrumentation.ts`](https://github.com/paperclipai/paperclip/blob/main/server/src/instrumentation.ts)](https://github.com/paperclipai/paperclip/blob/master/server/src/instrumentation.ts)**. At startup, Paperclip attempts to `require("@opentelemetry/api")` (lines 100–112). If the package is missing, the server falls back to a **no-op tracer** and logs a warning—allowing the application to run without telemetry. When available, the full SDK loads via `@opentelemetry/sdk-node` alongside **`@opentelemetry/auto-instrumentations-node`** (line 368).

This conditional loading pattern ensures that:
- Development environments without OTLP collectors start without errors
- Production deployments opt into tracing via environment variables
- Plugin authors can import trace types safely even when OTEL is absent

## OTLP Exporter Selection

Paperclip supports three exporter protocols based on the **`OTEL_EXPORTER_OTLP_ENDPOINT`** value (lines 13–15, 316–322):

| Protocol | Package Used | Use Case |
|----------|-------------|----------|
| `grpc` (default) | `@opentelemetry/exporter-trace-otlp-grpc` | High-throughput, gRPC-native collectors |
| `http/protobuf` | `@opentelemetry/exporter-trace-otlp-proto` | Proxied or firewall-restricted environments |
| `http/json` | `@opentelemetry/exporter-trace-otlp-http` | Debugging, human-readable payloads |

The SDK attaches **resource attributes** using `@opentelemetry/resources` and `@opentelemetry/semantic-conventions`, including service name, version, and other standard identifiers (lines 321–326).

## Auto-Instrumented Spans and Disabled Modules

The `auto-instrumentations-node` package provides broad coverage, but Paperclip **selectively disables heavy modules** that don't benefit the control-plane service (inside [`instrumentation.ts`](https://github.com/paperclipai/paperclip/blob/main/instrumentation.ts)):

```ts
{
  "@opentelemetry/instrumentation-fs": { enabled: false },
  "@opentelemetry/instrumentation-dns": { enabled: false },
  "@opentelemetry/instrumentation-net": { enabled: false },
}

```

With these disabled, the following **spans are auto-instrumented**:

- **HTTP server** – Express request handling for incoming API calls
- **HTTP client** – Outbound `http`/`https` requests made by the server
- **PostgreSQL** – Database queries through the `pg` driver (Paperclip's primary data layer)
- **Core Node APIs** – Timers, async hooks, and other runtime operations in the default node instrumentation set

This curated set focuses telemetry on request flow and database performance while reducing noise from low-level network operations.

## Custom Startup Timing Spans

Beyond auto-instrumentation, Paperclip records **internal initialization phases** in **[[`packages/adapter-utils/src/acpx-engine/startup-timing.ts`](https://github.com/paperclipai/paperclip/blob/main/packages/adapter-utils/src/acpx-engine/startup-timing.ts)](https://github.com/paperclipai/paperclip/blob/master/packages/adapter-utils/src/acpx-engine/startup-timing.ts)**. This helper creates spans for:

- Database connection establishment
- Plugin loading and initialization
- Other boot-time operations

The wrapper is deliberately typed to a **minimal Span subset** from `@opentelemetry/api`, ensuring it compiles and runs even when the full OTEL packages are absent. This design lets plugin code remain telemetry-aware without requiring the SDK as a dependency.

## Environment Configuration and Startup

Enable tracing by setting these environment variables before starting the server:

```bash
export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4317"
export OTEL_SERVICE_NAME="paperclip"

# start the server – instrumentation auto-boots

pnpm dev

```

Without `OTEL_EXPORTER_OTLP_ENDPOINT`, the instrumentation module skips SDK initialization entirely and uses the no-op tracer.

## Manual Span Creation

Application code can create custom spans using the OpenTelemetry API:

```ts
import { trace, SpanStatusCode } from "@opentelemetry/api";

export async function doWork() {
  const tracer = trace.getTracer("paperclip-work");
  const span = tracer.startSpan("doWork");

  try {
    // business logic here …
  } catch (err) {
    span.setStatus({ code: SpanStatusCode.ERROR, message: String(err) });
    throw err;
  } finally {
    span.end();
  }
}

```

The [**`@opentelemetry/api`**](https://github.com/open-telemetry/opentelemetry-js/tree/main/api) package provides these primitives; they operate as no-ops when the SDK isn't loaded.

## Reading Trace Context for Startup Measurements

The startup timing helper demonstrates context-aware span creation:

```ts
import { context, trace } from "@opentelemetry/api";

export function measureStartupStep(name: string, fn: () => Promise<void>) {
  const tracer = trace.getTracer("paperclip-startup");
  const span = tracer.startSpan(name, undefined, context.active());
  return fn()
    .catch((e) => {
      span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) });
      throw e;
    })
    .finally(() => span.end());
}

```

This pattern preserves trace continuity across asynchronous initialization steps.

## Test Coverage

The tracing configuration is validated in **[[`server/src/__tests__/instrumentation.test.ts`](https://github.com/paperclipai/paperclip/blob/main/server/src/__tests__/instrumentation.test.ts)](https://github.com/paperclipai/paperclip/blob/master/server/src/__tests__/instrumentation.test.ts)**, which confirms:

- SDK loads without errors when dependencies are present
- Exporter selection resolves correctly for each protocol
- Spans can be created, modified, and ended through the full lifecycle

These tests ensure that tracing remains functional across dependency updates and configuration changes.

## Summary

- **Optional activation** – OpenTelemetry tracing requires both package installation and `OTEL_EXPORTER_OTLP_ENDPOINT` to be set
- **Central bootstrap** – [`server/src/instrumentation.ts`](https://github.com/paperclipai/paperclip/blob/main/server/src/instrumentation.ts) handles SDK initialization, protocol selection, and auto-instrumentation tuning
- **Curated auto-instrumentation** – HTTP/Express, PostgreSQL, and core Node APIs are instrumented; fs, DNS, and net are disabled
- **Custom startup spans** – [`packages/adapter-utils/src/acpx-engine/startup-timing.ts`](https://github.com/paperclipai/paperclip/blob/main/packages/adapter-utils/src/acpx-engine/startup-timing.ts) provides boot-phase telemetry with minimal API coupling
- **Protocol flexibility** – OTLP/gRPC, HTTP/Protobuf, and HTTP/JSON exporters are supported via environment configuration
- **Graceful degradation** – No-op tracers allow the application to run without OpenTelemetry installed or configured

## Frequently Asked Questions

### Does Paperclip require OpenTelemetry to run?

No. Paperclip uses a **no-op tracer fallback** when `@opentelemetry/api` is missing or `OTEL_EXPORTER_OTLP_ENDPOINT` is unset. The server logs a warning and continues normal operation. This design is verified in [`server/src/__tests__/instrumentation.test.ts`](https://github.com/paperclipai/paperclip/blob/main/server/src/__tests__/instrumentation.test.ts).

### Which database operations generate spans?

All queries through the **`pg` PostgreSQL driver** generate spans via auto-instrumentation. This covers connection acquisition, query execution, and result processing. The instrumentation is provided by `@opentelemetry/instrumentation-pg` through the `auto-instrumentations-node` package.

### Can I use a different collector protocol than gRPC?

Yes. Set `OTEL_EXPORTER_OTLP_ENDPOINT` to a URL with the appropriate scheme, or configure the protocol explicitly. Paperclip selects `http/protobuf` or `http/json` exporters based on environment detection in [`instrumentation.ts`](https://github.com/paperclipai/paperclip/blob/main/instrumentation.ts) lines 316–322. The default `grpc` protocol uses port 4317; HTTP protocols typically use 4318.

### How do plugins participate in tracing without depending on the full SDK?

Plugins import a **minimal Span interface** from [`packages/plugins/sdk/src/types.ts`](https://github.com/paperclipai/paperclip/blob/main/packages/plugins/sdk/src/types.ts) rather than `@opentelemetry/api`. The startup timing helper in [`packages/adapter-utils/src/acpx-engine/startup-timing.ts`](https://github.com/paperclipai/paperclip/blob/main/packages/adapter-utils/src/acpx-engine/startup-timing.ts) uses this pattern to create spans that work regardless of whether the OpenTelemetry SDK is present at runtime.