How OpenTelemetry Tracing Is Configured in Paperclip and Which Spans Are Auto-Instrumented
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 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/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):
{
"@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/httpsrequests made by the server - PostgreSQL – Database queries through the
pgdriver (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/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:
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:
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 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:
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/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_ENDPOINTto be set - Central bootstrap –
server/src/instrumentation.tshandles 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.tsprovides 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.
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 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 rather than @opentelemetry/api. The startup timing helper in 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →