Skip to content

Extensibility

When signals.logs is enabled, pi-otel registers a global OTel LoggerProvider. Other pi packages can route their own log records into the same OTLP stream — no dependency on pi-otel required.

Add @opentelemetry/api-logs as a dependency and call the global provider directly. The call is a safe no-op if pi-otel isn't loaded or logs are disabled.

ts
import { logs, SeverityNumber } from "@opentelemetry/api-logs";

const logger = logs.getLogger("my-package", "1.0.0");

logger.emit({
  severityNumber: SeverityNumber.DEBUG,
  body: "cache hit for tool bash",
  attributes: { "tool.name": "bash", "cache.key": "abc123" },
});

Records appear in Aspire Structured Logs under your own instrumentation scope (my-package), alongside pi-otel's lifecycle events.

pi-otel:log event bus

If you're already in a pi extension and don't want to add an OTel dependency, emit on pi's shared event bus:

ts
pi.events.emit("pi-otel:log", {
  eventName: "my-package.cache-hit",  // becomes the event.name attribute
  severity: "debug",                  // "debug" | "info" | "warn" | "error"
  body: "cache hit for tool bash",
  attributes: { "tool.name": "bash", "cache.key": "abc123" },
});

Records land under the pi-otel instrumentation scope. No-op if signals.logs is disabled.

Comparison

OTel APIpi-otel:log event bus
Dependency@opentelemetry/api-logsnone
Instrumentation scopeyour package namepi-otel
Works without pi contextyesno (needs pi.events)
No-op when logs disabledyesyes