feat(agents-core): structured logging with OTel log export#3073
feat(agents-core): structured logging with OTel log export#3073amikofalvy wants to merge 5 commits intomainfrom
Conversation
Replace always-on pino-pretty with environment-aware configuration: - Production/Vercel: JSON to stdout + OTel LogRecords via multistream - Development: pino-pretty (unchanged) Architecture: - OTelLogStream (main thread Writable) bridges pino JSON to OTel LogRecords - Automatic trace-log correlation via context.active() (no mixin needed) - Provider-agnostic: reads OTEL_EXPORTER_OTLP_LOGS_ENDPOINT for any OTLP backend - Graceful degradation: no-op if LoggerProvider not registered or SDK not installed - No worker threads: avoids flush-on-freeze and bundler issues in serverless Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
🦋 Changeset detectedLatest commit: 5bff3ec The changes in this PR will be included in the next version bump. This PR includes changesets to release 10 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
TL;DR — Adds structured JSON logging in production/Vercel environments and bridges pino log output to the OpenTelemetry Logs SDK via OTLP HTTP, so application logs are exported alongside traces to an OTel-compatible collector. Key changes
Summary | 12 files | 5 commits | base: Pino-to-OTel log bridge via
|
| Function | Behavior |
|---|---|
setupOTelLogProvider() |
Lazy-imports SDK, creates BatchLogRecordProcessor + OTLPLogExporter, registers global provider |
flushOTelLogs() |
Duck-types the global provider for forceFlush() — safe no-op if SDK absent |
shutdownOTelLogProvider() |
Same pattern for shutdown() — wired into shutdownOpenTelemetry() |
shutdownOpenTelemetry() |
Shuts down both the trace SDK and log provider in parallel |
| SIGTERM handler | Calls shutdownOpenTelemetry() to flush buffered telemetry before Vercel's ~5s SIGKILL |
otel-log-provider.ts · instrumentation.ts · env.ts · tsdown.config.ts
Claude Opus | 𝕏
There was a problem hiding this comment.
PR Review Summary
(5) Total Issues | Risk: High
🔴❗ Critical (1) ❗🔴
Inline Comments:
- 🔴 Critical:
otel-log-stream.ts:32OTel logger obtained before LoggerProvider is registered — all log exports silently fail
🟠⚠️ Major (4) 🟠⚠️
🟠 1) scope Async SDK startup not awaited
Issue: startOpenTelemetrySDK() was changed to async function (returns Promise<void>) but is called without await at line 5 of index.ts.
Why: If the OTel SDK start or log provider setup fails, the error will be an unhandled promise rejection rather than a startup failure. More importantly, subsequent module imports and logger instantiations will race against the async setupOTelLogProvider() call, exacerbating the initialization order bug.
Fix: This is partially addressed by fixing the lazy logger initialization in otel-log-stream.ts. However, consider either:
- Using top-level await:
await startOpenTelemetrySDK(); - Or handling the promise explicitly with a catch handler
Refs:
Inline Comments:
- 🟠 Major:
otel-log-stream.ts:50-52Catch-all swallows OTel emission errors, not just JSON parse errors - 🟠 Major:
instrumentation.ts:112shutdownOTelLogProvider()is never called on process shutdown - 🟠 Major:
.env.example:69Missing fromenv.tsschema — CI will fail
🟡 Minor (0) 🟡
No minor issues.
💭 Consider (3) 💭
💭 1) otel-log-provider.ts:11 BatchLogRecordProcessor uses default configuration
Issue: The trace SDK uses env-configured OTEL_BSP_SCHEDULE_DELAY and OTEL_BSP_MAX_EXPORT_BATCH_SIZE, but the log processor uses defaults (30s delay, 512 batch size).
Why: Inconsistent batching behavior between traces and logs under high volume.
Fix: Consider adding similar env var support or document that defaults are intentional.
💭 2) otel-log-provider.ts:19 Debug-level logging for setup failures
Issue: Using debug level for OTel setup failures means operators may not notice when log export is unexpectedly disabled despite configuring OTEL_EXPORTER_OTLP_LOGS_ENDPOINT.
Fix: Consider info level when the env var is set but setup fails (indicating user intent).
💭 3) otel-log-provider.ts:24 flushOTelLogs has no timeout protection
Issue: If the OTLP endpoint is slow/unresponsive, forceFlush() could hang indefinitely.
Fix: Consider wrapping in Promise.race with a timeout for serverless environments.
🚫 REQUEST CHANGES
Summary: The core logger initialization order issue (Critical #1) means OTel log export will silently fail — the OTelLogStream captures a no-op logger at module load time before the provider is registered. This needs to be fixed by lazily initializing the logger. Additionally, the missing env schema entry will cause CI failures. The shutdown handling gap and error swallowing in the catch block should also be addressed before merge.
Discarded (6)
| Location | Issue | Reason Discarded |
|---|---|---|
package.json:185 |
Version mismatch between api-logs (0.203.0) and other OTel packages | OTel uses semver, caret ranges handle this appropriately. Lockfile deduplication is working. |
tsdown.config.ts:14 |
api-logs not in externals list unlike sdk-logs | Intentional — api-logs is a regular dep meant to be bundled, SDK packages are optional/dynamic. |
changeset |
Message could be more specific | Valid changeset, message is adequate for a patch release. |
logger.ts:115 |
pino-pretty fallback message could be more actionable | Pre-existing code, fallback behavior is sound. |
logger.ts |
OTelLogStream instances not cleaned up in recreateInstance | Edge case — recreateInstance is rarely called, GC handles cleanup. |
otel-log-stream.ts:61 |
Attributes forwarded without cardinality controls | Log backends handle high cardinality better than metrics. Pino redact config handles secrets. |
Reviewers (4)
| Reviewer | Returned | Main Findings | Consider | While You're Here | Inline Comments | Pending Recs | Discarded |
|---|---|---|---|---|---|---|---|
pr-review-standards |
2 | 1 | 0 | 0 | 1 | 0 | 0 |
pr-review-sre |
7 | 1 | 3 | 0 | 2 | 0 | 1 |
pr-review-errors |
3 | 0 | 1 | 0 | 1 | 0 | 1 |
pr-review-devops |
5 | 0 | 0 | 0 | 1 | 0 | 4 |
| Total | 17 | 2 | 4 | 0 | 5 | 0 | 6 |
| } | ||
|
|
||
| export class OTelLogStream extends Writable { | ||
| private otelLogger = logs.getLogger('pino-bridge'); |
There was a problem hiding this comment.
🔴 CRITICAL: OTel logger obtained before LoggerProvider is registered
Issue: logs.getLogger('pino-bridge') is called at class construction time, but OTelLogStream instances are created in logger.ts constructor (line 104) which runs at module load time — before setupOTelLogProvider() is called in instrumentation.ts.
Why: The OTel API returns a no-op logger when no provider is registered. This no-op logger gets cached in otelLogger, causing all subsequent emit() calls to silently do nothing. This defeats the entire purpose of the feature.
Fix: Lazily initialize the logger on first use:
| private otelLogger = logs.getLogger('pino-bridge'); | |
| private get otelLogger() { | |
| return logs.getLogger('pino-bridge'); | |
| } |
Or alternatively, call logs.getLogger() directly in emitLogRecord().
Refs:
- OTel API logs.getLogger() docs — returns no-op when no provider registered
- instrumentation.ts:112 — where provider is set up (after SDK starts)
| } catch { | ||
| // Silently skip unparseable lines | ||
| } |
There was a problem hiding this comment.
🟠 MAJOR: Catch-all swallows OTel emission errors, not just JSON parse errors
Issue: The catch block intends to skip unparseable JSON lines, but it also silently suppresses errors from emitLogRecord() and otelLogger.emit() — which may fail for reasons unrelated to JSON parsing (OTel SDK issues, malformed attributes, provider misconfiguration).
Why: If OTel emission fails systematically, there's zero feedback that logs aren't reaching the backend. Debugging why logs aren't appearing becomes extremely difficult.
Fix: Separate JSON parsing errors (expected, safe to skip) from emission errors (should surface at least once):
| } catch { | |
| // Silently skip unparseable lines | |
| } | |
| } catch (parseError) { | |
| // Expected: partial lines, non-JSON preamble - silently skip | |
| } |
And wrap emitLogRecord() call separately with rate-limited error logging (e.g., log once per N failures to avoid storms).
| throw error; | ||
| } | ||
|
|
||
| await setupOTelLogProvider(); |
There was a problem hiding this comment.
🟠 MAJOR: shutdownOTelLogProvider() is never called on process shutdown
Issue: The otel-log-provider.ts module exports shutdownOTelLogProvider() but it's not wired into any shutdown hook.
Why: On SIGTERM/graceful shutdown, logs buffered in BatchLogRecordProcessor will be lost if shutdown() is not called. The trace SDK handles this via NodeSDK.shutdown(), but the log provider setup is separate.
Fix: Add shutdownOTelLogProvider() to the application shutdown sequence. Consider creating a unified shutdown function:
export async function shutdownOpenTelemetry(): Promise<void> {
await Promise.all([
defaultSDK.shutdown(),
shutdownOTelLogProvider(),
]);
}Refs:
- otel-log-provider.ts:37 — shutdown function that should be called
| SIGNOZ_API_KEY= | ||
| OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:14318/v1/traces | ||
| # OTel logs endpoint — points to SigNoz collector (port 4318) or standalone collector (port 14318) | ||
| OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=http://localhost:4318/v1/logs |
There was a problem hiding this comment.
🟠 MAJOR: Missing from env.ts schema — CI will fail
Issue: OTEL_EXPORTER_OTLP_LOGS_ENDPOINT is added to .env.example but not declared in agents-api/src/env.ts.
Why: The check-env-descriptions.mjs CI script enforces parity between .env.example and the Zod schema. This will cause CI failures.
Fix: Add to agents-api/src/env.ts alongside the other OTel config (around line 175):
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT: z
.string()
.optional()
.describe('OTLP endpoint for exporting logs to any OTLP-compatible backend (e.g., SigNoz, Grafana)'),Refs:
- env.ts:165-175 — existing OTel config section
- adding-env-variables skill
There was a problem hiding this comment.
Solid approach — the pino multistream + OTel bridge pattern is clean and the proxy-based lazy initialization is correct. Two actionable issues: a bug in the stream's error handling that silently drops valid log lines, and an unhandled async concern in the callers of startOpenTelemetrySDK. The lockfile also has unrelated react-dom resolution drift that should be cleaned up.
Claude Opus | 𝕏
| try { | ||
| const str = typeof chunk === 'string' ? chunk : chunk.toString(); | ||
| for (const line of str.split('\n')) { | ||
| if (!line.trim()) continue; | ||
| const parsed = JSON.parse(line); | ||
| this.emitLogRecord(parsed); | ||
| } | ||
| } catch { | ||
| // Silently skip unparseable lines | ||
| } |
There was a problem hiding this comment.
Bug: one malformed line silently drops all remaining lines in the chunk.
The try/catch wraps the entire for loop. If any single line fails JSON.parse, the exception exits the loop and all subsequent valid lines in that chunk are never emitted to OTel. Move the try/catch inside the loop so each line is handled independently:
| try { | |
| const str = typeof chunk === 'string' ? chunk : chunk.toString(); | |
| for (const line of str.split('\n')) { | |
| if (!line.trim()) continue; | |
| const parsed = JSON.parse(line); | |
| this.emitLogRecord(parsed); | |
| } | |
| } catch { | |
| // Silently skip unparseable lines | |
| } | |
| try { | |
| const str = typeof chunk === 'string' ? chunk : chunk.toString(); | |
| for (const line of str.split('\n')) { | |
| if (!line.trim()) continue; | |
| try { | |
| const parsed = JSON.parse(line); | |
| this.emitLogRecord(parsed); | |
| } catch { | |
| // Skip unparseable lines | |
| } | |
| } | |
| } catch { | |
| // Silently skip chunk-level errors (e.g. toString failure) | |
| } |
| throw error; | ||
| } | ||
|
|
||
| await setupOTelLogProvider(); |
There was a problem hiding this comment.
startOpenTelemetrySDK() is now async but neither caller awaits it:
agents-api/src/index.ts:5:startOpenTelemetrySDK();(fire-and-forget)create-agents-template/.../instrumentation.ts:20:startOpenTelemetrySDK();(fire-and-forget)
Since setupOTelLogProvider() has its own try/catch this won't cause unhandled rejections, but the log provider setup is now a race — early log records emitted before the provider is registered are silently dropped by the NOOP_LOGGER proxy.
This is likely acceptable for the OTel bridge (logs during startup are low-value), but document the intentional fire-and-forget behavior or convert the callers to await.
There was a problem hiding this comment.
This diff includes react-dom resolution changes (19.2.4 → 19.2.0 in several better-auth, next, nuqs, react-router snapshots) that have no relation to the OTel dependencies being added. This typically happens when the lockfile is regenerated from scratch rather than layered on top of main.
Per repo conventions, rebase the lockfile cleanly:
git checkout main -- pnpm-lock.yaml
pnpm installThis preserves existing resolutions and only adds the new OTel entries.
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
Warning Review the following alerts detected in dependencies. According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.
|
Ito Test Report ✅13 test cases ran. 13 passed. The unified local QA run passed all 13 of 13 test cases with zero failures, showing no code-supported defect signals across API liveness/readiness, auth boundaries, and UI navigation flows. Most importantly, /health and /ready remained contract-stable even under repeated and concurrent load (including 60 burst requests), protected endpoints correctly enforced controlled 401/4xx behavior while valid bypass auth returned expected data, deep-link/refresh/history and mobile (390x844) navigation stayed authenticated without 5xx or error boundaries, and the prior ADV-4 concern was confirmed as a test-expectation mismatch rather than an application defect. ✅ Passed (13)Commit: Tell us how we did: Give Ito Feedback |
Reset pnpm-lock.yaml from main and reinstalled to properly layer the new @opentelemetry dependencies. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
There was a problem hiding this comment.
PR Review Summary
(5) Total Issues | Risk: High
🔴❗ Critical (1) ❗🔴
No new critical issues in delta.
🟠⚠️ Major (3) 🟠⚠️
No new major issues in delta.
🟡 Minor (0) 🟡
No minor issues.
💭 Consider (0) 💭
No consider items.
🕐 Pending Recommendations (5)
Issues raised in prior review that remain unaddressed:
- 🔴
otel-log-stream.ts:32OTel logger obtained before LoggerProvider is registered — all log exports silently fail - 🟠
scopeAsync SDK startup not awaited inindex.ts:5 - 🟠
otel-log-stream.ts:50-52Catch-all swallows OTel emission errors, not just JSON parse errors - 🟠
instrumentation.ts:112shutdownOTelLogProvider()is never called on process shutdown - 🟠
.env.example:69Missing fromenv.tsschema — CI will fail
✅ Delta Assessment
Commit 4941ccf37: Regenerated lockfile from main to fix frozen-lockfile CI failures.
This addresses the lockfile drift concern raised by Pullfrog in the prior review. The lockfile now properly layers the new OTel dependencies on top of main's existing resolutions. ✓
🚫 REQUEST CHANGES
Summary: The lockfile regeneration is correct and addresses the prior concern about resolution drift. However, all 5 critical/major issues from the previous review remain unaddressed. The most important is the lazy logger initialization bug (otel-log-stream.ts:32) — without this fix, OTel log export will silently fail because the logger is obtained before the provider is registered.
Discarded (0)
No discarded items.
Reviewers (1)
| Reviewer | Returned | Main Findings | Consider | While You're Here | Inline Comments | Pending Recs | Discarded |
|---|---|---|---|---|---|---|---|
orchestrator |
0 | 0 | 0 | 0 | 0 | 5 | 0 |
| Total | 0 | 0 | 0 | 0 | 0 | 5 | 0 |
Note: Delta-only review — no sub-reviewers dispatched. Prior issues carried forward as pending recommendations.
Preview URLsUse these stable preview aliases for testing this PR:
These point to the same Vercel preview deployment as the bot comment, but they stay stable and easier to find. Raw Vercel deployment URLs
|
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
There was a problem hiding this comment.
PR Review Summary
(5) Total Issues | Risk: High
🔴❗ Critical (0) ❗🔴
No new critical issues in delta.
🟠⚠️ Major (0) 🟠⚠️
No new major issues in delta.
🟡 Minor (0) 🟡
No minor issues.
💭 Consider (0) 💭
No consider items.
🕐 Pending Recommendations (5)
Issues raised in prior reviews that remain unaddressed:
- 🔴
otel-log-stream.ts:32OTel logger obtained before LoggerProvider is registered — all log exports silently fail - 🟠
scopeAsync SDK startup not awaited inindex.ts:5 - 🟠
otel-log-stream.ts:50-52Catch-all swallows OTel emission errors, not just JSON parse errors - 🟠
instrumentation.ts:112shutdownOTelLogProvider()is never called on process shutdown - 🟠
.env.example:69Missing fromenv.tsschema — CI will fail
✅ Delta Assessment
Commit 1d9c5158f: Retrigger CI after auto-format.
This commit contains no code changes — it's a CI retrigger. The 5 pending issues from the initial review remain unaddressed.
🚫 REQUEST CHANGES
Summary: No code changes in this delta. The 5 critical/major issues from the initial review remain unaddressed. The most important is the lazy logger initialization bug (otel-log-stream.ts:32) — without this fix, OTel log export will silently fail because the logger is obtained before the provider is registered.
Discarded (0)
No discarded items.
Reviewers (1)
| Reviewer | Returned | Main Findings | Consider | While You're Here | Inline Comments | Pending Recs | Discarded |
|---|---|---|---|---|---|---|---|
orchestrator |
0 | 0 | 0 | 0 | 0 | 5 | 0 |
| Total | 0 | 0 | 0 | 0 | 0 | 5 | 0 |
Note: Delta-only review — no sub-reviewers dispatched (empty diff). Prior issues carried forward as pending recommendations.
Ito Test Report ✅18 test cases ran. 18 passed. All 16 executed test cases passed with zero failures, showing strong stability across core API routes, chat streaming, traces workflows, and security hardening checks in the local non-production setup. The most important findings were reliable health/readiness behavior, correct unauthorized/forged-access blocking and safe 400 validation for malformed or out-of-order traces inputs, resilient streaming and navigation under rapid actions/refresh/concurrency/session churn, and graceful traces UI error handling when SigNoz was unavailable, with the caveat that multiple scenarios used local auth/token bypasses and the A2A repeat test used a synthetic fallback stream path. ✅ Passed (18)Commit: Tell us how we did: Give Ito Feedback |
…, env schema - Fix critical: lazily resolve OTel logger via getter to avoid caching no-op before LoggerProvider is registered - Fix error handling: separate JSON parse errors from OTel emission errors, rate-limit emission error logging, per-line try/catch to avoid dropping valid lines after one parse failure - Wire SIGTERM shutdown: call shutdownOTelLogProvider() on process SIGTERM (Vercel Fluid Compute sends SIGTERM ~5s before SIGKILL) - Add OTEL_EXPORTER_OTLP_LOGS_ENDPOINT to env.ts schema - Regenerate lockfile cleanly from main Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
There was a problem hiding this comment.
PR Review Summary
(0) Total Issues | Risk: Low
✅ Delta Assessment
Commit 5bff3ec35: fix: address PR feedback — lazy OTel logger, error handling, shutdown, env schema
This commit addresses all 5 issues raised in prior reviews:
| Prior Issue | Fix Applied |
|---|---|
| 🔴 OTel logger obtained before provider registration | ✅ Changed to private get otelLogger() getter — now lazily retrieves on each emit, correctly picking up the registered provider |
| 🟠 Catch-all swallowed emission errors | ✅ Moved try/catch inside the loop for JSON parsing, added separate try/catch around emit() with rate-limited error logging (emitErrorLogged flag) |
| 🟠 shutdownOTelLogProvider() never called | ✅ Added shutdownOpenTelemetry() function and wired it to process.on('SIGTERM', ...) — gracefully drains buffered logs on Vercel Fluid Compute |
| 🟠 Missing env.ts schema entry | ✅ Added OTEL_EXPORTER_OTLP_LOGS_ENDPOINT to env.ts schema with description |
| 🟠 Async SDK startup race condition | ✅ Mitigated by lazy getter — logs emitted before provider is ready now correctly use no-op, then pick up real provider once registered |
Implementation Quality
The fixes are well-implemented:
-
Lazy getter pattern (
otel-log-stream.ts:34-36) — Clean solution that avoids the initialization order bug without requiring explicit lifecycle management. Eachemit()call retrieves the current global logger provider, which seamlessly transitions from no-op to real provider. -
Error handling (
otel-log-stream.ts:78-85) — Rate-limited logging prevents error storms while ensuring operators see at least one diagnostic message if OTel emission fails systematically. -
Shutdown handler (
instrumentation.ts:127-130) — Correct pattern for Vercel serverless: registersSIGTERMhandler that callsshutdownOpenTelemetry()with fire-and-forget semantics (Vercel sends SIGKILL ~5s later regardless). -
Parallel flush/shutdown (
instrumentation.ts:117,124) — UsesPromise.all()for both flush and shutdown operations to minimize latency during graceful shutdown.
Consider (Non-blocking)
The following items from the original review remain as potential future improvements but are not blocking:
- BatchLogRecordProcessor default config — Uses OTel SDK defaults (30s delay, 512 batch size) rather than the env-configured values used for traces. Document as intentional or add env var support in a follow-up.
- Debug-level logging for setup failures —
logger.debug()for OTel SDK package unavailability may be too quiet whenOTEL_EXPORTER_OTLP_LOGS_ENDPOINTis configured but packages aren't installed.
✅ APPROVE
Summary: All prior review feedback has been addressed with well-implemented fixes. The lazy logger getter pattern correctly solves the initialization order bug, error handling is appropriately rate-limited, and the SIGTERM shutdown hook ensures graceful log drain in serverless environments. Ready to merge! 🎉
Reviewers (1)
| Reviewer | Returned | Main Findings | Consider | While You're Here | Inline Comments | Pending Recs | Discarded |
|---|---|---|---|---|---|---|---|
orchestrator |
0 | 0 | 0 | 0 | 0 | 0 | 0 |
| Total | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Note: Delta-only review — prior issues resolved, no new findings.
Ito Test Report ✅13 test cases ran. 13 passed. The unified local verification run passed all 13/13 test cases with zero failures, showing stable API/UI behavior across startup, navigation, interruption, mobile viewport, and tenant-route access while /health consistently returned 204, /ready stayed contract-valid (200 with expected fields), and no backend 5xx bursts were observed. Most importantly, auth and resilience hardening checks behaved correctly: dev-session happy path set cookies and enabled /default/projects, invalid or malformed Authorization/bypass-secret attempts were safely rejected (401/non-2xx), concurrent multi-context flooding preserved auth isolation with no leakage, tenant SigNoz health remained controlled and non-5xx, deep-link stale-session handling redirected to login when the logged-out marker was present (while dev auto-login without that marker remained intentional), and async OTel/logging startup did not block initial flows. ✅ Passed (13)Commit: Tell us how we did: Give Ito Feedback |
|
This pull request has been automatically marked as stale because it has not had recent activity. If this PR is still relevant:
Thank you for your contributions! |
Summary
pino.multistream(). Development uses pino-pretty (unchanged).OTelLogStream(Writable stream) parses pino JSON and emits OTel LogRecords with automatic trace-log correlation viacontext.active()— no worker threads, no pino mixin needed.OTEL_EXPORTER_OTLP_LOGS_ENDPOINTfor any OTLP-compatible backend (SigNoz, Grafana, Datadog, etc.). Gracefully degrades to no-op when LoggerProvider is not registered or OTel SDK packages are not installed.Why
What changed
packages/agents-core/src/utils/logger.tspackages/agents-core/src/utils/otel-log-stream.tspackages/agents-core/src/utils/otel-log-provider.tsagents-api/src/instrumentation.tssetupOTelLogProvider()after SDK start;flushBatchProcessor()now also flushes OTel logspackages/agents-core/package.json@opentelemetry/api-logs(regular dep),sdk-logs+exporter-logs-otlp-http(optional deps)packages/agents-core/tsdown.config.tsexternalto prevent bundler tracing.env.exampleOTEL_EXPORTER_OTLP_LOGS_ENDPOINTTest plan
pnpm --filter @inkeep/agents-core typecheckpassespnpm --filter @inkeep/agents-core test— 140 files, 2112 tests passpnpm --filter @inkeep/agents-core buildsucceedslocalhost:4318/v1/logsOTEL_EXPORTER_OTLP_LOGS_ENDPOINTset, verify logs appear in SigNoz Logs tab🤖 Generated with Claude Code