{"id":24,"date":"2026-06-06T21:48:52","date_gmt":"2026-06-06T21:48:52","guid":{"rendered":"https:\/\/softwareproduction.eu\/wordpress\/?p=24"},"modified":"2026-06-06T23:45:30","modified_gmt":"2026-06-06T23:45:30","slug":"the-nuxt-observability-stack-tracing-logging-and-pm2-metrics","status":"publish","type":"post","link":"https:\/\/softwareproduction.eu\/?p=24","title":{"rendered":"The Nuxt Observability Stack: Tracing, Logging, and PM2 Metrics"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Migrating from a legacy application to a modern Nuxt 4 stack is not just about new frameworks and better performance numbers. The real shift is moving from <em>reactive firefighting<\/em> to <em>proactive observability<\/em> \u2014 knowing what is slow, why it is slow, and how the platform behaves under real load.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This observability stack has three pillars:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>End-to-end distributed tracing<\/strong> across Nginx, Nuxt, backend services, and Redis<\/li>\n<li><strong>Structured logging<\/strong> with per-module, runtime-tunable log levels<\/li>\n<li><strong>Node.js process diagnostics<\/strong> for GC, heap, and CPU under PM2<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Together, they turn a deployment into something that can be <em>reasoned<\/em> about, not just hoped over.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Flying Blind vs. Full Visibility<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Without observability, slowdowns are only visible when users complain, and failures are only visible when error rates spike. The underlying cause remains unknown: which component was slow, which call failed, which cache missed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In a system with multiple containers \u2014 for example, a frontend app, an API, a proxy, and Redis \u2014 a single request crosses several services. Without tracing, correlating what happened means manually matching timestamps across separate log streams. Most teams stop long before they get a clear picture.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The target state is <strong>one trace ID created at the edge and propagated from the browser through every service<\/strong>, so a single click in the observability backend reveals the full request waterfall.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Three-Layer Telemetry: Traces, Proxy Spans, and Container Metrics<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The observability stack has three layers, each capturing a different dimension of the system:<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart TB\n  subgraph L1[\"Layer 1: SDK Instrumentation\"]\n    L1a[\"Node.js applicationinsights&lt;br\/&gt;+ .NET AI SDK\"]\n    L1b[\"\u2192 Request traces, dependency calls, exceptions\"]\n    L1c[\"\u2192 Custom events (GraphQL operations, cache metrics)\"]\n  end\n\n  subgraph L2[\"Layer 2: Nginx OpenTelemetry Module\"]\n    L2a[\"\u2192 Span per proxied request\"]\n    L2b[\"\u2192 W3C Trace Context headers&lt;br\/&gt;(traceparent, tracestate)\"]\n    L2c[\"\u2192 Complete proxy \u2192 SPA \u2192 API waterfall\"]\n  end\n\n  subgraph L3[\"Layer 3: Container Apps Managed OTel Agent\"]\n    L3a[\"\u2192 Container-level metrics&lt;br\/&gt;(CPU, memory, restarts)\"]\n    L3b[\"\u2192 All containers, including Redis\"]\n    L3c[\"\u2192 Zero code changes\"]\n  end\n\n  L1 --- L2 --- L3<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Layer 1: SDK Instrumentation<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Both the frontend app and the API send request traces, dependency calls, exceptions, and custom events to the observability backend. The Node.js SDK automatically instruments incoming HTTP requests, outgoing HTTP calls, and Redis operations.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A GraphQL server module can add <strong>custom dependency telemetry<\/strong> for every subgraph call and every Redis cache operation:<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart TB\n  subgraph GQL[\"Custom Dependency Event: GraphQL\"]\n    direction TB\n    g1[\"Name: GraphQL: cms\/pageByPath\"]\n    g2[\"Type: GraphQL\"]\n    g3[\"Duration: 45ms\"]\n    g4[\"Success: true\"]\n    g5[\"operationName: pageByPath\"]\n    g6[\"subgraph: cms\"]\n    g7[\"cacheHit: false\"]\n    g8[\"transactionId: abc-123-def\"]\n  end\n\n  subgraph RED[\"Custom Dependency Event: Redis\"]\n    direction TB\n    r1[\"Name: Redis: cache-check\"]\n    r2[\"Type: Redis\"]\n    r3[\"Duration: 2ms\"]\n    r4[\"Success: true\"]\n    r5[\"operation: GET\"]\n    r6[\"cacheHit: true\"]\n    r7[\"key: page-data:\/products\/premium\"]\n  end<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">These custom events land in the <strong>same trace<\/strong> as the HTTP request, so it becomes clear which operations ran, which caches hit or missed, and how long each step took.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Layer 2: Nginx OpenTelemetry<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The reverse proxy includes the <code>nginxinc\/nginx-otel<\/code> module. Every proxied request becomes a span and carries W3C Trace Context headers:<\/p>\n\n\n\n<pre class=\"mermaid\">sequenceDiagram\n  participant B as Browser\n  participant N as Nginx Proxy\n  participant S as Nuxt SPA (Node.js)\n  participant A as Backend API\n  participant R as Redis\n\n  B-&gt;&gt;N: HTTP request&lt;br\/&gt;(no trace context yet)\n  Note right of N: Creates span&lt;br\/&gt;Generates traceparent header&lt;br\/&gt;traceparent: 00-abcdef1234567890-span1-01\n  N-&gt;&gt;S: Forward request&lt;br\/&gt;+ traceparent\n\n  Note right of S: Reads traceparent&lt;br\/&gt;Creates child span&lt;br\/&gt;Propagates to outgoing calls\n\n  S-&gt;&gt;R: Redis cache GET&lt;br\/&gt;(child span)\n  S-&gt;&gt;A: GraphQL \u2192 CMS API&lt;br\/&gt;(child span)\n  S-&gt;&gt;A: GraphQL \u2192 Backend API&lt;br\/&gt;(child span)\n\n  A-&gt;&gt;A: Database calls,&lt;br\/&gt;business logic (child spans)<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A single trace ID stitches together every hop. The end-to-end transaction view in the observability backend renders the full waterfall:<\/p>\n\n\n\n<pre class=\"mermaid\">gantt\n  dateFormat  x\n  axisFormat  %Lms\n\n  section Nginx Proxy\n  Nginx Proxy         :active, nginx, 0, 150\n\n  section SPA Request\n  SPA Request         :spa, 10, 140\n  Redis GET           :redis, 20, 10\n  GraphQL CMS         :cms, 30, 40\n  GraphQL Backend     :backend, 40, 80\n\n  section Backend API\n  API Request         :api, 60, 70\n  SQL Query           :sql, 80, 30<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Layer 3: Container-Level Metrics<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The container environment runs a managed OpenTelemetry collector that gathers container metrics \u2014 CPU, memory, restart counts \u2014 for all containers, including Redis. No application changes are required.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This layer answers operational questions:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Is Redis consuming too much memory?<\/li>\n<li>Are frontend replicas flapping?<\/li>\n<li>What is the steady-state CPU profile for API containers?<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Transaction ID Propagation<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Distributed traces are useful for visualizing a single request, but day-to-day debugging often starts from logs. To bridge both worlds, the proxy generates an <code>x-transaction-id<\/code> header for every incoming request:<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart TB\n  N[\"Nginx&lt;br\/&gt;x-transaction-id: txn-abc-123\"]\n  FE[\"Frontend app\"]\n  API[\"API\"]\n  GQL[\"GraphQL custom events\"]\n\n  N --&gt;|\"Reads header&lt;br\/&gt;adds to outgoing calls&lt;br\/&gt;logs include txn-abc-123\"| FE\n  N --&gt; API\n  FE --&gt;|\"Includes txn-abc-123&lt;br\/&gt;in request &amp; logs\"| API\n  FE --&gt;|\"Tag events with&lt;br\/&gt;txn-abc-123\"| GQL\n  API --&gt;|\"Logs include&lt;br\/&gt;txn-abc-123\"| GQL<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The transaction ID is mapped to the W3C <code>traceparent<\/code> trace ID. Developers can start from either side \u2014 a transaction ID from logs or a trace ID from the observability backend \u2014 and still recover the complete request history.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">What Metrics Tell You<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The combined telemetry stack tracks several metric categories, each answering a distinct question:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Metric Category<\/th><th>Examples<\/th><th>Question It Answers<\/th><\/tr><\/thead><tbody><tr><td><strong>Response times<\/strong><\/td><td>Per-endpoint, per-container latency<\/td><td>&#8220;Which pages are slow?&#8221;<\/td><\/tr><tr><td><strong>Error rates<\/strong><\/td><td>HTTP 5xx, GraphQL errors, exceptions<\/td><td>&#8220;What is failing?&#8221;<\/td><\/tr><tr><td><strong>Cache metrics<\/strong><\/td><td>Hit\/miss rates per cache tier<\/td><td>&#8220;Is caching effective?&#8221;<\/td><\/tr><tr><td><strong>Resource usage<\/strong><\/td><td>CPU, memory per container\/worker<\/td><td>&#8220;Are we right-sized?&#8221;<\/td><\/tr><tr><td><strong>Dependency durations<\/strong><\/td><td>GraphQL subgraph calls, Redis ops<\/td><td>&#8220;Which external call is slow?&#8221;<\/td><\/tr><tr><td><strong>User journeys<\/strong><\/td><td>Page-to-page navigation funnels<\/td><td>&#8220;Where do users drop off?&#8221;<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Alerting Strategy: Symptoms First, Causes Later<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Metrics matter only when they drive action. The guiding principle is:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">> <strong>Alert on symptoms, investigate with traces.<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Symptom alert<\/strong>:<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">  &#8220;Frontend P95 response time exceeded 2 seconds for 5 minutes.&#8221;<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Investigation<\/strong>:<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">  Open the traces for those slow requests \u2192 locate the slow dependency \u2192 fix the underlying issue.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Alerting directly on causes like Redis CPU > 80% creates noise and false positives, because Redis CPU can legitimately spike during cache invalidation without harming users. Symptom-based alerts keep noise low and align alerts with real user impact.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Structured Logging in Nuxt: From <code>console.log<\/code> to Observability<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Traces tell you <em>where<\/em> the problem is. Logs tell you <em>what<\/em> happened. To make that effective, logging has to be more than printing strings.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The <code>console.log<\/code> Problem<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Using <code>console.log<\/code> in a production SSR application causes real issues:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>No severity levels<\/strong> \u2014 errors are indistinguishable from informational noise  <\/li>\n<li><strong>No structure<\/strong> \u2014 freeform strings cannot be reliably queried, filtered, or aggregated  <\/li>\n<li><strong>No context<\/strong> \u2014 you cannot tell which request, user, or component produced the log  <\/li>\n<li><strong>No control<\/strong> \u2014 you cannot selectively enable verbose logging for one module without overwhelming the output  <\/li>\n<li><strong>SSR noise<\/strong> \u2014 server-side logs are mixed with framework output, health checks, and PM2 logs<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">There is a big difference between &#8220;we have logging&#8221; and &#8220;we have useful logging.&#8221;   The first gives you strings to grep. The second gives you a structured, queryable observability layer.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The Logging Architecture<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The logging system has three main building blocks:<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart TB\n  subgraph APP[\"Application Code\"]\n    A1[\"const log = useLogger('shopping-cart')\"]\n    A2[\"log.info('Item added', { productId, quantity })\"]\n  end\n\n  subgraph UL[\"useLogger Composable\"]\n    UL1[\"Tagged with module name\"]\n    UL2[\"Checks if this module's level is enabled\"]\n    UL3[\"Formats structured message\"]\n  end\n\n  subgraph MS[\"Multi-Sink Router\"]\n    S1[\"Sink 1: Console (development)&lt;br\/&gt;Formatted, colored, human-readable\"]\n    S2[\"Sink 2: Observability Backend&lt;br\/&gt;Structured JSON, custom properties\"]\n    S3[\"Sink 3: DevTools Log Viewer&lt;br\/&gt;Real-time, filterable, in-browser\"]\n  end\n\n  APP --&gt; UL --&gt; MS\n  MS --&gt; S1\n  MS --&gt; S2\n  MS --&gt; S3<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">The <code>useLogger<\/code> Composable<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Each module gets its own logger instance:<\/p>\n\n\n\n<pre><code class=\"language-typescript\">const log = useLogger('shopping-cart')\n\nlog.debug('Cart state loaded', { items: cart.items.length })\nlog.info('Item added', { productId: 'abc', quantity: 2 })\nlog.warn('Price mismatch detected', { expected: 29.99, actual: 31.99 })\nlog.error('Checkout failed', { error: err.message, orderId })<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Every logger is <strong>tagged<\/strong> with its module name. This enables per-module log level control \u2014 you can set <code>shopping-cart<\/code> to <code>debug<\/code> while keeping <code>navigation<\/code> at <code>warn<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Severity Levels<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Level<\/th><th>When to Use<\/th><th>Example<\/th><\/tr><\/thead><tbody><tr><td><code>debug<\/code><\/td><td>Development-only details<\/td><td>&#8220;Cart state loaded, 3 items&#8221;<\/td><\/tr><tr><td><code>info<\/code><\/td><td>Significant business events<\/td><td>&#8220;Item added to cart&#8221;<\/td><\/tr><tr><td><code>warn<\/code><\/td><td>Unexpected but recoverable<\/td><td>&#8220;Price mismatch, using server price&#8221;<\/td><\/tr><tr><td><code>error<\/code><\/td><td>Failures requiring attention<\/td><td>&#8220;Checkout failed, payment rejected&#8221;<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Multi-Sink Routing<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Each log message is fanned out to multiple sinks at once.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Sink 1: Console (Development)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">In development, logs are written to both the browser console and Node.js stdout with:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Color coding by severity  <\/li>\n<li>A module name prefix  <\/li>\n<li>Collapsible structured payloads (objects expand on click)<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Sink 2: Observability Backend (Production)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">In production, logs are sent as structured events:<\/p>\n\n\n\n<pre><code class=\"language-text\">Observability Event:\n{\n  name: \"shopping-cart:info\",\n  properties: {\n    module: \"shopping-cart\",\n    severity: \"info\",\n    message: \"Item added\",\n    productId: \"abc-123\",\n    quantity: 2,\n    requestId: \"req-xyz\",\n    timestamp: \"2025-06-02T12:34:56Z\"\n  }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">These events can be queried with KQL (Kusto Query Language):<\/p>\n\n\n\n<pre><code class=\"language-sql\">customEvents\n| where name startswith \"shopping-cart\"\n| where customDimensions.severity == \"error\"\n| project timestamp, customDimensions.message, customDimensions.productId\n| order by timestamp desc<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Sink 3: DevTools Log Viewer<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A custom DevTools tab shows logs in real time:<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart TB\n  subgraph DT[\"DevTools \u2014 Logs Tab\"]\n    F[\"Filter controls:&lt;br\/&gt;[All Modules \u25bc] [Info \u25bc] [Search...]\"]\n    L1[\"12:34:56 INFO  shopping-cart&lt;br\/&gt;Item added {productId: 'abc', quantity: 2}\"]\n    L2[\"12:34:57 DEBUG catalog-query&lt;br\/&gt;Cache hit for key 10115\"]\n    L3[\"12:34:58 WARN  shopping-cart&lt;br\/&gt;Price mismatch {expected: 29.99, actual: 31}\"]\n    L4[\"12:35:01 ERROR checkout&lt;br\/&gt;Payment failed {orderId: 'ord-789'}\"]\n  end\n\n  F --&gt; L1 --&gt; L2 --&gt; L3 --&gt; L4<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Capabilities:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Filter by severity, such as only errors or debug and above  <\/li>\n<li>Filter by module, such as only <code>shopping-cart<\/code> logs  <\/li>\n<li>Full-text search across messages  <\/li>\n<li>Expandable structured data payloads<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Runtime Log Level Control<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Log levels are adjustable <strong>at runtime<\/strong> without restarting the app.<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart TB\n  subgraph CFG[\"Default levels (from config)\"]\n    C1[\"shopping-cart: info\"]\n    C2[\"catalog-query: warn\"]\n    C3[\"navigation: warn\"]\n  end\n\n  subgraph RT[\"Runtime override (via API or DevTools)\"]\n    R1[\"shopping-cart: debug  \u2190 changed\"]\n    R2[\"catalog-query: warn   \u2190 unchanged\"]\n    R3[\"navigation: info      \u2190 changed\"]\n  end\n\n  CFG --&gt; RT\n\n  subgraph EFFECT[\"Effect\"]\n    E1[\"shopping-cart now outputs debug logs\"]\n    E2[\"No server restart\"]\n    E3[\"No redeploy\"]\n    E4[\"No impact on other modules\"]\n  end\n\n  RT --&gt; EFFECT<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A typical production debugging workflow:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>A user reports an issue  <\/li>\n<li>Enable <code>debug<\/code> logging for the relevant module via an API or DevTools  <\/li>\n<li>Reproduce the problem  <\/li>\n<li>Inspect the debug logs in the observability backend  <\/li>\n<li>Turn debug logging off again and restore the default level<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">No deployment, no restart, and no log flood from unrelated modules.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">SSR-Aware Logging<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">In an SSR app, logging must handle both server and client execution contexts:<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart LR\n  subgraph SRV[\"SSR Execution (Server: Node.js)\"]\n    S1[\"log.info('Page rendered')\"]\n    S2[\"Output:&lt;br\/&gt;stdout (PM2 logs)&lt;br\/&gt;Observability backend\"]\n    S3[\"Context:&lt;br\/&gt;Request URL&lt;br\/&gt;Request ID&lt;br\/&gt;User-Agent\"]\n    S1 --&gt; S2 --&gt; S3\n  end\n\n  subgraph CLI[\"Client Execution (Browser)\"]\n    C1[\"log.info('Button clicked')\"]\n    C2[\"Output:&lt;br\/&gt;Browser console&lt;br\/&gt;DevTools Log Viewer&lt;br\/&gt;Observability backend telemetry\"]\n    C3[\"Context:&lt;br\/&gt;Current route&lt;br\/&gt;Session ID\"]\n    C1 --&gt; C2 --&gt; C3\n  end<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><code>useLogger<\/code> detects where it is running and routes logs to the right sinks.   Server-side logs include request context such as URL, request ID, and user agent.   Client-side logs include session context such as current route and user interactions.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Replacing <code>console.log<\/code> Safely<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The migration away from <code>console.log<\/code> is incremental.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>An ESLint rule flags <code>console.log<\/code> usage and suggests replacing it with <code>useLogger<\/code>. It does not auto-fix, so the developer explicitly chooses the severity and module tag.<\/li>\n<li>For legacy code, a global console interceptor captures <code>console.*<\/code> calls and forwards them into the structured logging pipeline under a <code>legacy<\/code> module tag. This ensures nothing is lost during the transition.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Over time, the codebase shifts from unstructured strings to queryable, structured events.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Node.js Observability Under PM2: Diagnostics, GC, and CPU<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Application-level traces and logs tell you <strong>what<\/strong> is slow. To understand <strong>why the Node.js process itself<\/strong> degrades \u2014 heap growth, GC pauses, event loop lag \u2014 you need process-level visibility.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Three Nuxt modules provide this:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>diagnostics<\/code> \u2014 per-request aggregation and pattern learning<\/li>\n<li><code>diagnostics-heap<\/code> \u2014 GC and heap monitoring with leak detection<\/li>\n<li><code>diagnostics-profiler<\/code> \u2014 automatic CPU profiling for slow requests<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">These sit alongside the Nuxt app, PM2, and Nginx, and feed directly into the same observability backend.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Layer 1: Per-Request Aggregation (<code>diagnostics<\/code> module)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The diagnostics module captures seven metrics for every HTTP request:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Metric<\/th><th>What It Measures<\/th><\/tr><\/thead><tbody><tr><td>Duration<\/td><td>Total request handling time (ms)<\/td><\/tr><tr><td>Input size<\/td><td>Request body size (bytes)<\/td><\/tr><tr><td>Output size<\/td><td>Response body size (bytes)<\/td><\/tr><tr><td>CPU usage<\/td><td>Process CPU delta during request<\/td><\/tr><tr><td>Memory delta<\/td><td>Heap memory change during request<\/td><\/tr><tr><td>Event loop lag<\/td><td>Main thread blocking time (ms)<\/td><\/tr><tr><td>Status code<\/td><td>HTTP response status<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">O(1) Memory Aggregation<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Traditional APM tools store one record per request \u2014 8.6 million records per day at 100 req\/s. This module takes a different approach: <strong>no per-request storage<\/strong>. Only aggregations such as min, max, sum, and count are retained.<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart TB\n  subgraph TRAD[\"Per-Request Storage (traditional APM)\"]\n    T1[\"Request 1: { duration: 150, cpu: 12, memory: 35MB, ... }\"]\n    T2[\"Request 2: { duration: 200, cpu: 15, memory: 42MB, ... }\"]\n    T3[\"Request 3: { duration: 180, cpu: 11, memory: 38MB, ... }\"]\n    Tn[\"Request N: { duration: ???, cpu: ??, memory: ???, ... }\"]\n    TM[\"Memory usage: O(N) \u2014 grows with request count\"]\n    T1 --&gt; T2 --&gt; T3 --&gt; Tn --&gt; TM\n  end\n\n  subgraph AGG[\"Aggregation-Only (diagnostics module)\"]\n    A1[\"Aggregate:\"]\n    A2[\"duration: { min, max, sum, count }\"]\n    A3[\"cpu: { min, max, sum, count }\"]\n    A4[\"memory: { min, max, sum, count }\"]\n    AM[\"Memory usage: O(1) \u2014 constant&lt;br\/&gt;regardless of request count\"]\n    A1 --&gt; A2 --&gt; A3 --&gt; A4 --&gt; AM\n  end<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Monitoring overhead is constant, regardless of traffic volume.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Slow-Request Pattern Detection<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Every 30 seconds, after at least 50 requests, the module detects <strong>patterns<\/strong> in slow requests by grouping on several features:<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart TB\n  subgraph FB[\"Feature Buckets\"]\n    F1[\"URL pattern: \/products\/*, \/checkout\/*, \/\"]\n    F2[\"HTTP method: GET, POST\"]\n    F3[\"Payload size: small (&lt;1KB), medium, large\"]\n    F4[\"Path depth: 1, 2, 3, 4+\"]\n  end\n\n  FB --&gt; P[\"For each bucket:&lt;br\/&gt;Compute probability(request is slow)&lt;br\/&gt;If probability \u2265 50% and count \u2265 3 \u2192 emit pattern\"]<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">For each feature bucket, the algorithm calculates the <strong>probability that a request in this bucket is slow<\/strong> (exceeds the configured threshold, such as 500ms). If a bucket has at least 50% slow probability with at least 3 samples, a pattern is emitted:<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart TB\n  P1[\"Observed: \/checkout\/*&lt;br\/&gt;73% of requests slow (&gt;500ms)&lt;br\/&gt;12 observations in last 30s\"]\n  P2[\"Emit custom event:&lt;br\/&gt;name = 'SlowRequestPatterns'&lt;br\/&gt;pattern = '\/checkout\/*'&lt;br\/&gt;probability &gt; 0.5\"]\n  P1 --&gt; P2<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Pattern detection surfaces <strong>systemic slowness<\/strong> that individual alerts miss. A single slow request might be a fluke. A persistent pattern for a specific URL points to a real problem with that page&#8217;s data fetching or rendering.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">SSR GraphQL Disambiguation<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">During SSR, the Nuxt server makes GraphQL calls to itself \u2014 real HTTP requests that pass through the diagnostics middleware. Without disambiguation, each page request would be counted twice.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The module identifies SSR-internal requests via the CSRF bypass token from the security layer and excludes them. You get accurate per-page measurements with no double-counting.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Layer 2: Heap Memory and GC (<code>diagnostics-heap<\/code> module)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>diagnostics-heap<\/code> module uses V8&#8217;s <code>PerformanceObserver<\/code> API to monitor garbage collection events in real time.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">GC Event Categories<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>GC Type<\/th><th>What It Collects<\/th><th>Typical Duration<\/th><\/tr><\/thead><tbody><tr><td><code>scavenge<\/code><\/td><td>Young generation (new objects)<\/td><td>1\u20135 ms<\/td><\/tr><tr><td><code>mark-sweep<\/code><\/td><td>Full heap (major GC)<\/td><td>10\u201350 ms<\/td><\/tr><tr><td><code>incremental<\/code><\/td><td>Incremental marking<\/td><td>1\u201310 ms<\/td><\/tr><tr><td><code>weakcb<\/code><\/td><td>Weak reference callbacks<\/td><td><1 ms<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Each event records duration, heap before\/after, and bytes freed. Events are aggregated into time-series data and sent periodically to the observability backend.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Automatic Memory Leak Detection<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The module tracks consecutive heap growth over time. When <code>heapUsed<\/code> increases for \\(N\\) consecutive intervals without a significant GC reduction, it emits a leak detection event:<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart TB\n  subgraph WIN[\"Observation Window: 10 intervals (5 min each)\"]\n    I1[\"Interval 1: heapUsed = 800 MB\"]\n    I2[\"Interval 2: heapUsed = 820 MB  \u2191 +20 MB\"]\n    I3[\"Interval 3: heapUsed = 845 MB  \u2191 +25 MB\"]\n    I4[\"Interval 4: heapUsed = 860 MB  \u2191 +15 MB\"]\n    I5[\"Interval 5: heapUsed = 890 MB  \u2191 +30 MB\"]\n  end\n\n  WIN --&gt; DET[\"5 consecutive growth intervals detected&lt;br\/&gt;Growth rate \u2248 18 MB\/interval = 216 MB\/hour\"]\n\n  DET --&gt; EVT[\"Emit event:&lt;br\/&gt;{ event: 'PotentialMemoryLeak',&lt;br\/&gt;confidence: 'medium',&lt;br\/&gt;growthRateMBPerHour: 216,&lt;br\/&gt;consecutiveGrowths: 5 }\"]\n\n  EVT --&gt; HIGH[\"If growth continues to 8+ intervals:&lt;br\/&gt;confidence \u2192 'high'\"]<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The confidence level reduces false positives. Short-term growth is normal during traffic spikes. Only sustained growth triggers a leak alert.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Automatic Heap Dumps<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">When <code>heapUsed<\/code> exceeds a configurable threshold (default 1024 MB), a <code>.heapsnapshot<\/code> file is written automatically. It can be loaded into Chrome DevTools for detailed memory analysis.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">V8 Heap Space Breakdown<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Periodic sampling of <code>v8.getHeapSpaceStatistics()<\/code> provides per-space memory usage:<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart TB\n  subgraph HS[\"V8 Heap Spaces\"]\n    N[\"new_space: 16 MB total, 8 MB used&lt;br\/&gt;Purpose: New objects (GC: scavenge)\"]\n    O[\"old_space: 900 MB total, 780 MB used&lt;br\/&gt;Purpose: Survived objects\"]\n    C[\"code_space: 12 MB total, 10 MB used&lt;br\/&gt;Purpose: Compiled code\"]\n    L[\"large_object: 45 MB total, 40 MB used&lt;br\/&gt;Purpose: Objects &gt; 512 KB\"]\n  end\n\n  N --&gt; O --&gt; C --&gt; L<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This is essential for distinguishing object leaks (<code>old_space<\/code> growing) from code cache growth (<code>code_space<\/code> growing) \u2014 different causes, different fixes.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Layer 3: CPU Profiling (<code>diagnostics-profiler<\/code> module)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>diagnostics-profiler<\/code> module automatically captures V8 CPU profiles for requests that exceed the slow-request threshold.<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart TB\n  RS[\"Request starts&lt;br\/&gt;Timer begins\"]\n  TH[\"Duration exceeds threshold\"]\n  PR[\"Profiler activates&lt;br\/&gt;Capture V8 CPU profile\"]\n  RC[\"Request completes&lt;br\/&gt;Profile saved as .cpuprofile\"]\n  DEV[\"Load in Chrome DevTools&lt;br\/&gt;Flame chart analysis\"]\n\n  RS --&gt; TH --&gt; PR --&gt; RC --&gt; DEV\n\n  subgraph FL[\"Example Flame Chart Breakdown\"]\n    F1[\"SSR renderer: 45% CPU time\"]\n    F2[\"GraphQL response parsing: 30%\"]\n    F3[\"HTML serialization: 15%\"]\n    F4[\"Other: 10%\"]\n  end\n\n  DEV --&gt; FL<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Profiles are in the standard V8 format, which Chrome DevTools renders as a flame chart, showing exactly which functions consumed CPU time.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The Unified Picture: From Symptom to Root Cause<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">When a slow request occurs, <strong>all layers<\/strong> fire in concert \u2014 traces, logs, and Node diagnostics:<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart TB\n  subgraph L1[\"Layer 1 (diagnostics)\"]\n    L1a[\"Records duration: 2,300 ms\"]\n    L1b[\"Emits SlowRequest event\"]\n    L1c[\"Updates pattern detection\"]\n  end\n\n  subgraph L2[\"Layer 2 (diagnostics-heap)\"]\n    L2a[\"Records memory delta: +45 MB\"]\n    L2b[\"Checks for leak pattern\"]\n    L2c[\"If heap &gt; threshold \u2192 auto heap dump\"]\n  end\n\n  subgraph L3[\"Layer 3 (diagnostics-profiler)\"]\n    L3a[\"Captures .cpuprofile\"]\n    L3b[\"Shows 65% time in CMS API response parsing\"]\n  end\n\n  subgraph OBS[\"Observability backend\"]\n    O1[\"End-to-end trace correlates:\"]\n    O2[\"Nginx span\"]\n    O3[\"Nuxt request + GraphQL dependencies\"]\n    O4[\"API calls + SQL query\"]\n    O5[\"Custom logs tagged with transaction ID\"]\n    O6[\"SlowRequestPatterns + GC + leak signals\"]\n  end\n\n  L1 --&gt; OBS\n  L2 --&gt; OBS\n  L3 --&gt; OBS<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You can move from:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>An alert: &#8220;P95 for \/checkout is 2.3s&#8221;<\/li>\n<li>To the trace: &#8220;Most time is in the CMS subgraph&#8221;<\/li>\n<li>To logs: &#8220;Price mismatch warnings and retries&#8221;<\/li>\n<li>To process-level data: &#8220;Major GC pauses plus heap growth&#8221;<\/li>\n<li>To artifacts: <code>.heapsnapshot<\/code> and <code>.cpuprofile<\/code> for offline analysis<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">All within a single, correlated observability fabric.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Capacity Planning Endpoint<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The diagnostics module exposes a <code>\/api\/__profiler\/memory-capacity<\/code> endpoint that calculates the theoretical memory requirement:<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart TB\n  IN[\"Inputs:&lt;br\/&gt;Baseline = 200 MB&lt;br\/&gt;Requests\/sec = 10&lt;br\/&gt;Avg RT = 150 ms (0.15 s)&lt;br\/&gt;Memory\/req = 35 MB\"]\n  CONC[\"Concurrent requests = 10 \u00d7 0.15 = 1.5\"]\n  PEAK[\"Peak memory = 200 + (1.5 \u00d7 35) = 252.5 MB\"]\n  SAFETY[\"With 3\u00d7 safety factor = 757.5 MB\"]\n  CFG[\"Set --max-old-space-size \u2265 768 MB\"]\n\n  IN --&gt; CONC --&gt; PEAK --&gt; SAFETY --&gt; CFG<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This directly informs the V8 heap cap and container memory allocation, bridging runtime diagnostics with deployment configuration.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Lessons Learned Across the Stack<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Distributed tracing is not optional in a multi-container architecture<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Without trace correlation, debugging a slow request across four or more containers means combing through isolated log streams and aligning timestamps by hand. With W3C Trace Context, one trace ID tells the whole story. Setup cost: a few hours. Debugging savings: ongoing.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Custom dependency events are worth the effort<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Out-of-the-box instrumentation knows about HTTP calls and Redis commands but has no idea that a specific call is &#8220;a GraphQL query to the CMS subgraph for page-by-path.&#8221; Custom events supply that semantic meaning \u2014 you can ask for &#8220;all slow CMS page queries&#8221; instead of &#8220;all slow HTTP calls to this URL.&#8221;<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Separate the telemetry environment from the application environment<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Using separate observability instances for test and production stops test noise from polluting production dashboards. Feature branches can report into the test instance.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Layer 3 catches what SDK instrumentation misses<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">SDK instrumentation covers what happens inside application processes. Container and Node-level metrics capture everything around them \u2014 Redis memory growth, restarts, OOM kills, GC pauses. Without this layer, Redis running out of memory or Node leaks are invisible until things start failing.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Per-module log levels are essential at scale<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">With 35+ modules, a single global log level is useless because enabling <code>debug<\/code> generates thousands of messages per second. Per-module levels let teams zoom in on the area they care about without drowning in noise.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Runtime control changes how production issues are debugged<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">When enabling debug logging requires a deployment, teams either leave it on permanently or never enable it. Runtime controls turn it into a normal tool: enable, investigate, disable.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Structured data beats formatted strings<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><code>log.info('Item added', { productId: 'abc', quantity: 2 })<\/code> is queryable:   &#8220;show all items with quantity > 5.&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>console.log('Item abc added, quantity: 2')<\/code> needs regex parsing and still breaks when the format changes. The extra effort to log structured data pays off every time it needs to be analyzed.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Pattern detection beats single-event alerts<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Single slow-request alerts create noise and fatigue. A pattern like &#8220;73% of \/checkout requests are slow&#8221; is actionable. It tells you exactly where to investigate.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Automatic heap dumps are worth the disk space<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">When a leak is detected in production, reproducing it locally is often the hardest part. Automatic heap dumps capture the heap state at the moment of detection \u2014 no reproduction required. A single snapshot can save days of debugging.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Munir Husseini is a software architect specializing in full-stack TypeScript, .NET, and cloud-native architectures.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Migrating from a legacy application to a modern Nuxt 4 stack is not just about new frameworks and better performance numbers. The real shift is moving from reactive firefighting to proactive observability \u2014 knowing what is slow, why it is slow, and how the platform behaves under real load. This observability stack has three pillars: [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":244,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[5],"tags":[],"class_list":["post-24","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-advanced-web-app-with-nuxt-and-net"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.7 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>The Nuxt Observability Stack: Tracing, Logging, and PM2 Metrics - Scalable Web Production<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/softwareproduction.eu\/?p=24\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"The Nuxt Observability Stack: Tracing, Logging, and PM2 Metrics - Scalable Web Production\" \/>\n<meta property=\"og:description\" content=\"Migrating from a legacy application to a modern Nuxt 4 stack is not just about new frameworks and better performance numbers. The real shift is moving from reactive firefighting to proactive observability \u2014 knowing what is slow, why it is slow, and how the platform behaves under real load. This observability stack has three pillars: [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/softwareproduction.eu\/?p=24\" \/>\n<meta property=\"og:site_name\" content=\"Scalable Web Production\" \/>\n<meta property=\"article:published_time\" content=\"2026-06-06T21:48:52+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-06-06T23:45:30+00:00\" \/>\n<meta name=\"author\" content=\"Munir Husseini\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Munir Husseini\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"13 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=24#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=24\"},\"author\":{\"name\":\"Munir Husseini\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#\\\/schema\\\/person\\\/fec48f54713e1bd117640fb9b748802f\"},\"headline\":\"The Nuxt Observability Stack: Tracing, Logging, and PM2 Metrics\",\"datePublished\":\"2026-06-06T21:48:52+00:00\",\"dateModified\":\"2026-06-06T23:45:30+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=24\"},\"wordCount\":1561,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=24#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/softwareproduction.eu\\\/wordpress\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/18-full-observability-stack.jpg\",\"articleSection\":[\"Advanced Web App with Nuxt and .NET\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/softwareproduction.eu\\\/?p=24#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=24\",\"url\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=24\",\"name\":\"The Nuxt Observability Stack: Tracing, Logging, and PM2 Metrics - Scalable Web Production\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=24#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=24#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/softwareproduction.eu\\\/wordpress\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/18-full-observability-stack.jpg\",\"datePublished\":\"2026-06-06T21:48:52+00:00\",\"dateModified\":\"2026-06-06T23:45:30+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=24#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/softwareproduction.eu\\\/?p=24\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=24#primaryimage\",\"url\":\"https:\\\/\\\/softwareproduction.eu\\\/wordpress\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/18-full-observability-stack.jpg\",\"contentUrl\":\"https:\\\/\\\/softwareproduction.eu\\\/wordpress\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/18-full-observability-stack.jpg\",\"width\":1880,\"height\":1099},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=24#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/softwareproduction.eu\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"The Nuxt Observability Stack: Tracing, Logging, and PM2 Metrics\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#website\",\"url\":\"https:\\\/\\\/softwareproduction.eu\\\/\",\"name\":\"Softwareproduction\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/softwareproduction.eu\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#organization\",\"name\":\"Munir Husseini\",\"url\":\"https:\\\/\\\/softwareproduction.eu\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/softwareproduction.eu\\\/wordpress\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/softwareproduction-logo-32.png\",\"contentUrl\":\"https:\\\/\\\/softwareproduction.eu\\\/wordpress\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/softwareproduction-logo-32.png\",\"width\":32,\"height\":32,\"caption\":\"Munir Husseini\"},\"image\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#\\\/schema\\\/logo\\\/image\\\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#\\\/schema\\\/person\\\/fec48f54713e1bd117640fb9b748802f\",\"name\":\"Munir Husseini\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/b07845732d4d7bddfc43e608ae6662d564a14b35706dfae0c9610071d978f54e?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/b07845732d4d7bddfc43e608ae6662d564a14b35706dfae0c9610071d978f54e?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/b07845732d4d7bddfc43e608ae6662d564a14b35706dfae0c9610071d978f54e?s=96&d=mm&r=g\",\"caption\":\"Munir Husseini\"},\"sameAs\":[\"https:\\\/\\\/softwareproduction.eu\\\/\"],\"url\":\"https:\\\/\\\/softwareproduction.eu\\\/?author=1\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"The Nuxt Observability Stack: Tracing, Logging, and PM2 Metrics - Scalable Web Production","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/softwareproduction.eu\/?p=24","og_locale":"en_US","og_type":"article","og_title":"The Nuxt Observability Stack: Tracing, Logging, and PM2 Metrics - Scalable Web Production","og_description":"Migrating from a legacy application to a modern Nuxt 4 stack is not just about new frameworks and better performance numbers. The real shift is moving from reactive firefighting to proactive observability \u2014 knowing what is slow, why it is slow, and how the platform behaves under real load. This observability stack has three pillars: [&hellip;]","og_url":"https:\/\/softwareproduction.eu\/?p=24","og_site_name":"Scalable Web Production","article_published_time":"2026-06-06T21:48:52+00:00","article_modified_time":"2026-06-06T23:45:30+00:00","author":"Munir Husseini","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Munir Husseini","Est. reading time":"13 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/softwareproduction.eu\/?p=24#article","isPartOf":{"@id":"https:\/\/softwareproduction.eu\/?p=24"},"author":{"name":"Munir Husseini","@id":"https:\/\/softwareproduction.eu\/#\/schema\/person\/fec48f54713e1bd117640fb9b748802f"},"headline":"The Nuxt Observability Stack: Tracing, Logging, and PM2 Metrics","datePublished":"2026-06-06T21:48:52+00:00","dateModified":"2026-06-06T23:45:30+00:00","mainEntityOfPage":{"@id":"https:\/\/softwareproduction.eu\/?p=24"},"wordCount":1561,"commentCount":0,"publisher":{"@id":"https:\/\/softwareproduction.eu\/#organization"},"image":{"@id":"https:\/\/softwareproduction.eu\/?p=24#primaryimage"},"thumbnailUrl":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/18-full-observability-stack.jpg","articleSection":["Advanced Web App with Nuxt and .NET"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/softwareproduction.eu\/?p=24#respond"]}]},{"@type":"WebPage","@id":"https:\/\/softwareproduction.eu\/?p=24","url":"https:\/\/softwareproduction.eu\/?p=24","name":"The Nuxt Observability Stack: Tracing, Logging, and PM2 Metrics - Scalable Web Production","isPartOf":{"@id":"https:\/\/softwareproduction.eu\/#website"},"primaryImageOfPage":{"@id":"https:\/\/softwareproduction.eu\/?p=24#primaryimage"},"image":{"@id":"https:\/\/softwareproduction.eu\/?p=24#primaryimage"},"thumbnailUrl":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/18-full-observability-stack.jpg","datePublished":"2026-06-06T21:48:52+00:00","dateModified":"2026-06-06T23:45:30+00:00","breadcrumb":{"@id":"https:\/\/softwareproduction.eu\/?p=24#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/softwareproduction.eu\/?p=24"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/softwareproduction.eu\/?p=24#primaryimage","url":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/18-full-observability-stack.jpg","contentUrl":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/18-full-observability-stack.jpg","width":1880,"height":1099},{"@type":"BreadcrumbList","@id":"https:\/\/softwareproduction.eu\/?p=24#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/softwareproduction.eu\/"},{"@type":"ListItem","position":2,"name":"The Nuxt Observability Stack: Tracing, Logging, and PM2 Metrics"}]},{"@type":"WebSite","@id":"https:\/\/softwareproduction.eu\/#website","url":"https:\/\/softwareproduction.eu\/","name":"Softwareproduction","description":"","publisher":{"@id":"https:\/\/softwareproduction.eu\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/softwareproduction.eu\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/softwareproduction.eu\/#organization","name":"Munir Husseini","url":"https:\/\/softwareproduction.eu\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/softwareproduction.eu\/#\/schema\/logo\/image\/","url":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/05\/softwareproduction-logo-32.png","contentUrl":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/05\/softwareproduction-logo-32.png","width":32,"height":32,"caption":"Munir Husseini"},"image":{"@id":"https:\/\/softwareproduction.eu\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/softwareproduction.eu\/#\/schema\/person\/fec48f54713e1bd117640fb9b748802f","name":"Munir Husseini","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/b07845732d4d7bddfc43e608ae6662d564a14b35706dfae0c9610071d978f54e?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/b07845732d4d7bddfc43e608ae6662d564a14b35706dfae0c9610071d978f54e?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/b07845732d4d7bddfc43e608ae6662d564a14b35706dfae0c9610071d978f54e?s=96&d=mm&r=g","caption":"Munir Husseini"},"sameAs":["https:\/\/softwareproduction.eu\/"],"url":"https:\/\/softwareproduction.eu\/?author=1"}]}},"jetpack_featured_media_url":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/18-full-observability-stack.jpg","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/posts\/24","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=24"}],"version-history":[{"count":10,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/posts\/24\/revisions"}],"predecessor-version":[{"id":245,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/posts\/24\/revisions\/245"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/media\/244"}],"wp:attachment":[{"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=24"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=24"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=24"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}