Skip to content

Adds logs to identify apps behavior#1043

Merged
nicacioliveira merged 3 commits intomainfrom
ft/log-by-app
Apr 6, 2026
Merged

Adds logs to identify apps behavior#1043
nicacioliveira merged 3 commits intomainfrom
ft/log-by-app

Conversation

@hugo-ccabral
Copy link
Copy Markdown
Contributor

@hugo-ccabral hugo-ccabral commented Jan 31, 2026

Add App/Block Identification to Fetch Monitoring

Summary

This PR enhances the fetch monitoring capabilities by tracking which app and block (loader/action) initiated each outgoing HTTP request. This enables better observability and debugging of external API calls across the deco runtime.

Changes

1. Extended RequestContext with block identification (deco.ts)

  • Added blockId?: string to the RequestContext type
  • Added blockId getter to RequestContextBinder interface
  • Block context is now accessible via RequestContext.blockId

2. Block context binding in loader/action execution (blocks/utils.tsx)

  • Modified applyProps() to bind the resolverId as blockId to RequestContext
  • Uses RequestContext.bind() to ensure the context is available during the entire execution of the loader/action, including any fetch calls

3. Enhanced fetch monitoring (utils/patched_fetch.ts)

  • Event system: Added onFetch() subscriber API for custom monitoring
  • App extraction: Extracts app name from blockId (e.g., vtex/loaders/product/list.tsvtex)
  • OTEL structured logging: Logs all outgoing fetches with structured fields:
    • fetch.app - The app that made the call (e.g., "vtex", "shopify")
    • fetch.block_id - Full block identifier (e.g., "vtex/loaders/product/list.ts")
    • fetch.url - The URL being fetched
    • fetch.method - HTTP method (GET, POST, etc.)
    • fetch.status - Response status code (0 if request failed before response)
    • fetch.ok - Whether response was successful (2xx)
    • fetch.duration_ms - Request duration in milliseconds
    • fetch.error - Error message (only present on failed requests)
  • Error handling: Failed requests (network errors, timeouts, DNS failures) are logged with logger.error and include the error message

4. Exported new APIs (mod.ts)

  • Exported onFetch function for custom monitoring
  • Exported FetchEvent and FetchCompleteEvent types

How It Works

Request → Middleware → Loader/Action executes
                            ↓
                    applyProps() binds blockId to RequestContext
                            ↓
                    User code calls fetch()
                            ↓
                    Patched fetch reads RequestContext.blockId
                            ↓
                    Notifies listeners with app + block info
                            ↓
                    OTEL logger emits structured log

Example Log Output

Successful request

{
  "level": "INFO",
  "message": "outgoing fetch",
  "fetch.app": "vtex",
  "fetch.block_id": "vtex/loaders/product/productList.ts",
  "fetch.url": "https://api.vtex.com/products",
  "fetch.method": "GET",
  "fetch.status": 200,
  "fetch.ok": true,
  "fetch.duration_ms": 145
}

Failed request

{
  "level": "ERROR",
  "message": "outgoing fetch",
  "fetch.app": "vtex",
  "fetch.block_id": "vtex/loaders/product/productList.ts",
  "fetch.url": "https://api.vtex.com/products",
  "fetch.method": "GET",
  "fetch.status": 0,
  "fetch.ok": false,
  "fetch.duration_ms": 5023,
  "fetch.error": "connection timed out"
}

Custom Monitoring API

Users can subscribe to fetch events for custom monitoring:

import { onFetch } from "deco/mod.ts";

const unsubscribe = onFetch((event) => {
  // Send to custom metrics system
  myMetrics.record({
    app: event.app,
    block: event.blockId,
    duration: event.durationMs,
    status: event.status,
    error: event.error,
  });
});

Files Changed

  • deco.ts - Extended RequestContext type and interface
  • blocks/utils.tsx - Bind blockId in applyProps()
  • utils/patched_fetch.ts - Event system and OTEL logging
  • mod.ts - Export new APIs

Testing

  • Verify blockId is correctly propagated during loader execution
  • Verify blockId is correctly propagated during action execution
  • Verify nested loaders (via ctx.invoke) maintain correct blockId
  • Verify OTEL logs contain all structured fields
  • Verify onFetch subscribers receive events correctly
  • Verify failed requests are logged with error details

Summary by CodeRabbit

  • New Features
    • Fetch observability: outgoing fetches are recorded and logged with URL, method, status, duration, and associated block context.
    • Event API: subscribe to fetch completion events to receive telemetry for network calls (subscribe/unsubscribe support).
    • Execution-context binding: component executions preserve and associate the current block context for accurate attribution.
    • Context enhancement: request context now exposes an optional block identifier for better tracking.

@github-actions
Copy link
Copy Markdown
Contributor

Tagging Options

Should a new tag be published when this PR is merged?

  • 👍 for Patch 1.135.1 update
  • 🎉 for Minor 1.136.0 update
  • 🚀 for Major 2.0.0 update

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 31, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7ce0ab45-6693-4e15-9f6d-f28d00b4fb34

📥 Commits

Reviewing files that changed from the base of the PR and between 1f39cc0 and 7db4ca5.

📒 Files selected for processing (4)
  • blocks/utils.tsx
  • deco.ts
  • mod.ts
  • utils/patched_fetch.ts
✅ Files skipped from review due to trivial changes (1)
  • mod.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • deco.ts
  • utils/patched_fetch.ts
  • blocks/utils.tsx

📝 Walkthrough

Walkthrough

Adds block-level request context propagation (a blockId on RequestContext and binder, used to bind component execution) and a global fetch wrapper that emits typed fetch events via an onFetch listener API with a default OTEL logger.

Changes

Cohort / File(s) Summary
Context & Binder
deco.ts
Add optional blockId?: string to RequestContext, add `readonly blockId: string
Block execution binding
blocks/utils.tsx
Create fnCtx via fnContextFromHttpContext(ctx) and invoke component functions through RequestContext.bind(fnCtx, { blockId: ctx.resolverId }) instead of direct calls to ensure blockId is active during execution.
Fetch observability
utils/patched_fetch.ts
Introduce FetchEvent/FetchCompleteEvent types, onFetch subscribe API, and replace global fetch with an async wrapper that derives app/blockId/method/url, records timing/status/error, notifies listeners, and rethrows errors. Registers default OTEL logging listener.
Public exports
mod.ts
Export onFetch and types FetchEvent, FetchCompleteEvent from utils/patched_fetch.ts to the public runtime API.

Sequence Diagram

sequenceDiagram
    participant Component as "Component\n(rgba(70,130,180,0.5))"
    participant BlockUtils as "Block Utils\n(rgba(100,149,237,0.5))"
    participant PatchedFetch as "Patched Fetch\n(rgba(60,179,113,0.5))"
    participant Listeners as "Listeners\n(rgba(238,130,238,0.5))"
    participant OTEL as "OTEL Logger\n(rgba(255,165,0,0.5))"

    Component->>BlockUtils: execute component(fn, ctx)
    BlockUtils->>BlockUtils: fnCtx = fnContextFromHttpContext(ctx)\nbound = RequestContext.bind(fnCtx, { blockId: ctx.resolverId })
    BlockUtils->>PatchedFetch: call bound function (may trigger fetch)
    PatchedFetch->>PatchedFetch: derive app, blockId, url, method\nrecord startedAt
    PatchedFetch->>PatchedFetch: perform actual fetch (await)
    PatchedFetch->>Listeners: emit FetchCompleteEvent(url,status,ok,duration,error,blockId)
    Listeners->>OTEL: default listener logs structured fields
    PatchedFetch-->>Component: return result / rethrow error
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • mcandeia
  • guitavano

Poem

🐰 I bind the block before it hops,

so every fetch knows which garden it stops.
Timings and errors I gently relay,
OTEL brightens the path of the day.
A rabbit cheers: observability, hooray!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Adds logs to identify apps behavior' is vague and uses non-descriptive phrasing that obscures the actual change. Use a more specific title that captures the main change, such as 'Add fetch instrumentation to track originating app and block context' or 'Instrument fetch requests with app and block tracking'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ft/log-by-app

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

❤️ Share

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

Copy link
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: 3

🤖 Fix all issues with AI agents
In `@utils/patched_fetch.ts`:
- Around line 108-109: The computed abort signal is being overwritten when init
contains an explicit signal: undefined because the code uses { signal, ...init }
in the fetch call; change the call in patched_fetch (where fetcher is invoked)
to spread init first and then include the computed signal (e.g., { ...init,
signal }) so the computed signal wins and is preserved even if init.signal is
undefined.
- Around line 83-93: The current default log in the onFetch listener logs
event.url which can contain sensitive query strings; update the onFetch handler
to avoid logging full URLs by parsing event.url (use URL or equivalent) and log
only host and pathname (e.g., "fetch.host" and "fetch.path") or a redacted
version of the URL with query and hash removed, and include a clearly named
field like "fetch.redacted_url" instead of event.url; keep the other fields
(fetch.method, fetch.status, etc.) unchanged and ensure logger.info is updated
to use the redacted fields.
- Around line 96-122: The current globalThis.fetch override only calls
notifyListeners when fetcher resolves, so network errors/aborts are not emitted;
wrap the call to fetcher inside try/catch/finally in globalThis.fetch
(referencing globalThis.fetch, fetcher, RequestContext, listeners,
notifyListeners, Request) so that notifyListeners is always invoked: in the try
block capture the response; in catch record that there is no response and store
a sentinel status (e.g., 0) and ok=false and capture the error; in finally
compute duration and call notifyListeners with app, blockId, url, method,
startedAt, status (real or 0), ok, durationMs and optionally error details, then
rethrow the caught error so behavior is unchanged.

Comment thread utils/patched_fetch.ts
Comment thread utils/patched_fetch.ts Outdated
Comment thread utils/patched_fetch.ts Outdated
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 4 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="utils/patched_fetch.ts">

<violation number="1" location="utils/patched_fetch.ts:74">
P2: Iterating directly over the mutable `listeners` array is unsafe. If a listener unsubscribes synchronously during execution, the array is modified (spliced), which can cause subsequent listeners to be skipped. Iterate over a copy instead.</violation>

<violation number="2" location="utils/patched_fetch.ts:105">
P0: Constructing a `new Request(input, init)` consumes the body of `input` if it is a `Request` object. This causes the subsequent `fetcher(input)` call to fail or send an empty body for POST/PUT requests, as the body stream can only be read once. Additionally, `new Request()` throws errors on relative URLs, which are often valid in `fetch`.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread utils/patched_fetch.ts Outdated
Comment thread utils/patched_fetch.ts Outdated
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="utils/patched_fetch.ts">

<violation number="1" location="utils/patched_fetch.ts:113">
P2: The computed `signal` will be overwritten if `init` contains an explicit `signal: undefined` property. When spreading `{ signal, ...init }`, properties from `init` override earlier properties. Reverse the spread order to ensure the computed signal takes precedence.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread utils/patched_fetch.ts Outdated
@nicacioliveira nicacioliveira merged commit 22b1fdc into main Apr 6, 2026
3 checks passed
@nicacioliveira nicacioliveira deleted the ft/log-by-app branch April 6, 2026 16:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants