Skip to content

Conversation

@MonishJuspay
Copy link
Contributor

@MonishJuspay MonishJuspay commented Dec 18, 2025

add support for OTel log emission

Summary by CodeRabbit

  • New Features

    • Integrated OpenTelemetry logging to export application logs to external monitoring platforms for enhanced observability and diagnostics.
    • Added configuration options to enable OTEL log collection and specify custom export endpoints.
  • Chores

    • Updated internal code documentation.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 18, 2025

Walkthrough

The pull request introduces OpenTelemetry (OTEL) logging integration by adding configuration flags, a new OTEL logger provider module, log sink forwarding logic, and initialization wiring in the voice agent system. Configuration endpoints and enablement flags are exposed for environment-based control.

Changes

Cohort / File(s) Summary
Configuration
app/core/config/static.py
Adds ENABLE_OTEL_LOGS boolean flag and OTEL_EXPORTER_OTLP_LOGS_ENDPOINT string configuration derived from environment variables
Logger Infrastructure
app/core/logger/__init__.py, app/core/logger/otel_logs.py
Introduces OTEL sink function to forward logs to OTEL provider, setter for runtime provider configuration, and new module with setup_otel_logging() function to initialize OTEL exporting with Resource, LoggerProvider, BatchLogRecordProcessor, and CraneLogFilter
Agent Integration
app/ai/voice/agents/automatic/__init__.py
Calls OTEL logging setup initialization within on_function_calls_started when ENABLE_OTEL_LOGS is true
Documentation
run.py
Updates step number comment from STEP 5 to STEP 6 for uvicorn startup section

Sequence Diagram

sequenceDiagram
    participant Agent as Voice Agent
    participant Config as Configuration
    participant OtelSetup as OTEL Setup Module
    participant LoggerInit as Logger Module
    participant Provider as OTEL Provider
    participant Crane as Crane Endpoint

    Agent->>Config: Read ENABLE_OTEL_LOGS & OTEL_EXPORTER_OTLP_LOGS_ENDPOINT
    Agent->>OtelSetup: Call setup_otel_logging(service_name)
    OtelSetup->>OtelSetup: Check _otel_logging_initialized flag
    OtelSetup->>Provider: Create Resource & LoggerProvider
    OtelSetup->>Provider: Add BatchLogRecordProcessor & OTLPLogExporter
    OtelSetup->>LoggerInit: Call set_otel_logger_provider(provider)
    LoggerInit->>LoggerInit: Store global _otel_logger_provider reference
    OtelSetup->>LoggerInit: Add otel_sink to Loguru
    LoggerInit->>Provider: Register sink for log forwarding
    Note over LoggerInit,Provider: Subsequent logs flow through<br/>Loguru → otel_sink → OTEL Provider
    LoggerInit->>Crane: Export logs via OTLP (batch)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Focus areas:
    • app/core/logger/otel_logs.py: New module with complex initialization logic, error handling, and idempotency checks; verify proper setup guards and exception handling
    • app/core/logger/__init__.py: New otel_sink function and set_otel_logger_provider global state management; ensure thread-safety and feedback loop prevention
    • Integration point in app/ai/voice/agents/automatic/__init__.py: Verify conditional initialization logic and no unintended side effects

Poem

🐰 With whiskers twitching, logs take flight,
Through OTEL tunnels, shining bright,
To Crane's endpoint, straight and true,
Now telemetry flows anew! 🎋✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'add support for OTel log emission' clearly and directly describes the main objective of the changeset, which adds OpenTelemetry logging integration across multiple modules.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
app/core/logger/__init__.py (2)

87-94: Minor: Avoid duplicate time.time() calls.

The timestamp and observed_timestamp are computed with separate time.time() calls, which could result in slightly different values. Capture the timestamp once for consistency and minor efficiency gains.

🔎 Apply this diff:
+    now_ns = int(time.time() * 1e9)
     log_record = APILogRecord(
-        timestamp=int(time.time() * 1e9),  # nanoseconds
-        observed_timestamp=int(time.time() * 1e9),
+        timestamp=now_ns,  # nanoseconds
+        observed_timestamp=now_ns,
         body=record["message"],
         severity_number=severity,
         attributes=attributes,
     )

81-82: Consider parameterizing the logger name.

The logger name "clairvoyance" is hardcoded here, while setup_otel_logging accepts a service_name parameter. If the service name changes, this would become inconsistent. Consider storing the service name globally alongside the provider, or using a constant.

app/core/logger/otel_logs.py (2)

79-84: Remove unused import.

LoggingHandler is imported but never used in this function.

🔎 Apply this diff:
         from opentelemetry._logs import set_logger_provider
         from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
-        from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
+        from opentelemetry.sdk._logs import LoggerProvider
         from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
         from opentelemetry.sdk.resources import Resource

109-126: Consider atomic initialization to avoid partial setup state.

If an exception occurs after set_otel_logger_provider(logger_provider) (line 112) but before _otel_logging_initialized = True (line 125), the provider is set globally but the sink is not added. On retry, the idempotency guard would prevent completion. Consider setting _otel_logging_initialized = True earlier or restructuring to ensure atomicity.

🔎 One approach - set the flag at the end of try block, after all operations succeed:

The current structure is actually correct since the flag is set after all operations complete successfully. However, if logger.add() fails, the provider is already set globally which could lead to inconsistent state. Consider wrapping the provider setup in a way that can be rolled back:

         # Set the global OTEL logger provider in __init__.py so otel_sink can use it
         from app.core.logger import set_otel_logger_provider

-        set_otel_logger_provider(logger_provider)
-
         # Add OTEL sink to Loguru
         from app.core.logger import otel_sink

         logger.add(
             otel_sink,
             level="DEBUG",  # Capture all levels, filtering happens in the sink
             enqueue=True,  # Thread-safe async logging
             backtrace=False,
             diagnose=False,
         )

+        # Set provider only after sink is successfully added
+        set_otel_logger_provider(logger_provider)
+
         _otel_logging_initialized = True
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 441eceb and 558e5ea.

📒 Files selected for processing (5)
  • app/ai/voice/agents/automatic/__init__.py (2 hunks)
  • app/core/config/static.py (1 hunks)
  • app/core/logger/__init__.py (1 hunks)
  • app/core/logger/otel_logs.py (1 hunks)
  • run.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
app/ai/voice/agents/automatic/__init__.py (1)
app/core/logger/otel_logs.py (1)
  • setup_otel_logging (31-131)
🪛 Ruff (0.14.8)
app/core/logger/otel_logs.py

130-130: Do not catch blind exception: Exception

(BLE001)

🔇 Additional comments (10)
app/ai/voice/agents/automatic/__init__.py (2)

96-96: LGTM!

Import is correctly placed alongside other analytics-related imports.


614-617: LGTM!

The OTEL logging initialization is appropriately placed after session context setup and before tracing setup. The setup_otel_logging function has an internal idempotency guard, so this is safe to call per session in pool mode.

app/core/config/static.py (1)

134-137: Verify the default endpoint is appropriate for production.

The default endpoint https://crane.beta.breeze.in points to a beta environment. When ENABLE_OTEL_LOGS is enabled without explicitly setting OTEL_EXPORTER_OTLP_LOGS_ENDPOINT, logs will be sent to the beta environment. Consider whether:

  1. The default should be an empty string (like other similar configs), requiring explicit configuration
  2. Or the beta default is intentional for gradual rollout
app/core/logger/__init__.py (3)

35-46: LGTM!

Good design with a global reference and early return when provider is not set. This ensures minimal overhead when OTEL logging is disabled.


48-59: LGTM!

The filtering logic correctly prevents feedback loops by skipping logs that originate from OpenTelemetry internals or reference the Crane endpoint.


97-104: LGTM!

Simple and clean setter function for the global provider.

app/core/logger/otel_logs.py (4)

1-28: LGTM!

Clear module docstring with usage example, and appropriate severity level mapping for Loguru-to-OTEL translation.


49-68: LGTM!

Good approach: the CraneLogFilter is applied regardless of whether OTEL logging is enabled, ensuring the standard library loggers are always filtered for Crane-related noise.


128-131: Catching broad Exception is acceptable here.

The static analysis flags the broad exception catch, but for a logging subsystem, it's appropriate to catch all exceptions to prevent logging initialization from crashing the application. The error is logged before returning, which is the correct behavior.


134-143: LGTM!

Simple getter function that correctly imports and returns the global provider reference.

asyncio.run(initialize_devcycle())

# STEP 5: Start uvicorn server (will spawn worker processes)
# STEP 6: Start uvicorn server (will spawn worker processes)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing STEP 5 in the sequence.

The comment was changed to "STEP 6" but there's no "STEP 5" in the file. The sequence goes from STEP 4 (line 56) directly to STEP 6. This may confuse future maintainers. Either add the missing STEP 5 comment (perhaps for OTEL initialization if that was intended) or use STEP 5 here.

🤖 Prompt for AI Agents
In run.py around line 60, the inline step numbering jumps from "STEP 4" to "STEP
6"; add or correct the comment so sequence is contiguous. Replace "STEP 6" with
"STEP 5" (or insert a new "STEP 5" comment before this line if there is an
intended OTEL or other initialization step to document) to restore proper
sequential ordering and avoid confusion for future maintainers.


import time

from app.core.logger.otel_logs import LEVEL_TO_SEVERITY
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to top

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants