Skip to content

feat(logs): Introduce logs command #2664

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Aug 6, 2025
Merged

feat(logs): Introduce logs command #2664

merged 20 commits into from
Aug 6, 2025

Conversation

vgrozdanic
Copy link
Member

Add a new logs subcommand to the Sentry CLI, allowing users to manage and query logs from their organizations.

This includes a list command to fetch and display log entries with options for filtering and pagination.

Implemented common arguments for logs and integrated them into the command structure. Also added integration tests for the new functionality.

Part of #2661

Live tailing will be done as a part of separate PR to make this a bit easier to review, it's already very large change.

@vgrozdanic vgrozdanic requested review from szokeasaurusrex and a team as code owners July 30, 2025 09:14
@vgrozdanic vgrozdanic force-pushed the vg/add-logs-command branch 2 times, most recently from a960c3e to 8d7b8a6 Compare July 30, 2025 09:49
cursor[bot]

This comment was marked as outdated.

@vgrozdanic vgrozdanic force-pushed the vg/add-logs-command branch from 8d7b8a6 to 7f3cb0f Compare July 30, 2025 09:58
@vgrozdanic vgrozdanic force-pushed the vg/add-logs-command branch from 7f3cb0f to ff53f3e Compare July 30, 2025 10:16
cursor[bot]

This comment was marked as outdated.

Copy link
Member

@szokeasaurusrex szokeasaurusrex left a comment

Choose a reason for hiding this comment

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

Code mostly looks reasonable to me, thanks! I have left a few mostly minor suggestions.

My main concern, though, and why I am requesting changes, is that I could not get this to run locally. I tried using the following command, but was getting a 400 error. I suspect that perhaps the endpoint we are hitting only takes project IDs, not slugs.

➜  sentry-cli git:(vg/add-logs-command) cargo run logs list --org sentry -p sentry               
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.46s
     Running `target/debug/sentry-cli logs list --org sentry -p sentry`
error: API request failed

Caused by:
    sentry reported an error: Invalid project parameter. Values must be numbers. (http status: 400)

cursor[bot]

This comment was marked as outdated.

@vgrozdanic vgrozdanic force-pushed the vg/add-logs-command branch from 7a99398 to 57ca614 Compare July 31, 2025 08:36
@JoshFerge
Copy link
Member

love this! what do you think about our other data types? what would be the best interface? something like this?

sentry logs --live
sentry traces -live
sentry issues --live
sentry all --live # eventually a command to live log all sentry telemetry

@vgrozdanic
Copy link
Member Author

vgrozdanic commented Jul 31, 2025

love this! what do you think about our other data types? what would be the best interface? something like this?

sentry logs --live sentry traces -live sentry issues --live sentry all --live # eventually a command to live log all sentry telemetry

@JoshFerge Yep, something like that we already had in mind, i have a second draft PR that builds on top of the work from this one (#2666) and adds live tailing only for logs, but after that it should be relatively straight forward to support tailing for other data types.

EDIT: not sure how "easy" it is to support traces and all, but issues should be no problem 😅

Copy link
Member

@szokeasaurusrex szokeasaurusrex left a comment

Choose a reason for hiding this comment

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

Looking better, still have a couple questions though

? success
+------------------+---------------------+----------+----------------------+---------------------+
| Item ID | Timestamp | Severity | Message | Trace |
+------------------+---------------------+----------+----------------------+---------------------+
Copy link
Member

Choose a reason for hiding this comment

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

This output looks wrong. I would expect it to be the same as logs-list-with-data.trycmd. Why is it different?

Copy link
Member Author

Choose a reason for hiding this comment

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

Somehow this test wasn't even running because i have screwed it up, now all of the tests should be fixed

src/api/mod.rs Outdated

impl Dataset {
/// Returns the string representation of the dataset
pub fn as_str(&self) -> &'static str {
Copy link
Member

Choose a reason for hiding this comment

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

I believe you can make this private

Suggested change
pub fn as_str(&self) -> &'static str {
fn as_str(&self) -> &'static str {

@szokeasaurusrex
Copy link
Member

Hey @vgrozdanic, I just attempted to test this PR locally, but it still does not seem to work as expected.

I wanted to list these three logs.

image

To list the logs, I ran the following command.

➜  sentry-cli git:(vg/add-logs-command) cargo run -- logs list -p 4508694563782656 --org sentry-sdks
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.48s
     Running `target/debug/sentry-cli logs list -p 4508694563782656 --org sentry-sdks`
No logs found

As you can see, the command says no logs were found, even though we should have found the three logs we can see on the page in Sentry.

If you rerun the same command using --log-level=debug to observe the HTTP requests and responses, you will see that the server is returning the logs. So, it seems like the problem lies with the code not deserializing the server's response correctly.

➜  sentry-cli git:(vg/add-logs-command) cargo run -- logs list -p 4508694563782656 --org sentry-sdks --log-level=debug
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.42s
     Running `target/debug/sentry-cli logs list -p 4508694563782656 --org sentry-sdks --log-level=debug`
  INFO    2025-08-01 11:27:37.152130 +02:00 Loaded config from /Users/dszoke/Development/sentry-cli/.sentryclirc
  DEBUG   2025-08-01 11:27:37.152813 +02:00 sentry-cli version: 2.50.2, platform: "darwin", architecture: "arm64"
  INFO    2025-08-01 11:27:37.154792 +02:00 sentry-cli was invoked with the following command line: "target/debug/sentry-cli" "logs" "list" "-p" "4508694563782656" "--org" "sentry-sdks" "--log-level=debug"
  DEBUG   2025-08-01 11:27:37.155904 +02:00 request GET https://sentry.io/api/0/organizations/sentry-sdks/events/?dataset=ourlogs&field=sentry.item_id&field=trace&field=severity&field=timestamp&field=message&project=4508694563782656&per_page=100&statsPeriod=1h&sort=-timestamp
  DEBUG   2025-08-01 11:27:37.155945 +02:00 using token authentication
  DEBUG   2025-08-01 11:27:37.155990 +02:00 retry number 0, max retries: 5
  DEBUG   2025-08-01 11:27:37.232294 +02:00 > GET /api/0/organizations/sentry-sdks/events/?dataset=ourlogs&field=sentry.item_id&field=trace&field=severity&field=timestamp&field=message&project=4508694563782656&per_page=100&statsPeriod=1h&sort=-timestamp HTTP/1.1
  DEBUG   2025-08-01 11:27:37.232335 +02:00 > Host: sentry.io
  DEBUG   2025-08-01 11:27:37.232343 +02:00 > Accept: */*
  DEBUG   2025-08-01 11:27:37.232349 +02:00 > Connection: TE
  DEBUG   2025-08-01 11:27:37.232356 +02:00 > TE: gzip
  DEBUG   2025-08-01 11:27:37.232392 +02:00 > User-Agent: sentry-cli/2.50.2
  DEBUG   2025-08-01 11:27:37.234180 +02:00 > Authorization: Bearer sntryu_c***
  DEBUG   2025-08-01 11:27:37.660784 +02:00 < HTTP/1.1 200 OK
  DEBUG   2025-08-01 11:27:37.660834 +02:00 < server: nginx
  DEBUG   2025-08-01 11:27:37.660855 +02:00 < date: Fri, 01 Aug 2025 09:27:37 GMT
  DEBUG   2025-08-01 11:27:37.660867 +02:00 < content-type: application/json
  DEBUG   2025-08-01 11:27:37.660877 +02:00 < vary: Accept-Encoding,Accept-Language, Cookie
  DEBUG   2025-08-01 11:27:37.660893 +02:00 < link: <https://sentry.io/api/0/organizations/sentry-sdks/events/?dataset=ourlogs&field=sentry.item_id&field=trace&field=severity&field=timestamp&field=message&project=4508694563782656&per_page=100&statsPeriod=1h&sort=-timestamp&cursor=0:0:1>; rel="previous"; results="false"; cursor="0:0:1", <https://sentry.io/api/0/organizations/sentry-sdks/events/?dataset=ourlogs&field=sentry.item_id&field=trace&field=severity&field=timestamp&field=message&project=4508694563782656&per_page=100&statsPeriod=1h&sort=-timestamp&cursor=0:100:0>; rel="next"; results="false"; cursor="0:100:0"
  DEBUG   2025-08-01 11:27:37.660904 +02:00 < allow: GET, HEAD, OPTIONS
  DEBUG   2025-08-01 11:27:37.660913 +02:00 < access-control-allow-methods: GET, HEAD, OPTIONS
  DEBUG   2025-08-01 11:27:37.660952 +02:00 < access-control-allow-headers: X-Sentry-Auth, X-Requested-With, Origin, Accept, Content-Type, Authentication, Authorization, Content-Encoding, sentry-trace, baggage, X-CSRFToken
  DEBUG   2025-08-01 11:27:37.660963 +02:00 < access-control-expose-headers: X-Sentry-Error, X-Sentry-Direct-Hit, X-Hits, X-Max-Hits, Endpoint, Retry-After, Link
  DEBUG   2025-08-01 11:27:37.660972 +02:00 < access-control-allow-origin: *
  DEBUG   2025-08-01 11:27:37.660981 +02:00 < x-sentry-rate-limit-remaining: 996
  DEBUG   2025-08-01 11:27:37.660989 +02:00 < x-sentry-rate-limit-limit: 1000
  DEBUG   2025-08-01 11:27:37.660997 +02:00 < x-sentry-rate-limit-reset: 1754040600
  DEBUG   2025-08-01 11:27:37.661006 +02:00 < x-sentry-rate-limit-concurrentremaining: 14
  DEBUG   2025-08-01 11:27:37.661014 +02:00 < x-sentry-rate-limit-concurrentlimit: 15
  DEBUG   2025-08-01 11:27:37.661023 +02:00 < content-language: en
  DEBUG   2025-08-01 11:27:37.661032 +02:00 < reporting-endpoints: default=https://sentry.my.sentry.io/api/0/reporting-api-experiment/
  DEBUG   2025-08-01 11:27:37.661040 +02:00 < x-frame-options: deny
  DEBUG   2025-08-01 11:27:37.661048 +02:00 < x-content-type-options: nosniff
  DEBUG   2025-08-01 11:27:37.661057 +02:00 < x-xss-protection: 1; mode=block
  DEBUG   2025-08-01 11:27:37.661075 +02:00 < content-security-policy: base-uri 'none'; default-src 'none'; connect-src 'self' *.algolia.net *.algolianet.com *.algolia.io sentry.io *.sentry.io s1.sentry-cdn.com o1.ingest.sentry.io api2.amplitude.com app.pendo.io data.pendo.io reload.getsentry.net t687h3m0nh65.statuspage.io sentry.zendesk.com ekr.zdassets.com maps.googleapis.com; frame-src app.pendo.io demo.arcade.software js.stripe.com sentry.io 'self'; style-src * 'unsafe-inline'; frame-ancestors 'self' *.sentry.io; script-src 'self' 'unsafe-inline' 'report-sample' s1.sentry-cdn.com js.sentry-cdn.com browser.sentry-cdn.com statuspage-production.s3.amazonaws.com static.zdassets.com aui-cdn.atlassian.com connect-cdn.atl-paas.net js.stripe.com 'strict-dynamic' cdn.pendo.io data.pendo.io pendo-io-static.storage.googleapis.com pendo-static-5634074999128064.storage.googleapis.com; media-src *; object-src 'none'; img-src * blob: data:; worker-src blob:; font-src * data:; report-uri https://o1.ingest.sentry.io/api/54785/security/?sentry_key=f724a8a027db45f5b21507e7142ff78e&sentry_release=81196427e84449af89ec87c74201522e28d79b6b
  DEBUG   2025-08-01 11:27:37.661091 +02:00 < x-envoy-upstream-service-time: 296
  DEBUG   2025-08-01 11:27:37.661100 +02:00 < x-sentry-proxy-url: http://10.2.0.67:8999/api/0/organizations/sentry-sdks/events/
  DEBUG   2025-08-01 11:27:37.661131 +02:00 < x-envoy-attempt-count: 1
  DEBUG   2025-08-01 11:27:37.661147 +02:00 < x-served-by: frontend-default-6b97b7cfd8-59bxm
  DEBUG   2025-08-01 11:27:37.661159 +02:00 < strict-transport-security: max-age=31536000; includeSubDomains; preload
  DEBUG   2025-08-01 11:27:37.661168 +02:00 < via: 1.1 google
  DEBUG   2025-08-01 11:27:37.661177 +02:00 < Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
  DEBUG   2025-08-01 11:27:37.661187 +02:00 < Transfer-Encoding: chunked
  DEBUG   2025-08-01 11:27:37.663707 +02:00 response status: 200
  DEBUG   2025-08-01 11:27:37.663749 +02:00 body: {"data":[{"sentry.item_id":"d1befd406c0620b5047757aef3649801","trace":"a2328953e06b4f7100bcea441c357259","severity":"error","timestamp":"2025-08-01T09:25:48+00:00","message":"Database connection failed","tags[sentry.timestamp_precise,number]":1.75404034824715e+18},{"sentry.item_id":"975283f1a4430cac5a7c57aef3649801","trace":"a2328953e06b4f7100bcea441c357259","severity":"info","timestamp":"2025-08-01T09:25:48+00:00","message":"Hello, world!","tags[sentry.timestamp_precise,number]":1.754040348247139e+18},{"sentry.item_id":"9959d7e2eb1b929dd77556aef3649801","trace":"a2328953e06b4f7100bcea441c357259","severity":"info","timestamp":"2025-08-01T09:25:48+00:00","message":"Hello, world!","tags[sentry.timestamp_precise,number]":1.7540403482469248e+18}],"meta":{"fields":{"sentry.item_id":"string","trace":"string","severity":"string","timestamp":"string","message":"string","tags[sentry.timestamp_precise,number]":"number"},"units":{"sentry.item_id":null,"trace":null,"severity":null,"timestamp":null,"message":null,"tags[sentry.timestamp_precise,number]":null},"isMetricsData":false,"isMetricsExtractedData":false,"tips":{},"datasetReason":"unchanged","dataset":"ourlogs","dataScanned":"full","accuracy":{"confidence":[{},{},{}]}},"confidence":[{},{},{}]}
No logs found
  INFO    2025-08-01 11:27:37.664836 +02:00 Skipping update nagger update check

@vgrozdanic
Copy link
Member Author

Hey @vgrozdanic, I just attempted to test this PR locally, but it still does not seem to work as expected.

I wanted to list these three logs.

image To list the logs, I ran the following command.
➜  sentry-cli git:(vg/add-logs-command) cargo run -- logs list -p 4508694563782656 --org sentry-sdks
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.48s
     Running `target/debug/sentry-cli logs list -p 4508694563782656 --org sentry-sdks`
No logs found

As you can see, the command says no logs were found, even though we should have found the three logs we can see on the page in Sentry.

If you rerun the same command using --log-level=debug to observe the HTTP requests and responses, you will see that the server is returning the logs. So, it seems like the problem lies with the code not deserializing the server's response correctly.

➜  sentry-cli git:(vg/add-logs-command) cargo run -- logs list -p 4508694563782656 --org sentry-sdks --log-level=debug
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.42s
     Running `target/debug/sentry-cli logs list -p 4508694563782656 --org sentry-sdks --log-level=debug`
  INFO    2025-08-01 11:27:37.152130 +02:00 Loaded config from /Users/dszoke/Development/sentry-cli/.sentryclirc
  DEBUG   2025-08-01 11:27:37.152813 +02:00 sentry-cli version: 2.50.2, platform: "darwin", architecture: "arm64"
  INFO    2025-08-01 11:27:37.154792 +02:00 sentry-cli was invoked with the following command line: "target/debug/sentry-cli" "logs" "list" "-p" "4508694563782656" "--org" "sentry-sdks" "--log-level=debug"
  DEBUG   2025-08-01 11:27:37.155904 +02:00 request GET https://sentry.io/api/0/organizations/sentry-sdks/events/?dataset=ourlogs&field=sentry.item_id&field=trace&field=severity&field=timestamp&field=message&project=4508694563782656&per_page=100&statsPeriod=1h&sort=-timestamp
  DEBUG   2025-08-01 11:27:37.155945 +02:00 using token authentication
  DEBUG   2025-08-01 11:27:37.155990 +02:00 retry number 0, max retries: 5
  DEBUG   2025-08-01 11:27:37.232294 +02:00 > GET /api/0/organizations/sentry-sdks/events/?dataset=ourlogs&field=sentry.item_id&field=trace&field=severity&field=timestamp&field=message&project=4508694563782656&per_page=100&statsPeriod=1h&sort=-timestamp HTTP/1.1
  DEBUG   2025-08-01 11:27:37.232335 +02:00 > Host: sentry.io
  DEBUG   2025-08-01 11:27:37.232343 +02:00 > Accept: */*
  DEBUG   2025-08-01 11:27:37.232349 +02:00 > Connection: TE
  DEBUG   2025-08-01 11:27:37.232356 +02:00 > TE: gzip
  DEBUG   2025-08-01 11:27:37.232392 +02:00 > User-Agent: sentry-cli/2.50.2
  DEBUG   2025-08-01 11:27:37.234180 +02:00 > Authorization: Bearer sntryu_c***
  DEBUG   2025-08-01 11:27:37.660784 +02:00 < HTTP/1.1 200 OK
  DEBUG   2025-08-01 11:27:37.660834 +02:00 < server: nginx
  DEBUG   2025-08-01 11:27:37.660855 +02:00 < date: Fri, 01 Aug 2025 09:27:37 GMT
  DEBUG   2025-08-01 11:27:37.660867 +02:00 < content-type: application/json
  DEBUG   2025-08-01 11:27:37.660877 +02:00 < vary: Accept-Encoding,Accept-Language, Cookie
  DEBUG   2025-08-01 11:27:37.660893 +02:00 < link: <https://sentry.io/api/0/organizations/sentry-sdks/events/?dataset=ourlogs&field=sentry.item_id&field=trace&field=severity&field=timestamp&field=message&project=4508694563782656&per_page=100&statsPeriod=1h&sort=-timestamp&cursor=0:0:1>; rel="previous"; results="false"; cursor="0:0:1", <https://sentry.io/api/0/organizations/sentry-sdks/events/?dataset=ourlogs&field=sentry.item_id&field=trace&field=severity&field=timestamp&field=message&project=4508694563782656&per_page=100&statsPeriod=1h&sort=-timestamp&cursor=0:100:0>; rel="next"; results="false"; cursor="0:100:0"
  DEBUG   2025-08-01 11:27:37.660904 +02:00 < allow: GET, HEAD, OPTIONS
  DEBUG   2025-08-01 11:27:37.660913 +02:00 < access-control-allow-methods: GET, HEAD, OPTIONS
  DEBUG   2025-08-01 11:27:37.660952 +02:00 < access-control-allow-headers: X-Sentry-Auth, X-Requested-With, Origin, Accept, Content-Type, Authentication, Authorization, Content-Encoding, sentry-trace, baggage, X-CSRFToken
  DEBUG   2025-08-01 11:27:37.660963 +02:00 < access-control-expose-headers: X-Sentry-Error, X-Sentry-Direct-Hit, X-Hits, X-Max-Hits, Endpoint, Retry-After, Link
  DEBUG   2025-08-01 11:27:37.660972 +02:00 < access-control-allow-origin: *
  DEBUG   2025-08-01 11:27:37.660981 +02:00 < x-sentry-rate-limit-remaining: 996
  DEBUG   2025-08-01 11:27:37.660989 +02:00 < x-sentry-rate-limit-limit: 1000
  DEBUG   2025-08-01 11:27:37.660997 +02:00 < x-sentry-rate-limit-reset: 1754040600
  DEBUG   2025-08-01 11:27:37.661006 +02:00 < x-sentry-rate-limit-concurrentremaining: 14
  DEBUG   2025-08-01 11:27:37.661014 +02:00 < x-sentry-rate-limit-concurrentlimit: 15
  DEBUG   2025-08-01 11:27:37.661023 +02:00 < content-language: en
  DEBUG   2025-08-01 11:27:37.661032 +02:00 < reporting-endpoints: default=https://sentry.my.sentry.io/api/0/reporting-api-experiment/
  DEBUG   2025-08-01 11:27:37.661040 +02:00 < x-frame-options: deny
  DEBUG   2025-08-01 11:27:37.661048 +02:00 < x-content-type-options: nosniff
  DEBUG   2025-08-01 11:27:37.661057 +02:00 < x-xss-protection: 1; mode=block
  DEBUG   2025-08-01 11:27:37.661075 +02:00 < content-security-policy: base-uri 'none'; default-src 'none'; connect-src 'self' *.algolia.net *.algolianet.com *.algolia.io sentry.io *.sentry.io s1.sentry-cdn.com o1.ingest.sentry.io api2.amplitude.com app.pendo.io data.pendo.io reload.getsentry.net t687h3m0nh65.statuspage.io sentry.zendesk.com ekr.zdassets.com maps.googleapis.com; frame-src app.pendo.io demo.arcade.software js.stripe.com sentry.io 'self'; style-src * 'unsafe-inline'; frame-ancestors 'self' *.sentry.io; script-src 'self' 'unsafe-inline' 'report-sample' s1.sentry-cdn.com js.sentry-cdn.com browser.sentry-cdn.com statuspage-production.s3.amazonaws.com static.zdassets.com aui-cdn.atlassian.com connect-cdn.atl-paas.net js.stripe.com 'strict-dynamic' cdn.pendo.io data.pendo.io pendo-io-static.storage.googleapis.com pendo-static-5634074999128064.storage.googleapis.com; media-src *; object-src 'none'; img-src * blob: data:; worker-src blob:; font-src * data:; report-uri https://o1.ingest.sentry.io/api/54785/security/?sentry_key=f724a8a027db45f5b21507e7142ff78e&sentry_release=81196427e84449af89ec87c74201522e28d79b6b
  DEBUG   2025-08-01 11:27:37.661091 +02:00 < x-envoy-upstream-service-time: 296
  DEBUG   2025-08-01 11:27:37.661100 +02:00 < x-sentry-proxy-url: http://10.2.0.67:8999/api/0/organizations/sentry-sdks/events/
  DEBUG   2025-08-01 11:27:37.661131 +02:00 < x-envoy-attempt-count: 1
  DEBUG   2025-08-01 11:27:37.661147 +02:00 < x-served-by: frontend-default-6b97b7cfd8-59bxm
  DEBUG   2025-08-01 11:27:37.661159 +02:00 < strict-transport-security: max-age=31536000; includeSubDomains; preload
  DEBUG   2025-08-01 11:27:37.661168 +02:00 < via: 1.1 google
  DEBUG   2025-08-01 11:27:37.661177 +02:00 < Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
  DEBUG   2025-08-01 11:27:37.661187 +02:00 < Transfer-Encoding: chunked
  DEBUG   2025-08-01 11:27:37.663707 +02:00 response status: 200
  DEBUG   2025-08-01 11:27:37.663749 +02:00 body: {"data":[{"sentry.item_id":"d1befd406c0620b5047757aef3649801","trace":"a2328953e06b4f7100bcea441c357259","severity":"error","timestamp":"2025-08-01T09:25:48+00:00","message":"Database connection failed","tags[sentry.timestamp_precise,number]":1.75404034824715e+18},{"sentry.item_id":"975283f1a4430cac5a7c57aef3649801","trace":"a2328953e06b4f7100bcea441c357259","severity":"info","timestamp":"2025-08-01T09:25:48+00:00","message":"Hello, world!","tags[sentry.timestamp_precise,number]":1.754040348247139e+18},{"sentry.item_id":"9959d7e2eb1b929dd77556aef3649801","trace":"a2328953e06b4f7100bcea441c357259","severity":"info","timestamp":"2025-08-01T09:25:48+00:00","message":"Hello, world!","tags[sentry.timestamp_precise,number]":1.7540403482469248e+18}],"meta":{"fields":{"sentry.item_id":"string","trace":"string","severity":"string","timestamp":"string","message":"string","tags[sentry.timestamp_precise,number]":"number"},"units":{"sentry.item_id":null,"trace":null,"severity":null,"timestamp":null,"message":null,"tags[sentry.timestamp_precise,number]":null},"isMetricsData":false,"isMetricsExtractedData":false,"tips":{},"datasetReason":"unchanged","dataset":"ourlogs","dataScanned":"full","accuracy":{"confidence":[{},{},{}]}},"confidence":[{},{},{}]}
No logs found
  INFO    2025-08-01 11:27:37.664836 +02:00 Skipping update nagger update check

@szokeasaurusrex rust skill issue - i have found a bug in my code, working on a fix... I have found a couple of more mistakes in my code, but soon will push the updated version. Sorry to bother you this much, as you can tell, i haven't written much rust so far 😅

@szokeasaurusrex
Copy link
Member

szokeasaurusrex commented Aug 1, 2025

It's okay, no worries @vgrozdanic!

But please, once you have fixed the problem, make sure you have a test case which validates that the command handles this type of response correctly

@vgrozdanic vgrozdanic force-pushed the vg/add-logs-command branch from a038c26 to 2ea949b Compare August 1, 2025 10:18
Copy link
Member

@szokeasaurusrex szokeasaurusrex left a comment

Choose a reason for hiding this comment

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

Looks good, still some comments to address before merge (but I'll take care of it)

Comment on lines 114 to 115
let logs_to_show = &logs[..args.max_rows.min(logs.len())];
for log in logs_to_show {
Copy link
Member

Choose a reason for hiding this comment

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

Let's please rewrite this way, it is easier to read (this is what I meant with my take suggestion in Slack)

Suggested change
let logs_to_show = &logs[..args.max_rows.min(logs.len())];
for log in logs_to_show {
for log in logs.iter().take(args.max_rows) {

? success
Manage and query logs in Sentry. This command provides access to log entries.

Usage: sentry-cli logs [OPTIONS] [COMMAND]
Copy link
Member

Choose a reason for hiding this comment

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

As [EXE] adds .exe on Windows, but evaluates to an empty string on other platforms, I believe that we can remove the logs-help-windows.trycmd if we make this edit here

Suggested change
Usage: sentry-cli logs [OPTIONS] [COMMAND]
Usage: sentry-cli[EXE] logs [OPTIONS] [COMMAND]

Comment on lines 40 to 43
#[cfg(not(windows))]
manager.register_trycmd_test("logs/logs-help.trycmd");
#[cfg(windows)]
manager.register_trycmd_test("logs/logs-help-windows.trycmd");
Copy link
Member

Choose a reason for hiding this comment

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

If you make the requested change to logs-help.trycmd, this should work

Suggested change
#[cfg(not(windows))]
manager.register_trycmd_test("logs/logs-help.trycmd");
#[cfg(windows)]
manager.register_trycmd_test("logs/logs-help-windows.trycmd");
manager.register_trycmd_test("logs/logs-help.trycmd");

#[command(about = LIST_ABOUT)]
#[command(long_about = format!("{LIST_ABOUT}. \
Query and filter log entries from your Sentry projects. \
Supports filtering by time period, log level, and custom queries."))]
Copy link
Member

Choose a reason for hiding this comment

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

Filtering by time period does not seem to be possible

Copy link
Member

@szokeasaurusrex szokeasaurusrex left a comment

Choose a reason for hiding this comment

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

While playing around with this PR, I noticed that it appears we only support displaying logs from the last hour. We should probably instead allow the time period to be user-configured, either in this PR or in a future PR.

If we add the ability to user-configure the time period in a future PR, this PR should state clearly in the help text that for now, only logs from the past hour are displayed

cursor: None,
query,
per_page: Some(args.max_rows),
stats_period: Some("1h"),
Copy link
Member

Choose a reason for hiding this comment

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

This value should either be user-configurable, or we should state in the command's --help text that we only display logs from the last hour

Copy link
Member

Choose a reason for hiding this comment

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

So since the way we query the API is descending by time, and we limit to 100 rows via the per_page parameter, we will always get the 100 most recent logs. I did a quick test on our API with 90d and 1h, and the timing is not really different, so I'd propose defaulting to 90d and not making it user-configurable for now, that would always give the user the most recent logs. What do you think about that @szokeasaurusrex?

Copy link
Member

Choose a reason for hiding this comment

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

@shellmayr, is the stats_period even needed in the query string? If not, let's just delete the struct field completely and not send the parameter, assuming that the default time period the server assumes is reasonable. Otherwise, I would just hardcode the 90d default

Copy link
Member

Choose a reason for hiding this comment

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

@szokeasaurusrex I think it defaults to 14 days if not explicitly set

Copy link
Member

Choose a reason for hiding this comment

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

Hmm okay, I guess either the server default or a 90 day default is reasonable in that case

Copy link
Member

Choose a reason for hiding this comment

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

I'd rather it be explicit, so the CLI doesn't rely on current, unspecified behaviour - that way the behaviour should stay the same if something changes in the API configuration.

Copy link
Member

Choose a reason for hiding this comment

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

Very wise

cursor[bot]

This comment was marked as outdated.

Copy link
Member

@szokeasaurusrex szokeasaurusrex left a comment

Choose a reason for hiding this comment

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

I think this needs a couple final polishing touches but otherwise looks close to done! Thanks Simon

src/api/mod.rs Outdated
Comment on lines 1448 to 1455
/// Query string to filter events
pub query: Option<&'a str>,
/// Number of events per page (default: 100)
pub per_page: Option<usize>,
/// Time period for stats (default: "1h")
pub stats_period: Option<&'a str>,
/// Sort order (default: "-timestamp")
pub sort: Option<&'a str>,
Copy link
Member

Choose a reason for hiding this comment

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

For now, all of these fields appear to always be being set to a Some variant, so the default value never ends up being used.

Therefore, I would like to see the struct fields be made non-optional, so we can also get rid of the defaults in the code, and make the API simpler. We can always later make the fields optional, as needed.

Suggested change
/// Query string to filter events
pub query: Option<&'a str>,
/// Number of events per page (default: 100)
pub per_page: Option<usize>,
/// Time period for stats (default: "1h")
pub stats_period: Option<&'a str>,
/// Sort order (default: "-timestamp")
pub sort: Option<&'a str>,
/// Query string to filter events
pub query: &'a str,
/// Number of events per page
pub per_page: usize,
/// Time period for stats
pub stats_period: &'a str,
/// Sort order
pub sort: &'a str,

Copy link
Member

@shellmayr shellmayr Aug 6, 2025

Choose a reason for hiding this comment

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

@szokeasaurusrex Calls to the endpoint don't need to have a query, stats_period, per_page or sort. What are we achieving by making them non-optional?

Copy link
Member

@szokeasaurusrex szokeasaurusrex Aug 6, 2025

Choose a reason for hiding this comment

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

I want to keep the Sentry CLI code simple. Making these optional means we have to add code paths to Sentry CLI, which for now, are not being used, because we always set the query, stats_period, per_page, and sort. Rust, however, still forces us to handle the cases of these being None (even though, in reality, they never are None).

As a result, we have code paths, which are completely unused, to handle the None cases. In the case of the query, we only provide it if it is there. For stats_period, per_page, and sort, we have default values defined, which are never used. From reading the code, however, it is not immediately clear that these default values are never used.

The problem with keeping them as Option is just that it increases complexity and it makes it harder to see where the actual values get defined. I understand that the values are optional from the server perspective, but that absolutely does not mean that we need to make them optional in the CLI's API

Copy link
Member

Choose a reason for hiding this comment

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

@shellmayr As an alternative, I would also be okay with just hardcoding the defaults and removing these fields from the struct completely. I'm okay with whatever you think is more reasonable, just want to avoid the Option when its adding currently unnecessary complexity by handling cases which never occur.

params.push(format!("statsPeriod={}", self.stats_period.unwrap_or("1h")));

params.push(format!("sort={}", self.sort.unwrap_or("-timestamp")));

Copy link

Choose a reason for hiding this comment

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

Bug: Default Parameter Inclusion Bug

The FetchEventsOptions::to_query_params method incorrectly includes per_page, statsPeriod, and sort parameters in the query string with default values, even when their Option fields are None. This is due to the use of unwrap_or(), which forces their inclusion, contradicting their intended optional behavior and the API's design. In contrast, cursor and query parameters are correctly added only when explicitly provided.

Fix in Cursor Fix in Web

params.push(format!("project={}", QueryArg(self.project_id)));
params.push(format!("query={}", QueryArg(self.query)));
params.push(format!("per_page={}", self.per_page));
params.push(format!("statsPeriod={}", QueryArg(self.stats_period)));
Copy link

Choose a reason for hiding this comment

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

Bug: Query Parameter Inclusion Bug

The FetchEventsOptions::to_query_params method unconditionally adds a query= parameter to the URL, even when self.query is empty. This is inconsistent with existing API patterns (e.g., list_organization_project_issues) and causes integration test failures, as test mocks expect the query parameter to be omitted when no query is provided. The query parameter should only be included if self.query is not empty.

Fix in Cursor Fix in Web

Copy link
Member

@szokeasaurusrex szokeasaurusrex left a comment

Choose a reason for hiding this comment

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

🚀 🚀 🚀

Thanks for bearing with me @vgrozdanic and @shellmayr!

.mock_endpoint(
MockEndpointBuilder::new(
"GET",
"/api/0/organizations/wat-org/events/?dataset=logs&field=sentry.item_id&field=trace&field=severity&field=timestamp&field=message&project=12345&per_page=100&statsPeriod=90d&sort=-timestamp"
Copy link
Member

Choose a reason for hiding this comment

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

Hmm @shellmayr, I might have been wrong about the query not needing to be optional. Looks like this test is failing now because the CLI is adding an empty query=& parameter to this URL, causing the 501 error

Copy link
Member

Choose a reason for hiding this comment

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

I think that should be fine, fixing that test now

Copy link
Member

Choose a reason for hiding this comment

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

I think it is the same problem with the test above it

@shellmayr shellmayr merged commit 150beb6 into master Aug 6, 2025
31 checks passed
@shellmayr shellmayr deleted the vg/add-logs-command branch August 6, 2025 11:42
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.

4 participants