Skip to content

feat: add logs:edge-functions command and historical log support#7944

Open
biilmann wants to merge 12 commits intomainfrom
logs-for-agents
Open

feat: add logs:edge-functions command and historical log support#7944
biilmann wants to merge 12 commits intomainfrom
logs-for-agents

Conversation

@biilmann
Copy link
Member

Summary

  • Add logs:edge-functions command for streaming and historical edge function logs
  • Add --from/--to date options to logs:function and logs:edge-functions for historical log fetching via the analytics API
  • Add --deploy-id option to both commands to look up functions from a specific deploy
  • Accept function ID (not just name) as argument to logs:function
  • Show CLI log command hints in deploy output when functions or edge functions are deployed
  • Create shared log-api.ts utility for date parsing, REST API fetching, and log formatting

New files

  • src/commands/logs/log-api.ts — shared utilities for historical log fetching
  • src/commands/logs/edge-functions.ts — edge function log handler
  • tests/integration/commands/logs/edge-functions.test.ts — tests for logs:edge-functions

Test plan

  • npm run typecheck — no new type errors
  • npm exec vitest -- run tests/integration/commands/logs/ — all 15 tests pass
  • npm exec vitest -- run tests/integration/commands/deploy/ — 39/40 pass (1 pre-existing failure)
  • Manual: ./bin/run.js logs:function --help shows --from, --to, --deploy-id options
  • Manual: ./bin/run.js logs:edge-functions --help shows all options
  • Manual: Deploy with functions and verify CLI hints appear in output

🤖 Generated with Claude Code

@biilmann biilmann requested a review from a team as a code owner February 13, 2026 07:43
@github-actions
Copy link

github-actions bot commented Feb 13, 2026

📊 Benchmark results

Comparing with 00b18b7

  • Dependency count: 1,080 ⬇️ 0.09% decrease vs. 00b18b7
  • Package size: 330 MB ⬇️ 5.53% decrease vs. 00b18b7
  • Number of ts-expect-error directives: 362 (no change)

@biilmann biilmann requested a review from a team as a code owner February 13, 2026 18:01
biilmann and others added 10 commits February 23, 2026 11:11
Add edge function log streaming and historical log fetching for both
function and edge function logs. The deploy command output now shows
CLI hints for accessing logs when functions are deployed.

- Add `logs:edge-functions` command with WebSocket streaming and
  historical mode via `--from`/`--to`
- Add `--from`/`--to` date options to `logs:function` for historical
  log fetching via analytics API
- Add `--deploy-id` option to `logs:function` and `logs:edge-functions`
  to look up functions from a specific deploy
- Accept function ID (not just name) in `logs:function`
- Show function/edge-function CLI log hints in deploy output
- Create shared `log-api.ts` utility for date parsing, API fetching,
  and log formatting

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The deploy API response doesn't include an edge_functions_count field.
Thread the edgeFunctionsCount from deploySite's local hashing step
through to the deploy output so edge function log hints display
correctly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…g sanitization

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
…g sanitization

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
…g sanitization

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add proper type assertions to eliminate unsafe-any and non-null
assertion lint errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Feb 23, 2026

📝 Walkthrough

Summary by CodeRabbit

Release Notes

  • New Features

    • Added logs:edge-functions command to stream and view edge function logs.
    • Enhanced logs:function command with --deploy-id flag for deploy-scoped function selection and --from/--to flags for historical log retrieval.
    • Deploy command now displays deployed functions and edge function information in output.
  • Documentation

    • Updated logs command documentation with edge-functions subcommand details and usage examples.

Walkthrough

This pull request adds edge function logging support to the Netlify CLI. It introduces a new logs:edge-functions command that supports both real-time WebSocket streaming and historical log fetching via REST API. The logs:function command is extended with --deploy-id, --from, and --to flags for deploy-scoped and time-bounded queries. A new utility module handles historical log retrieval, parsing, and formatting. The deploy command is enhanced to track deployed functions and edge function presence for user-facing output and JSON serialization. Documentation and integration tests are added for the new functionality.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: adding a logs:edge-functions command and historical log support across both the new and existing logs:function command.
Description check ✅ Passed The description is directly related to the changeset, providing clear details about new commands, options, files, and testing performed.
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 unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch logs-for-agents

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

♻️ Duplicate comments (1)
tests/integration/commands/logs/edge-functions.test.ts (1)

199-216: URL substring match is less precise than new URL().hostname used in functions.test.ts.

The url.includes('analytics.services.netlify.com') check can match the hostname appearing anywhere in the URL string (e.g., as a query parameter). The companion functions.test.ts uses new URL(url).hostname for the equivalent mock routing, which is more precise. Aligning on the hostname approach would be consistent.

♻️ Proposed fix (align with functions.test.ts approach)
-    const spyFetch = vi.fn().mockImplementation((url: string) => {
-      if (url.includes('analytics.services.netlify.com')) {
+    const spyFetch = vi.fn().mockImplementation((url: string) => {
+      const parsedUrl = new URL(url)
+      if (parsedUrl.hostname === 'analytics.services.netlify.com') {
-      const analyticsCall = spyFetch.mock.calls.find((args: string[]) =>
-        args[0].includes('analytics.services.netlify.com'),
+      const analyticsCall = spyFetch.mock.calls.find((args: string[]) => {
+        const parsedUrl = new URL(args[0])
+        return parsedUrl.hostname === 'analytics.services.netlify.com'
+      }
       )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/integration/commands/logs/edge-functions.test.ts` around lines 199 -
216, The host-matching in the fetch mock is too broad; update the
mockImplementation in spyFetch to parse the input URL and compare the hostname
exactly (e.g., use new URL(url).hostname === 'analytics.services.netlify.com')
instead of url.includes(...), keeping the same return behavior for that host;
ensure you reference the existing spyFetch/mockImplementation and originalFetch
variables and keep global.fetch assignment and subsequent assertions
(spyWebsocket, program.parseAsync, spyFetch.mock.calls) unchanged.
🧹 Nitpick comments (4)
src/commands/logs/log-api.ts (1)

12-32: Add a request timeout to prevent hangs.

Consider aborting the request after a reasonable timeout to avoid indefinitely blocking the CLI on slow or stalled networks.

♻️ Suggested update with AbortController
 export async function fetchHistoricalLogs({
   url,
   accessToken,
 }: {
   url: string
   accessToken: string
 }): Promise<unknown> {
-  const response = await fetch(url, {
-    method: 'GET',
-    headers: {
-      Authorization: `Bearer ${accessToken}`,
-    },
-  })
+  const controller = new AbortController()
+  const timeout = setTimeout(() => controller.abort(), 30_000)
+  const response = await fetch(url, {
+    method: 'GET',
+    headers: {
+      Authorization: `Bearer ${accessToken}`,
+    },
+    signal: controller.signal,
+  })
 
-  if (!response.ok) {
-    const errorData = (await response.json().catch(() => ({}))) as { error?: string }
-    throw new Error(errorData.error ?? `HTTP ${response.status.toString()}: ${response.statusText}`)
-  }
+  try {
+    if (!response.ok) {
+      const errorData = (await response.json().catch(() => ({}))) as { error?: string }
+      throw new Error(errorData.error ?? `HTTP ${response.status.toString()}: ${response.statusText}`)
+    }
+  } finally {
+    clearTimeout(timeout)
+  }
 
   return response.json()
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/logs/log-api.ts` around lines 12 - 32, The fetchHistoricalLogs
function can hang on slow networks; create an AbortController, pass
controller.signal into fetch, and start a timeout (e.g., 10s) with setTimeout
that calls controller.abort() to cancel the request; after fetch completes
clearTimeout to avoid leaks and handle AbortError by throwing a clear
timeout-specific Error (or rethrow) so the CLI doesn't hang; update
fetchHistoricalLogs to use the controller/signal and proper cleanup around the
existing response.ok/error handling and response.json call.
src/commands/logs/edge-functions.ts (1)

27-27: Redundant .toString()CLI_LOG_LEVEL_CHOICES_STRING is an array.

CLI_LOG_LEVEL_CHOICES_STRING is LOG_LEVELS_LIST.map(...) — an array. Template literals coerce arrays to strings automatically (same as calling .toString()), so the explicit call is redundant. functions.ts omits it consistently.

♻️ Proposed fix
-    log(`Invalid log level. Choices are:${CLI_LOG_LEVEL_CHOICES_STRING.toString()}`)
+    log(`Invalid log level. Choices are:${CLI_LOG_LEVEL_CHOICES_STRING}`)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/logs/edge-functions.ts` at line 27, The template literal in the
log call redundantly calls .toString() on the array
CLI_LOG_LEVEL_CHOICES_STRING; remove the explicit .toString() so the line reads
log(`Invalid log level. Choices are:${CLI_LOG_LEVEL_CHOICES_STRING}`) and mirror
the style used in functions.ts, ensuring you update the usage in
edge-functions.ts where the log(...) invocation occurs.
src/commands/logs/functions.ts (1)

34-36: Missing siteId null guard — inconsistent with edge-functions.ts.

edge-functions.ts explicitly checks if (!siteId) and exits with a helpful message. functions.ts uses siteId! non-null assertions at lines 46/50, so if the project isn't linked, the URL at line 85 would contain undefined as a path segment.

🛡️ Proposed guard (mirrors edge-functions.ts)
  const { id: siteId } = site
+
+  if (!siteId) {
+    log('You must link a project before attempting to view function logs')
+    return
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/logs/functions.ts` around lines 34 - 36, Add the same null-guard
used in edge-functions.ts to functions.ts: check the extracted siteId (from
const { id: siteId } = site) before using it, and if it's falsy call
command.out.error with a helpful message and exit/return early so code never
uses siteId!; update any places currently using siteId! (e.g., the URL
construction around line ~85) to rely on the guarded, non-null siteId instead.
src/commands/deploy/deploy.ts (1)

815-818: Update CLI hint placeholder to reflect ID support.

The command now accepts function IDs, so the hint should not imply name-only usage.

Proposed tweak
-    'Function CLI': `netlify logs:function --deploy-id ${results.deployId} <function-name>`,
+    'Function CLI': `netlify logs:function --deploy-id ${results.deployId} <function-name-or-id>`,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/deploy/deploy.ts` around lines 815 - 818, The CLI hint in the
functionLogsData object currently suggests a name-only placeholder
("<function-name>") even though the command accepts function IDs; update the
'Function CLI' string to indicate both ID and name are supported (e.g., use
"<function-id|name>" or "<function-id or function-name>") so users aren’t
misled. Locate the functionLogsData constant and change the template string that
builds `netlify logs:function --deploy-id ${results.deployId} <function-name>`
to use the combined placeholder while keeping the --deploy-id
${results.deployId} interpolation intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/commands/logs.md`:
- Line 108: Replace the vision-based phrase "look up" in the `deploy-id` flag
description with a non-vision term; update the line that reads "`deploy-id`
(*string*) - Deploy ID to look up the function from" to use "find" or "retrieve"
(e.g., "Deploy ID to find the function" or "Deploy ID to retrieve the function")
so Vale's accessibility rule base.accessibilityVision is satisfied.
- Line 102: Update the user-facing argument description that currently uses the
raw token functionName so Vale stops flagging it; either enclose the token in
backticks (`functionName - Name or ID of the function to stream logs for`) or
reword it to plain English (e.g., "function name - Name or ID of the function to
stream logs for") in the logs command documentation entry where functionName
appears.

In `@src/commands/logs/functions.ts`:
- Around line 83-85: The URL currently interpolates branch and
resolvedFunctionName directly into the path (see variables branch and
resolvedFunctionName and the url constant), which breaks for branch names
containing slashes; update the URL construction to apply encodeURIComponent to
each path segment (encode branch and resolvedFunctionName) before interpolation
so the path segments are safely encoded while leaving query parameters
(fromMs/toMs) unchanged.

In `@src/commands/logs/log-api.ts`:
- Around line 56-71: printHistoricalLogs currently compares entry.level
lowercased against levelsToPrint as-is, so uppercase filters like "ERROR" will
miss matches; normalize the incoming levelsToPrint once (e.g., map to lowercase
and optionally trim, or build a Set of lowercased values) at the start of
printHistoricalLogs and then compare the lowercased entry.level against that
normalized collection (use the existing local variable level for the entry
comparison) to avoid case-sensitive mismatches.

In `@tests/integration/commands/logs/functions.test.ts`:
- Line 205: The three new tests (including the one with title 'should find
function by ID') use an empty destructured parameter "async ({}) =>", which
triggers Biome's noEmptyPattern lint error; edit each test's declaration (all
tests that currently use async ({}) =>) and replace the empty destructuring with
an empty parameter list "async () =>" so the test functions are declared as
async () => instead of async ({}) => (look for the test titles such as 'should
find function by ID' and the two other newly added tests that use the same
pattern).

---

Duplicate comments:
In `@tests/integration/commands/logs/edge-functions.test.ts`:
- Around line 199-216: The host-matching in the fetch mock is too broad; update
the mockImplementation in spyFetch to parse the input URL and compare the
hostname exactly (e.g., use new URL(url).hostname ===
'analytics.services.netlify.com') instead of url.includes(...), keeping the same
return behavior for that host; ensure you reference the existing
spyFetch/mockImplementation and originalFetch variables and keep global.fetch
assignment and subsequent assertions (spyWebsocket, program.parseAsync,
spyFetch.mock.calls) unchanged.

---

Nitpick comments:
In `@src/commands/deploy/deploy.ts`:
- Around line 815-818: The CLI hint in the functionLogsData object currently
suggests a name-only placeholder ("<function-name>") even though the command
accepts function IDs; update the 'Function CLI' string to indicate both ID and
name are supported (e.g., use "<function-id|name>" or "<function-id or
function-name>") so users aren’t misled. Locate the functionLogsData constant
and change the template string that builds `netlify logs:function --deploy-id
${results.deployId} <function-name>` to use the combined placeholder while
keeping the --deploy-id ${results.deployId} interpolation intact.

In `@src/commands/logs/edge-functions.ts`:
- Line 27: The template literal in the log call redundantly calls .toString() on
the array CLI_LOG_LEVEL_CHOICES_STRING; remove the explicit .toString() so the
line reads log(`Invalid log level. Choices are:${CLI_LOG_LEVEL_CHOICES_STRING}`)
and mirror the style used in functions.ts, ensuring you update the usage in
edge-functions.ts where the log(...) invocation occurs.

In `@src/commands/logs/functions.ts`:
- Around line 34-36: Add the same null-guard used in edge-functions.ts to
functions.ts: check the extracted siteId (from const { id: siteId } = site)
before using it, and if it's falsy call command.out.error with a helpful message
and exit/return early so code never uses siteId!; update any places currently
using siteId! (e.g., the URL construction around line ~85) to rely on the
guarded, non-null siteId instead.

In `@src/commands/logs/log-api.ts`:
- Around line 12-32: The fetchHistoricalLogs function can hang on slow networks;
create an AbortController, pass controller.signal into fetch, and start a
timeout (e.g., 10s) with setTimeout that calls controller.abort() to cancel the
request; after fetch completes clearTimeout to avoid leaks and handle AbortError
by throwing a clear timeout-specific Error (or rethrow) so the CLI doesn't hang;
update fetchHistoricalLogs to use the controller/signal and proper cleanup
around the existing response.ok/error handling and response.json call.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2453221 and bd52070.

📒 Files selected for processing (29)
  • docs/commands/agents.md
  • docs/commands/api.md
  • docs/commands/blobs.md
  • docs/commands/build.md
  • docs/commands/clone.md
  • docs/commands/completion.md
  • docs/commands/db.md
  • docs/commands/dev.md
  • docs/commands/env.md
  • docs/commands/functions.md
  • docs/commands/init.md
  • docs/commands/link.md
  • docs/commands/login.md
  • docs/commands/logs.md
  • docs/commands/open.md
  • docs/commands/recipes.md
  • docs/commands/sites.md
  • docs/commands/status.md
  • docs/commands/unlink.md
  • docs/commands/watch.md
  • docs/index.md
  • src/commands/deploy/deploy.ts
  • src/commands/logs/edge-functions.ts
  • src/commands/logs/functions.ts
  • src/commands/logs/index.ts
  • src/commands/logs/log-api.ts
  • src/utils/deploy/deploy-site.ts
  • tests/integration/commands/logs/edge-functions.test.ts
  • tests/integration/commands/logs/functions.test.ts

**Arguments**

- functionName - Name of the function to stream logs for
- functionName - Name or ID of the function to stream logs for
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Vale spellcheck warning: functionName in user-facing docs.

The vale linter flags functionName as a misspelling. Either wrap it in backticks or rephrase to "function name" for the argument description.

✏️ Suggested fix
-- functionName - Name or ID of the function to stream logs for
+- `function-name` - Name or ID of the function to stream logs for
📝 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
- functionName - Name or ID of the function to stream logs for
- `function-name` - Name or ID of the function to stream logs for
🧰 Tools
🪛 GitHub Check: lint-docs

[warning] 102-102:
[vale] reported by reviewdog 🐶
[base.spelling] Spellcheck: did you really mean 'functionName'?

Raw Output:
{"message": "[base.spelling] Spellcheck: did you really mean 'functionName'?", "location": {"path": "docs/commands/logs.md", "range": {"start": {"line": 102, "column": 3}}}, "severity": "WARNING"}

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/commands/logs.md` at line 102, Update the user-facing argument
description that currently uses the raw token functionName so Vale stops
flagging it; either enclose the token in backticks (`functionName - Name or ID
of the function to stream logs for`) or reword it to plain English (e.g.,
"function name - Name or ID of the function to stream logs for") in the logs
command documentation entry where functionName appears.

- `level` (*string*) - Log levels to stream. Choices are: trace, debug, info, warn, error, fatal
- `debug` (*boolean*) - Print debugging information
- `auth` (*string*) - Netlify auth token - can be used to run this command without logging in
- `deploy-id` (*string*) - Deploy ID to look up the function from
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Vale accessibility warning: avoid vision-based term "look".

Vale (base.accessibilityVision) flags "look up" as a vision-based term. Replace with "find" or "retrieve".

✏️ Suggested fix
-- `deploy-id` (*string*) - Deploy ID to look up the function from
+- `deploy-id` (*string*) - Deploy ID to find the function from
📝 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
- `deploy-id` (*string*) - Deploy ID to look up the function from
- `deploy-id` (*string*) - Deploy ID to find the function from
🧰 Tools
🪛 GitHub Check: lint-docs

[warning] 108-108:
[vale] reported by reviewdog 🐶
[base.accessibilityVision] Don't use vision-based terms. Use something inclusive like 'check', 'search', or 'examine' instead of 'look'.

Raw Output:
{"message": "[base.accessibilityVision] Don't use vision-based terms. Use something inclusive like 'check', 'search', or 'examine' instead of 'look'.", "location": {"path": "docs/commands/logs.md", "range": {"start": {"line": 108, "column": 41}}}, "severity": "WARNING"}

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/commands/logs.md` at line 108, Replace the vision-based phrase "look up"
in the `deploy-id` flag description with a non-vision term; update the line that
reads "`deploy-id` (*string*) - Deploy ID to look up the function from" to use
"find" or "retrieve" (e.g., "Deploy ID to find the function" or "Deploy ID to
retrieve the function") so Vale's accessibility rule base.accessibilityVision is
satisfied.

Comment on lines +83 to +85
const branch = siteInfo.build_settings?.repo_branch ?? 'main'

const url = `https://analytics.services.netlify.com/v2/sites/${siteId}/branch/${branch}/function_logs/${resolvedFunctionName}?from=${fromMs.toString()}&to=${toMs.toString()}`
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Unencoded path segments will break for feature-branch workflows.

branch and resolvedFunctionName are interpolated directly into the URL path. Git branches can legally contain / (e.g. feature/foo), which adds an unintended extra path segment and silently routes to the wrong endpoint. encodeURIComponent should be applied to each variable segment.

🐛 Proposed fix
-    const url = `https://analytics.services.netlify.com/v2/sites/${siteId}/branch/${branch}/function_logs/${resolvedFunctionName}?from=${fromMs.toString()}&to=${toMs.toString()}`
+    const url = `https://analytics.services.netlify.com/v2/sites/${encodeURIComponent(siteId!)}/branch/${encodeURIComponent(branch)}/function_logs/${encodeURIComponent(resolvedFunctionName)}?from=${fromMs.toString()}&to=${toMs.toString()}`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/logs/functions.ts` around lines 83 - 85, The URL currently
interpolates branch and resolvedFunctionName directly into the path (see
variables branch and resolvedFunctionName and the url constant), which breaks
for branch names containing slashes; update the URL construction to apply
encodeURIComponent to each path segment (encode branch and resolvedFunctionName)
before interpolation so the path segments are safely encoded while leaving query
parameters (fromMs/toMs) unchanged.

Comment on lines +56 to +71
export function printHistoricalLogs(data: unknown, levelsToPrint: string[]): void {
const entries = Array.isArray(data) ? (data as { timestamp?: string; level?: string; message?: string }[]) : []

if (entries.length === 0) {
log('No logs found for the specified time range')
return
}

for (const entry of entries) {
const level = (entry.level ?? '').toLowerCase()
if (levelsToPrint.length > 0 && !levelsToPrint.includes(level)) {
continue
}
log(formatLogEntry(entry))
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Normalize filter levels to avoid case-sensitive mismatches.

If callers pass uppercase levels (e.g., ERROR), filtering can drop valid entries. Normalize once before comparing.

🐛 Suggested fix
 export function printHistoricalLogs(data: unknown, levelsToPrint: string[]): void {
   const entries = Array.isArray(data) ? (data as { timestamp?: string; level?: string; message?: string }[]) : []
+  const normalizedLevels = levelsToPrint.map((level) => level.toLowerCase())
 
   if (entries.length === 0) {
     log('No logs found for the specified time range')
     return
   }
 
   for (const entry of entries) {
     const level = (entry.level ?? '').toLowerCase()
-    if (levelsToPrint.length > 0 && !levelsToPrint.includes(level)) {
+    if (normalizedLevels.length > 0 && !normalizedLevels.includes(level)) {
       continue
     }
     log(formatLogEntry(entry))
   }
 }
📝 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
export function printHistoricalLogs(data: unknown, levelsToPrint: string[]): void {
const entries = Array.isArray(data) ? (data as { timestamp?: string; level?: string; message?: string }[]) : []
if (entries.length === 0) {
log('No logs found for the specified time range')
return
}
for (const entry of entries) {
const level = (entry.level ?? '').toLowerCase()
if (levelsToPrint.length > 0 && !levelsToPrint.includes(level)) {
continue
}
log(formatLogEntry(entry))
}
}
export function printHistoricalLogs(data: unknown, levelsToPrint: string[]): void {
const entries = Array.isArray(data) ? (data as { timestamp?: string; level?: string; message?: string }[]) : []
const normalizedLevels = levelsToPrint.map((level) => level.toLowerCase())
if (entries.length === 0) {
log('No logs found for the specified time range')
return
}
for (const entry of entries) {
const level = (entry.level ?? '').toLowerCase()
if (normalizedLevels.length > 0 && !normalizedLevels.includes(level)) {
continue
}
log(formatLogEntry(entry))
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/logs/log-api.ts` around lines 56 - 71, printHistoricalLogs
currently compares entry.level lowercased against levelsToPrint as-is, so
uppercase filters like "ERROR" will miss matches; normalize the incoming
levelsToPrint once (e.g., map to lowercase and optionally trim, or build a Set
of lowercased values) at the start of printHistoricalLogs and then compare the
lowercased entry.level against that normalized collection (use the existing
local variable level for the entry comparison) to avoid case-sensitive
mismatches.

expect(spyLog).toHaveBeenCalledTimes(2)
})

test('should find function by ID', async ({}) => {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Biome noEmptyPattern errors — will fail lint CI.

All three new tests use async ({}) => with an empty destructuring pattern. Biome emits an error for each. Change to async () =>.

🐛 Proposed fix
-  test('should find function by ID', async ({}) => {
+  test('should find function by ID', async () => {
-  test('should look up function from deploy when --deploy is specified', async ({}) => {
+  test('should look up function from deploy when --deploy-id is specified', async () => {
-  test('should fetch historical logs when --from is specified', async ({}) => {
+  test('should fetch historical logs when --from is specified', async () => {

Also applies to: 231-231, 267-267

🧰 Tools
🪛 Biome (2.4.4)

[error] 205-205: Unexpected empty object pattern.

(lint/correctness/noEmptyPattern)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/integration/commands/logs/functions.test.ts` at line 205, The three new
tests (including the one with title 'should find function by ID') use an empty
destructured parameter "async ({}) =>", which triggers Biome's noEmptyPattern
lint error; edit each test's declaration (all tests that currently use async
({}) =>) and replace the empty destructuring with an empty parameter list "async
() =>" so the test functions are declared as async () => instead of async ({})
=> (look for the test titles such as 'should find function by ID' and the two
other newly added tests that use the same pattern).

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (2)
docs/commands/logs.md (2)

102-102: ⚠️ Potential issue | 🟡 Minor

Wrap functionName in code formatting (or rephrase).

functionName is still flagged by Vale spellcheck in user-facing docs. Use backticks or plain words (function name) to avoid the warning.

✏️ Suggested fix
-- functionName - Name or ID of the function to stream logs for
+- `functionName` - Name or ID of the function to stream logs for
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/commands/logs.md` at line 102, Update the docs/commands/logs.md line
that currently shows "functionName - Name or ID of the function to stream logs
for" by wrapping functionName in inline code backticks or rewording it to plain
words (e.g., `functionName` or "function name") so Vale spellcheck no longer
flags it; locate the occurrence of the identifier "functionName" in that file
and replace it with the chosen formatted/rephrased version.

106-106: ⚠️ Potential issue | 🟡 Minor

Replace vision-based phrase “look up” in flag description.

This wording is still flagged by Vale accessibility rules; switch to “find” or “retrieve”.

✏️ Suggested fix
-- `deploy-id` (*string*) - Deploy ID to look up the function from
+- `deploy-id` (*string*) - Deploy ID to find the function from
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/commands/logs.md` at line 106, The flag description for `deploy-id` uses
the vision-based phrase "look up"; update the description in the `deploy-id`
flag entry to use an alternative such as "find" or "retrieve" (e.g., change
"Deploy ID to look up the function from" to "Deploy ID to find the function" or
"Deploy ID to retrieve the function") so it no longer uses vision-based
language.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@docs/commands/logs.md`:
- Line 102: Update the docs/commands/logs.md line that currently shows
"functionName - Name or ID of the function to stream logs for" by wrapping
functionName in inline code backticks or rewording it to plain words (e.g.,
`functionName` or "function name") so Vale spellcheck no longer flags it; locate
the occurrence of the identifier "functionName" in that file and replace it with
the chosen formatted/rephrased version.
- Line 106: The flag description for `deploy-id` uses the vision-based phrase
"look up"; update the description in the `deploy-id` flag entry to use an
alternative such as "find" or "retrieve" (e.g., change "Deploy ID to look up the
function from" to "Deploy ID to find the function" or "Deploy ID to retrieve the
function") so it no longer uses vision-based language.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between bd52070 and a79f5d9.

📒 Files selected for processing (1)
  • docs/commands/logs.md

| Subcommand | description |
|:--------------------------- |:-----|
| [`logs:deploy`](/commands/logs#logsdeploy) | Stream the logs of deploys currently being built to the console |
| [`logs:edge-functions`](/commands/logs#logsedge-functions) | Stream netlify edge function logs to the console |
Copy link
Member

@eduardoboucas eduardoboucas Feb 26, 2026

Choose a reason for hiding this comment

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

I understand why this is plural and logs:function is singular, but it still itches! 😖

In the future, I think we could rename logs:function to logs:functions and start accepting multiple function names, since there's nothing stopping us from listening to different streams and interleaving them, just like we do with edge functions.

const functionLogsData: Record<string, string> = {
'Function logs': terminalLink(results.functionLogsUrl, results.functionLogsUrl, { fallback: false }),
'Edge function Logs': terminalLink(results.edgeFunctionLogsUrl, results.edgeFunctionLogsUrl, { fallback: false }),
'Function CLI': `netlify logs:function --deploy-id ${results.deployId} <function-name>`,
Copy link
Member

Choose a reason for hiding this comment

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

"Function CLI" feels a bit weird? How about:

- Functions logs
  - Web: https://(...)
  - CLI: `netlify logs:function (...)`
- Edge Functions logs
  - Web: https://(...)
  - CLI: `netlify logs:edge-functions (...)`

}
}

const ws = getWebSocket('wss://socketeer.services.netlify.com/edge-function/logs')
Copy link
Member

Choose a reason for hiding this comment

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

Side note: I hate that we're exposing this. We should set up a customer-facing domain name for this.

return ms
}

export async function fetchHistoricalLogs({
Copy link
Member

Choose a reason for hiding this comment

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

This method doesn't really have anything to do with fetching logs. It's just making a fetch call and returning the response. I would recommend either getting rid of it and making the call directly, or moving the URL generation over here. I would recommend the latter, since we're currently constructing the analytics endpoint in different places and we could centralise that.

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.

3 participants