Skip to content

feat: otel and signoz implementation#1297

Merged
tipusinghaw merged 5 commits intodevelopfrom
feat/otel-and-signoz-integration-platform
Jun 27, 2025
Merged

feat: otel and signoz implementation#1297
tipusinghaw merged 5 commits intodevelopfrom
feat/otel-and-signoz-integration-platform

Conversation

@tipusinghaw
Copy link
Copy Markdown
Contributor

@tipusinghaw tipusinghaw commented Jun 24, 2025

What

  • OTel and signoz integration

Summary by CodeRabbit

  • New Features

    • Introduced OpenTelemetry-based tracing and logging for enhanced observability across the API gateway.
    • Logging now emits structured telemetry data with enriched context and correlation IDs.
    • Added a custom logger service that integrates with OpenTelemetry spans.
  • Improvements

    • Refined logging behavior and context extraction for more consistent and informative logs.
    • Enhanced profiling capabilities with a new method for starting log profiles.
  • Dependencies

    • Added OpenTelemetry libraries and instrumentation packages to support tracing and logging features.

@tipusinghaw tipusinghaw self-assigned this Jun 24, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Jun 24, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

OpenTelemetry tracing and logging are integrated into the API gateway and logger service. The main application initializes the OpenTelemetry SDK, and the logger service emits logs to OpenTelemetry with enriched context. Additional dependencies for OpenTelemetry are added, and minor refactoring and formatting changes are applied across several files.

Changes

File(s) Change Summary
apps/api-gateway/src/main.ts Initializes OpenTelemetry SDK at bootstrap, wraps startup in try-catch, refactors middleware to arrow function, updates imports.
apps/api-gateway/src/tracer.ts Introduces OpenTelemetry tracing/logging setup, exports SDK, logger, and provider, configures graceful shutdown.
libs/logger/src/logger.service.ts LoggerService emits logs to OpenTelemetry, adds emitToOtel, updates all log methods, adds profiling support.
libs/logger/src/logging.interceptor.ts Refactors interceptor to move logging/context extraction, adds info log, prepares for user context extraction.
libs/logger/src/nestjsLoggerServiceAdapter.ts Refactors method returns, adds MyLoggerService with OpenTelemetry span logging, updates imports.
package.json Adds OpenTelemetry dependencies, reorders two existing dependencies.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant API_Gateway (main.ts)
    participant OpenTelemetry SDK (tracer.ts)
    participant LoggerService
    participant OTLP Exporter

    Client->>API_Gateway (main.ts): Sends request
    API_Gateway (main.ts)->>OpenTelemetry SDK: Initializes tracing/logging (on startup)
    API_Gateway (main.ts)->>LoggerService: Handles request, logs event
    LoggerService->>OpenTelemetry SDK: Emits log event with context
    OpenTelemetry SDK->>OTLP Exporter: Exports traces/logs
Loading

Poem

In the warren, logs now flow,
With traces bright as morning snow.
OpenTelemetry hops in with cheer,
Capturing context far and near.
Every log and trace aligned—
A bunny’s dream, observably designed!
🐇✨


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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@tipusinghaw
Copy link
Copy Markdown
Contributor Author

@CodeRabbit review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Jun 24, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@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: 7

🧹 Nitpick comments (2)
apps/api-gateway/src/main.ts (1)

22-29: Consider enhancing error handling for production readiness.

The try-catch implementation is good, but consider whether the application should continue if OpenTelemetry fails to start, especially in production environments.

  try {
    await otelSDK.start();
    // eslint-disable-next-line no-console
    console.log('OpenTelemetry SDK started successfully');
  } catch (error) {
    // eslint-disable-next-line no-console
    console.error('Failed to start OpenTelemetry SDK:', error);
+   // Consider: Should we exit(1) in production if telemetry is critical?
+   if (process.env.NODE_ENV === 'production' && process.env.REQUIRE_TELEMETRY === 'true') {
+     process.exit(1);
+   }
  }
libs/logger/src/nestjsLoggerServiceAdapter.ts (1)

44-53: Consider improving the span-aware logging implementation.

The concept is good but there are opportunities for enhancement:

  1. More descriptive class name
  2. Error handling for span operations
  3. Structured logging with attributes
-@Injectable({ scope: Scope.TRANSIENT })
-export class MyLoggerService extends ConsoleLogger {
-  customLog(message: string): void {
-    const activeSpan = api.trace.getSpan(api.context.active());
-    if (activeSpan) {
-      activeSpan.addEvent(message);
-    }
-    this.log(message);
-  }
-}
+@Injectable({ scope: Scope.TRANSIENT })
+export class OpenTelemetryAwareLoggerService extends ConsoleLogger {
+  customLog(message: string, attributes?: Record<string, any>): void {
+    try {
+      const activeSpan = api.trace.getSpan(api.context.active());
+      if (activeSpan) {
+        activeSpan.addEvent(message, attributes);
+      }
+    } catch (error) {
+      // Silently handle span errors to avoid breaking application flow
+      console.warn('Failed to add span event:', error);
+    }
+    this.log(message);
+  }
+}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f2746cd and e7e7944.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (6)
  • apps/api-gateway/src/main.ts (4 hunks)
  • apps/api-gateway/src/tracer.ts (1 hunks)
  • libs/logger/src/logger.service.ts (3 hunks)
  • libs/logger/src/logging.interceptor.ts (1 hunks)
  • libs/logger/src/nestjsLoggerServiceAdapter.ts (1 hunks)
  • package.json (2 hunks)
🧰 Additional context used
🪛 ESLint
libs/logger/src/logging.interceptor.ts

[error] 12-12: Expected no linebreak before this expression.

(implicit-arrow-linebreak)

🔇 Additional comments (8)
package.json (1)

45-54: Verify OpenTelemetry package versions for security and compatibility.

The comprehensive set of OpenTelemetry dependencies looks appropriate for the integration. However, ensure these versions are current and free from known vulnerabilities.

What are the latest stable versions of OpenTelemetry Node.js packages as of 2024?
apps/api-gateway/src/main.ts (2)

1-1: LGTM! Proper SDK import placement.

Importing the OpenTelemetry SDK at the very top ensures instrumentation is active before other modules are loaded.


44-55: LGTM! Arrow function refactor improves consistency.

The middleware conversion to arrow function syntax is clean and consistent with modern JavaScript practices.

apps/api-gateway/src/tracer.ts (2)

54-61: LGTM! Proper graceful shutdown implementation.

The SIGTERM handler correctly shuts down both the SDK and logger provider, ensuring telemetry data is properly flushed before process exit.


32-34: Verify the authorization header format for your telemetry backend.

The Api-Key authorization format should match your telemetry backend's expected format (SigNoz in this case).

What is the correct authorization header format for SigNoz OTLP endpoints?

Also applies to: 39-41

libs/logger/src/nestjsLoggerServiceAdapter.ts (1)

13-35: LGTM! Clean refactoring to void methods.

The removal of explicit return statements makes the code cleaner while maintaining the same functionality.

libs/logger/src/logger.service.ts (2)

66-68: LGTM - Clean delegation pattern.

The startProfile method correctly delegates to the underlying logger implementation.


8-8: Verify cross-module dependency architecture.

The import of otelLogger from ../../../apps/api-gateway/src/tracer creates a direct dependency from a library (libs/logger) to an application module (apps/api-gateway). This violates typical layered architecture principles where libraries should not depend on specific applications.

Consider refactoring to inject the otel logger as a dependency or use a shared module to avoid tight coupling between library and application layers.

#!/bin/bash
# Search for other cross-module dependencies between libs and apps
rg --type ts "from.*apps/" libs/

Comment thread libs/logger/src/logging.interceptor.ts
Comment thread libs/logger/src/logging.interceptor.ts Outdated
Comment thread apps/api-gateway/src/tracer.ts
Comment thread apps/api-gateway/src/tracer.ts Outdated
Comment thread libs/logger/src/logger.service.ts
Comment thread libs/logger/src/logger.service.ts
Comment on lines +70 to 111
private emitToOtel(severityText: string, message: string | Error, data?: LogData): void {
const correlationId = data?.correlationId || this.contextStorageService.getContextId();
otelLogger.emit({
body: `${correlationId} ${'string' === typeof message ? message : message.message}`,
severityText,
attributes: {
app: data?.app || this.app,
organization: data?.organization || this.organization,
context: data?.context || this.context,
sourceClass: data?.sourceClass || this.sourceClass,
correlationId,
microservice: this.microserviceName,
...(data
? {
...data,
...(data.error
? {
error:
'string' === typeof data.error
? data.error
: data.error instanceof Error
? {
name: data.error.name,
message: data.error.message,
stack: data.error.stack
}
: 'object' === typeof data.error
? JSON.parse(JSON.stringify(data.error))
: String(data.error)
}
: {})
}
: {})
}
});
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Robust error normalization but verify edge cases.

The emitToOtel method implements comprehensive error normalization logic. However, there are a few areas to consider:

  1. Line 97: JSON.parse(JSON.stringify(data.error)) could throw if the object contains circular references or non-serializable properties.
  2. The method doesn't handle potential failures of otelLogger.emit().

Consider adding safer error handling:

 private emitToOtel(severityText: string, message: string | Error, data?: LogData): void {
+  try {
     const correlationId = data?.correlationId || this.contextStorageService.getContextId();
     otelLogger.emit({
       body: `${correlationId} ${'string' === typeof message ? message : message.message}`,
       severityText,
       attributes: {
         app: data?.app || this.app,
         organization: data?.organization || this.organization,
         context: data?.context || this.context,
         sourceClass: data?.sourceClass || this.sourceClass,
         correlationId,
         microservice: this.microserviceName,
         ...(data
           ? {
               ...data,
               ...(data.error
                 ? {
                     error:
                       'string' === typeof data.error
                         ? data.error
                         : data.error instanceof Error
                           ? {
                               name: data.error.name,
                               message: data.error.message,
                               stack: data.error.stack
                             }
                           : 'object' === typeof data.error
-                            ? JSON.parse(JSON.stringify(data.error))
+                            ? this.safeStringify(data.error)
                             : String(data.error)
                   }
                 : {})
             }
           : {})
       }
     });
+  } catch (error) {
+    // Silently fail if telemetry emission fails
+  }
 }
+
+private safeStringify(obj: any): string {
+  try {
+    return JSON.stringify(obj);
+  } catch {
+    return '[Circular or Non-Serializable Object]';
+  }
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private emitToOtel(severityText: string, message: string | Error, data?: LogData): void {
const correlationId = data?.correlationId || this.contextStorageService.getContextId();
otelLogger.emit({
body: `${correlationId} ${'string' === typeof message ? message : message.message}`,
severityText,
attributes: {
app: data?.app || this.app,
organization: data?.organization || this.organization,
context: data?.context || this.context,
sourceClass: data?.sourceClass || this.sourceClass,
correlationId,
microservice: this.microserviceName,
...(data
? {
...data,
...(data.error
? {
error:
'string' === typeof data.error
? data.error
: data.error instanceof Error
? {
name: data.error.name,
message: data.error.message,
stack: data.error.stack
}
: 'object' === typeof data.error
? JSON.parse(JSON.stringify(data.error))
: String(data.error)
}
: {})
}
: {})
}
});
}
private emitToOtel(severityText: string, message: string | Error, data?: LogData): void {
try {
const correlationId = data?.correlationId || this.contextStorageService.getContextId();
otelLogger.emit({
body: `${correlationId} ${'string' === typeof message ? message : message.message}`,
severityText,
attributes: {
app: data?.app || this.app,
organization: data?.organization || this.organization,
context: data?.context || this.context,
sourceClass: data?.sourceClass || this.sourceClass,
correlationId,
microservice: this.microserviceName,
...(data
? {
...data,
...(data.error
? {
error:
'string' === typeof data.error
? data.error
: data.error instanceof Error
? {
name: data.error.name,
message: data.error.message,
stack: data.error.stack
}
: 'object' === typeof data.error
? this.safeStringify(data.error)
: String(data.error)
}
: {})
}
: {})
}
});
} catch (error) {
// Silently fail if telemetry emission fails
}
}
private safeStringify(obj: any): string {
try {
return JSON.stringify(obj);
} catch {
return '[Circular or Non-Serializable Object]';
}
}
🤖 Prompt for AI Agents
In libs/logger/src/logger.service.ts between lines 70 and 105, the emitToOtel
method's error normalization uses JSON.parse(JSON.stringify(data.error)), which
can throw on circular or non-serializable objects, and otelLogger.emit() is
called without error handling. To fix this, replace the JSON serialization with
a safer deep clone or a utility that handles circular references, and wrap the
otelLogger.emit() call in a try-catch block to handle and log any exceptions
gracefully without crashing the application.

Comment thread libs/logger/src/logging.interceptor.ts Outdated
this.contextStorageService.set('x-correlation-id', newContextId);
this.contextStorageService.setContextId(newContextId);
}
//TODO: Uncomment this when user context needs to be set
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do we need this commented code?

Copy link
Copy Markdown
Contributor Author

@tipusinghaw tipusinghaw Jun 27, 2025

Choose a reason for hiding this comment

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

It's required for user context, but we can maintain it in the doc or somewhere else

Comment on lines +45 to +53
export class MyLoggerService extends ConsoleLogger {
customLog(message: string): void {
const activeSpan = api.trace.getSpan(api.context.active());
if (activeSpan) {
activeSpan.addEvent(message);
}
this.log(message);
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Where are we exactly using this class?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It was for custom span, currently we are not supporting this. Removed

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It was for custom span, currently we are not supporting this. Removed

Comment on lines -81 to -83
public startProfile(id: string) : void {
this.logger.startProfile(id);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Are we sure, we won't need this, because the interface we are implementing, has a startProfile function signature?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The startProfile method remains in the file. Only its position within the file has changed.

@GHkrishna GHkrishna added the feature This is a new feature label Jun 27, 2025
Signed-off-by: Tipu_Singh <tipu.singh@ayanworks.com>
Signed-off-by: Tipu_Singh <tipu.singh@ayanworks.com>
Signed-off-by: Tipu_Singh <tipu.singh@ayanworks.com>
Signed-off-by: Tipu_Singh <tipu.singh@ayanworks.com>
@tipusinghaw tipusinghaw force-pushed the feat/otel-and-signoz-integration-platform branch from ba1be12 to 7d40bb0 Compare June 27, 2025 10:47
Signed-off-by: Tipu_Singh <tipu.singh@ayanworks.com>
@sonarqubecloud
Copy link
Copy Markdown

@tipusinghaw tipusinghaw requested a review from GHkrishna June 27, 2025 11:43
@tipusinghaw tipusinghaw merged commit f5ce197 into develop Jun 27, 2025
8 checks passed
@tipusinghaw tipusinghaw deleted the feat/otel-and-signoz-integration-platform branch June 27, 2025 12:11
ankita-p17 pushed a commit that referenced this pull request Sep 30, 2025
* feat: otel and signoz implementation

Signed-off-by: Tipu_Singh <tipu.singh@ayanworks.com>

* fix:resolve suggested comments

Signed-off-by: Tipu_Singh <tipu.singh@ayanworks.com>

* fix:resolve suggested for  logging

Signed-off-by: Tipu_Singh <tipu.singh@ayanworks.com>

* feat: added flag for local setup

Signed-off-by: Tipu_Singh <tipu.singh@ayanworks.com>

* refactor: updated env sample and demo

Signed-off-by: Tipu_Singh <tipu.singh@ayanworks.com>

---------

Signed-off-by: Tipu_Singh <tipu.singh@ayanworks.com>
Signed-off-by: Ankita Patidar <ankita.patidar@ayanworks.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature This is a new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants