Skip to content

feat(cli): Add log:list CLI command - #6136

Open
shrutip90 wants to merge 3 commits into
mainfrom
sp/log-cli
Open

feat(cli): Add log:list CLI command#6136
shrutip90 wants to merge 3 commits into
mainfrom
sp/log-cli

Conversation

@shrutip90

@shrutip90 shrutip90 commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Add a log:list command similar to trace:list command for listing logs. Allows filtering on the following args:

bash-3.2$ genkit log:list --help
Usage: genkit log:list [options]

list logs

Options:
  -l, --limit <number>          limit the number of returned logs (default: "15")
  --trace-id <id>               filter by trace ID
  --span-id <id>                filter by span ID
  --severity <severity>         filter by severity (e.g., INFO, ERROR, WARNING)
  -f, --format <format>         output format (choices: "text", "jsonl", default: "text")
  --continuation-token <token>  continuation token for pagination
  -h, --help                    display help for command

Sample output:

bash-3.2$ genkit log:list
Error: listen EADDRINUSE: address already in use 127.0.0.1:4033
Found 15 logs:

ID:       cce5e07e-c686-4069-95da-efb18265f796
Severity: INFO
Time:     8/20/2026, 3:52:34 PM
Message:  Input[blockingMiddleware > generate, blockingMiddleware]
Attrs:    logging.googleapis.com/spanId=7f0e86e2c5652248, logging.googleapis.com/trace=projects/shruti-genkit-...
---
ID:       7956edb0-15ac-4a78-a419-fcf698326392
Severity: ERROR
Time:     8/20/2026, 3:52:34 PM

Checklist:

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new log:list command to the CLI, enabling users to retrieve and display logs with filtering options like limit, trace ID, span ID, severity, and pagination. It also updates the telemetry server, log store, and runtime manager to support querying logs by severity text and number, and fixes a minor pluralization issue in the trace:list command. Feedback on the changes suggests handling potential TypeError exceptions in formatBody when stringifying objects with circular references, validating the --limit option to ensure it is a positive integer, and URL-encoding the severityText filter parameter in the runtime manager to prevent query string corruption.

Comment thread genkit-tools/cli/src/commands/log-list.ts
Comment thread genkit-tools/cli/src/commands/log-list.ts
Comment thread genkit-tools/common/src/manager/manager.ts
@shrutip90
shrutip90 requested review from pavelgj and ssbushi August 21, 2026 16:34
@shrutip90

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new log:list CLI command to retrieve and filter logs, along with backend support in the telemetry server, file log store, and runtime manager to filter logs by severity. It also fixes a minor pluralization issue in the trace:list command. The review feedback highlights a few key issues: the /api/logs endpoint should extract and pass traceId and spanId query parameters to support standalone span filtering, the runtime manager needs to append spanId to the query when traceId is absent, and the CLI log formatter should safely check the type of log.timestamp to prevent a valid 0 timestamp from being incorrectly treated as falsy.

Comment on lines +221 to 234
const { limit, continuationToken, severityText, severityNumber } =
request.query;
response.json(
await params.logStore.list({
limit: limit ? Number.parseInt(limit.toString()) : 100,
continuationToken: continuationToken
? continuationToken.toString()
: undefined,
severityText: severityText ? severityText.toString() : undefined,
severityNumber: severityNumber
? Number.parseInt(severityNumber.toString())
: undefined,
})
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The /api/logs endpoint does not extract traceId or spanId from the query parameters. If a user filters logs by spanId without providing a traceId, the filter is silently ignored by the server. Extracting these query parameters and passing them to logStore.list ensures that filtering by spanId alone works correctly.

      const { limit, continuationToken, severityText, severityNumber, traceId, spanId } = 
        request.query;
      response.json(
        await params.logStore.list({
          limit: limit ? Number.parseInt(limit.toString()) : 100,
          continuationToken: continuationToken
            ? continuationToken.toString()
            : undefined,
          severityText: severityText ? severityText.toString() : undefined,
          severityNumber: severityNumber
            ? Number.parseInt(severityNumber.toString())
            : undefined,
          traceId: traceId ? traceId.toString() : undefined,
          spanId: spanId ? spanId.toString() : undefined,
        })
      );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Makes sense?

Comment on lines +164 to +167
if (filter?.severityNumber) {
if (query !== '') query += '&';
query += `severityNumber=${filter.severityNumber}`;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

When filter.traceId is not present but filter.spanId is, the request is routed to /api/logs. We need to append spanId to the query parameters so that the telemetry server can filter by it.

    if (filter?.severityNumber) {
      if (query !== '') query += '&';
      query += 'severityNumber=' + filter.severityNumber;
    }
    if (filter?.spanId && !filter?.traceId) {
      if (query !== '') query += '&';
      query += 'spanId=' + encodeURIComponent(filter.spanId);
    }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

+1. Can be simplified by treated these independently instead

Comment on lines +104 to +107
let time = 'unknown';
if (log.timestamp) {
time = new Date(log.timestamp).toLocaleString();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Checking log.timestamp as a truthy value can lead to a bug if the timestamp is 0 (which is a valid timestamp representing the epoch). It is safer to check if the type is a number or if it is not null/undefined.

Suggested change
let time = 'unknown';
if (log.timestamp) {
time = new Date(log.timestamp).toLocaleString();
}
let time = 'unknown';
if (typeof log.timestamp === 'number') {
time = new Date(log.timestamp).toLocaleString();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

+1

Comment on lines +43 to +44
.option('--trace-id <id>', 'filter by trace ID')
.option('--span-id <id>', 'filter by span ID')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What is the behaviour if neither of these are specified? Will the most recent logs be printed? Worth documenting, and also worth mentioning that while traceId and spanId are optional, they are recommended to be provided (at least traceId). Not sure if there's any use case where that is not true....

const response = await manager.listLogs(listRequest);

if (!response || !response.logs || response.logs.length === 0) {
logger.info('No logs found.');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Would this log to stdout? If so that may be distruptive in jsonl mode?

Comment on lines +104 to +107
let time = 'unknown';
if (log.timestamp) {
time = new Date(log.timestamp).toLocaleString();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

+1

Comment on lines +130 to +136
logger.info(
`To get the next page, use: --continuation-token ${response.continuationToken}`
);
} else {
console.log(
`\nTo get the next page, use: --continuation-token ${response.continuationToken}`
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Worth mentioning here and in trace-list that user should use log-get or trace-get to fetch the full log?

});
});

it('should output formatted logs without verbose flag', async () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
it('should output formatted logs without verbose flag', async () => {
it('should output formatted logs without format flag', async () => {

Comment on lines +164 to +167
if (filter?.severityNumber) {
if (query !== '') query += '&';
query += `severityNumber=${filter.severityNumber}`;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

+1. Can be simplified by treated these independently instead

Comment on lines +221 to 234
const { limit, continuationToken, severityText, severityNumber } =
request.query;
response.json(
await params.logStore.list({
limit: limit ? Number.parseInt(limit.toString()) : 100,
continuationToken: continuationToken
? continuationToken.toString()
: undefined,
severityText: severityText ? severityText.toString() : undefined,
severityNumber: severityNumber
? Number.parseInt(severityNumber.toString())
: undefined,
})
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Makes sense?

Comment on lines +46 to +47
'--severity <severity>',
'filter by severity (e.g., INFO, ERROR, WARNING)'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is this filtering to that exact severity or to and above? I think the latter is a common pattern for logs

traceId: z.string().optional(),
spanId: z.string().optional(),
severityText: z.string().optional(),
severityNumber: z.number().optional(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Curious, who is using severityNumber? The CLI does not seem to be using it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants