Skip to content

feat: OpenTelemetry för kapacitetsmätning#215

Open
johlju wants to merge 1 commit into
viscalyx:mainfrom
johlju:f/otel
Open

feat: OpenTelemetry för kapacitetsmätning#215
johlju wants to merge 1 commit into
viscalyx:mainfrom
johlju:f/otel

Conversation

@johlju

@johlju johlju commented May 17, 2026

Copy link
Copy Markdown
Member

Description

Screenshots (if applicable)

Related Issues

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactoring (no functional changes)
  • Performance improvement (improves performance without changing functionality)
  • Dependency update (updating libraries or tools)

Testing

  • npm run check passes locally
  • All existing tests still pass
  • Manual testing completed
  • UI tested on desktop and mobile (if applicable)

Checklist

  • Documentation updated as needed

Checklist

  • Code follows the project style guidelines (Biome)
  • Tests added/updated as needed
  • Self-review of code completed
  • Comments added for complex logic
  • No hardcoded strings (use translations if i18n is added)

This change is Reviewable

@coderabbitai

coderabbitai Bot commented May 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This PR adds OpenTelemetry-based observability for capacity events across the kravhantering application. It introduces environment configuration helpers, updates the Next.js instrumentation entry point to initialize OTel SDKs with HTTP exporters, implements capacity event recording for metrics, spans, and logs, and provides a complete Grafana LGTM dashboard for visualization. The implementation uses optional opt-in flags (OTEL_SDK_ENABLED=true plus an OTLP endpoint) to gate export behavior, while maintaining the existing JSON log stream as a fallback. Devcontainer configurations are updated with port forwarding and environment variables; comprehensive unit tests validate behavior when OTel is enabled or disabled; and documentation describes configuration, local setup, and manual verification steps.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The PR description is largely empty with only a template structure and a single related issue reference, lacking substantive information about the OpenTelemetry implementation. Complete the PR description: provide a brief summary of OpenTelemetry integration, explain why it was added, describe testing completion, and check relevant boxes for 'New feature', testing status, and verification items.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: OpenTelemetry för kapacitetsmätning' directly and clearly identifies the main feature: adding OpenTelemetry for capacity measurement, which aligns with the comprehensive changes across configuration, documentation, and implementation files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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.

@codecov

codecov Bot commented May 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.62366% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.42%. Comparing base (bac0d83) to head (8efc151).

Files with missing lines Patch % Lines
lib/observability/capacity-otel.ts 93.75% 5 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #215      +/-   ##
==========================================
+ Coverage   69.31%   69.42%   +0.10%     
==========================================
  Files         387      389       +2     
  Lines       22451    22544      +93     
  Branches     8250     8429     +179     
==========================================
+ Hits        15562    15651      +89     
- Misses       6708     6712       +4     
  Partials      181      181              
Files with missing lines Coverage Δ
lib/observability/capacity.ts 89.70% <100.00%> (+0.31%) ⬆️
lib/observability/otel-config.ts 100.00% <100.00%> (ø)
lib/observability/capacity-otel.ts 93.75% <93.75%> (ø)

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🧹 Nitpick comments (2)
.devcontainer/docker-compose.yml (1)

95-95: ⚡ Quick win

Pin grafana/otel-lgtm to a fixed tag or digest.

Using :latest makes local environments non-reproducible and can introduce surprise breakages across rebuilds.

Suggested change
-    image: grafana/otel-lgtm:latest
+    image: grafana/otel-lgtm:<pin-a-tested-version>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.devcontainer/docker-compose.yml at line 95, Replace the floating image tag
"grafana/otel-lgtm:latest" with a fixed, immutable reference (either a specific
version tag like "grafana/otel-lgtm:vX.Y.Z" or a content digest
"grafana/otel-lgtm@sha256:...") so local environments are reproducible; locate
the image entry currently set to "grafana/otel-lgtm:latest" in the
docker-compose service definition and update it to the chosen tag or digest,
then rebuild to verify the pinned image resolves correctly.
.devcontainer/elevated/docker-compose.yml (1)

109-109: ⚡ Quick win

Pin grafana/otel-lgtm to a fixed tag (or digest) in both compose files.

Both elevated and default devcontainer profiles use grafana/otel-lgtm:latest, which undermines reproducibility. Pin to a tested version in .devcontainer/docker-compose.yml (line 95) and .devcontainer/elevated/docker-compose.yml (line 109).

Suggested change
-    image: grafana/otel-lgtm:latest
+    image: grafana/otel-lgtm:<pin-a-tested-version>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.devcontainer/elevated/docker-compose.yml at line 109, Replace the floating
image reference "image: grafana/otel-lgtm:latest" with a fixed, tested tag or
digest to ensure reproducible builds; update the image line in both
.devcontainer/docker-compose.yml and .devcontainer/elevated/docker-compose.yml
(the lines currently showing "image: grafana/otel-lgtm:latest") to a specific
version (e.g., "grafana/otel-lgtm:<version>" or
"grafana/otel-lgtm@sha256:<digest>") and ensure both files use the same pinned
value.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@instrumentation.ts`:
- Around line 10-32: The dynamic imports and call to registerOTel can throw and
currently will reject register(), risking startup failure; wrap the entire async
block that imports `@vercel/otel`, OTLPLogExporter, OTLPMetricExporter,
BatchLogRecordProcessor, PeriodicExportingMetricReader and the call to
registerOTel(...) in a try/catch, log the caught error (including error details)
and return/degrade gracefully (do not rethrow) so telemetry failures do not
crash startup; ensure you still call getOpenTelemetryServiceName() only inside
the try if needed and preserve the existing configuration (logRecordProcessors
and metricReaders) when successful.

---

Nitpick comments:
In @.devcontainer/docker-compose.yml:
- Line 95: Replace the floating image tag "grafana/otel-lgtm:latest" with a
fixed, immutable reference (either a specific version tag like
"grafana/otel-lgtm:vX.Y.Z" or a content digest "grafana/otel-lgtm@sha256:...")
so local environments are reproducible; locate the image entry currently set to
"grafana/otel-lgtm:latest" in the docker-compose service definition and update
it to the chosen tag or digest, then rebuild to verify the pinned image resolves
correctly.

In @.devcontainer/elevated/docker-compose.yml:
- Line 109: Replace the floating image reference "image:
grafana/otel-lgtm:latest" with a fixed, tested tag or digest to ensure
reproducible builds; update the image line in both
.devcontainer/docker-compose.yml and .devcontainer/elevated/docker-compose.yml
(the lines currently showing "image: grafana/otel-lgtm:latest") to a specific
version (e.g., "grafana/otel-lgtm:<version>" or
"grafana/otel-lgtm@sha256:<digest>") and ensure both files use the same pinned
value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c78c25e0-d7a0-4131-beeb-71917620fc36

📥 Commits

Reviewing files that changed from the base of the PR and between bac0d83 and 8efc151.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (24)
  • .devcontainer/.env.example
  • .devcontainer/devcontainer.json
  • .devcontainer/docker-compose.yml
  • .devcontainer/elevated/devcontainer.json
  • .devcontainer/elevated/docker-compose.yml
  • .env.development
  • .env.example
  • .env.prodlike
  • cspell.jsonc
  • dev/grafana/provisioning/dashboard-provider.yml
  • dev/grafana/provisioning/dashboards/kravhantering-capacity-observability.json
  • docker-compose.otel.yml
  • docs/arkitekturbeskrivning-kravhantering.md
  • docs/capacity-management.md
  • docs/manual-test-cases.md
  • docs/poc-rhel10-podman.md
  • instrumentation.ts
  • lib/observability/capacity-otel.ts
  • lib/observability/capacity.ts
  • lib/observability/otel-config.ts
  • package.json
  • tests/unit/capacity-observability.test.ts
  • tests/unit/capacity-otel.test.ts
  • tests/unit/otel-config.test.ts

Comment thread instrumentation.ts
Comment on lines +10 to +32
const [
{ registerOTel },
{ OTLPLogExporter },
{ OTLPMetricExporter },
{ BatchLogRecordProcessor },
{ PeriodicExportingMetricReader },
] = await Promise.all([
import('@vercel/otel'),
import('@opentelemetry/exporter-logs-otlp-http'),
import('@opentelemetry/exporter-metrics-otlp-http'),
import('@opentelemetry/sdk-logs'),
import('@opentelemetry/sdk-metrics'),
])

registerOTel({
logRecordProcessors: [new BatchLogRecordProcessor(new OTLPLogExporter())],
metricReaders: [
new PeriodicExportingMetricReader({
exporter: new OTLPMetricExporter(),
}),
],
serviceName: getOpenTelemetryServiceName(),
})

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard OTel bootstrap failures to avoid startup breakage.

If any dynamic import or registerOTel(...) setup throws, register() rejects and can take down startup when telemetry is enabled. Please wrap initialization in try/catch and degrade gracefully.

Suggested patch
 export async function register(): Promise<void> {
   if (process.env.NEXT_RUNTIME === 'edge') return
   if (!isOpenTelemetryEnabled()) return
 
-  const [
-    { registerOTel },
-    { OTLPLogExporter },
-    { OTLPMetricExporter },
-    { BatchLogRecordProcessor },
-    { PeriodicExportingMetricReader },
-  ] = await Promise.all([
-    import('`@vercel/otel`'),
-    import('`@opentelemetry/exporter-logs-otlp-http`'),
-    import('`@opentelemetry/exporter-metrics-otlp-http`'),
-    import('`@opentelemetry/sdk-logs`'),
-    import('`@opentelemetry/sdk-metrics`'),
-  ])
-
-  registerOTel({
-    logRecordProcessors: [new BatchLogRecordProcessor(new OTLPLogExporter())],
-    metricReaders: [
-      new PeriodicExportingMetricReader({
-        exporter: new OTLPMetricExporter(),
-      }),
-    ],
-    serviceName: getOpenTelemetryServiceName(),
-  })
+  try {
+    const [
+      { registerOTel },
+      { OTLPLogExporter },
+      { OTLPMetricExporter },
+      { BatchLogRecordProcessor },
+      { PeriodicExportingMetricReader },
+    ] = await Promise.all([
+      import('`@vercel/otel`'),
+      import('`@opentelemetry/exporter-logs-otlp-http`'),
+      import('`@opentelemetry/exporter-metrics-otlp-http`'),
+      import('`@opentelemetry/sdk-logs`'),
+      import('`@opentelemetry/sdk-metrics`'),
+    ])
+
+    registerOTel({
+      logRecordProcessors: [new BatchLogRecordProcessor(new OTLPLogExporter())],
+      metricReaders: [
+        new PeriodicExportingMetricReader({
+          exporter: new OTLPMetricExporter(),
+        }),
+      ],
+      serviceName: getOpenTelemetryServiceName(),
+    })
+  } catch (error) {
+    // eslint-disable-next-line no-console
+    console.error(
+      '[capacity-observability] failed to initialize OpenTelemetry',
+      error instanceof Error ? error.message : String(error),
+    )
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@instrumentation.ts` around lines 10 - 32, The dynamic imports and call to
registerOTel can throw and currently will reject register(), risking startup
failure; wrap the entire async block that imports `@vercel/otel`, OTLPLogExporter,
OTLPMetricExporter, BatchLogRecordProcessor, PeriodicExportingMetricReader and
the call to registerOTel(...) in a try/catch, log the caught error (including
error details) and return/degrade gracefully (do not rethrow) so telemetry
failures do not crash startup; ensure you still call
getOpenTelemetryServiceName() only inside the try if needed and preserve the
existing configuration (logRecordProcessors and metricReaders) when successful.

@johlju

johlju commented May 29, 2026

Copy link
Copy Markdown
Member Author

Follow-up from the platform best-practices cleanup: this PR covers the application-side OTel/capacity observability work, but it does not close the remaining Platform Log Routing work.

What still remains after this PR:

  • Configure the production logging pipeline to route structured application log records by channel.
  • Forward security-audit records to the approved SIEM, audit sink, or security log platform.
  • Forward operational observability records to the approved monitoring or log analytics destination.
  • Define retention, access control, alerting, and incident-review ownership for those log streams.
  • Add operational verification showing that security-audit events reach the approved destination without exposing secrets or sensitive free text.

In other words: #215 is a good app-side observability foundation, especially for capacity events, but the platform-side routing, ownership, retention, and production verification still need separate implementation/tracking.

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.

OpenTelemetry för kapacitetsmätning

1 participant