-
-
Notifications
You must be signed in to change notification settings - Fork 232
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
Changes from 1 commit
ff53f3e
57ca614
2ea949b
49ed900
c8e60c0
519d55c
a015ab1
d836bf4
43e98c8
1b5d554
a5b1bb0
b7d5973
10240ce
e7f9805
4d4f99a
ccef59d
74f8596
fa1dde7
6c64120
3291c36
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
use clap::Args; | ||
|
||
/// Common arguments for all logs subcommands. | ||
#[derive(Args)] | ||
vgrozdanic marked this conversation as resolved.
Show resolved
Hide resolved
|
||
pub(super) struct CommonLogsArgs { | ||
#[arg(short = 'o', long = "org")] | ||
#[arg(help = "The organization ID or slug.")] | ||
pub(super) org: Option<String>, | ||
|
||
#[arg(short = 'p', long = "project")] | ||
#[arg(help = "The project ID or slug.")] | ||
pub(super) project: Option<String>, | ||
} | ||
vgrozdanic marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
use anyhow::Result; | ||
use clap::Args; | ||
|
||
use crate::api::{Api, FetchEventsOptions}; | ||
use crate::config::Config; | ||
use crate::utils::formatting::Table; | ||
|
||
use super::common_args::CommonLogsArgs; | ||
|
||
/// Arguments for listing logs | ||
#[derive(Args)] | ||
pub(super) struct ListLogsArgs { | ||
#[command(flatten)] | ||
pub(super) common: CommonLogsArgs, | ||
|
||
#[arg(long = "max-rows")] | ||
#[arg(help = "Maximum number of rows to print.")] | ||
pub(super) max_rows: Option<usize>, | ||
|
||
#[arg(long = "per-page", default_value = "100")] | ||
#[arg(help = "Number of log entries per request (max 1000).")] | ||
pub(super) per_page: usize, | ||
vgrozdanic marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
#[arg(long = "query", default_value = "")] | ||
#[arg(help = "Query to filter logs. Example: \"level:error\"")] | ||
pub(super) query: String, | ||
vgrozdanic marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
#[arg(long = "live")] | ||
#[arg(help = "Live-tail logs (not implemented yet).")] | ||
pub(super) live: bool, | ||
vgrozdanic marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
cursor[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
pub(super) fn execute(args: ListLogsArgs) -> Result<()> { | ||
let config = Config::current(); | ||
let (default_org, default_project) = config.get_org_and_project_defaults(); | ||
|
||
let org = args.common.org.or(default_org).ok_or_else(|| { | ||
anyhow::anyhow!("No organization specified. Use --org or set a default in config.") | ||
})?; | ||
let project = args.common.project.or(default_project).ok_or_else(|| { | ||
anyhow::anyhow!("No project specified. Use --project or set a default in config.") | ||
})?; | ||
|
||
let api = Api::current(); | ||
|
||
let query = if args.query.is_empty() { | ||
None | ||
} else { | ||
Some(args.query.as_str()) | ||
}; | ||
let fields = [ | ||
"sentry.item_id", | ||
"trace", | ||
"severity", | ||
"timestamp", | ||
"message", | ||
]; | ||
vgrozdanic marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
let options = FetchEventsOptions { | ||
project_id: Some(&project), | ||
query, | ||
per_page: Some(args.per_page), | ||
stats_period: Some("1h"), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @shellmayr, is the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @szokeasaurusrex I think it defaults to 14 days if not explicitly set There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Very wise |
||
..Default::default() | ||
}; | ||
|
||
let logs = api | ||
.authenticated()? | ||
.fetch_organization_events(&org, "ourlogs", &fields, options)?; | ||
|
||
let mut table = Table::new(); | ||
table | ||
.title_row() | ||
.add("Item ID") | ||
.add("Timestamp") | ||
.add("Severity") | ||
.add("Message") | ||
.add("Trace"); | ||
vgrozdanic marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
let max_rows = std::cmp::min(logs.len(), args.max_rows.unwrap_or(usize::MAX)); | ||
vgrozdanic marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if let Some(logs) = logs.get(..max_rows) { | ||
for log in logs { | ||
let row = table.add_row(); | ||
row.add(&log.item_id) | ||
.add(&log.timestamp) | ||
.add(log.severity.as_deref().unwrap_or("")) | ||
.add(log.message.as_deref().unwrap_or("")) | ||
.add(log.trace.as_deref().unwrap_or("")); | ||
} | ||
} | ||
cursor[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if table.is_empty() { | ||
println!("No logs found"); | ||
} else { | ||
table.print(); | ||
} | ||
|
||
Ok(()) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
pub mod common_args; | ||
|
||
mod list; | ||
|
||
use self::list::ListLogsArgs; | ||
use super::derive_parser::{SentryCLI, SentryCLICommand}; | ||
use anyhow::Result; | ||
use clap::ArgMatches; | ||
use clap::{Args, Command, Parser as _, Subcommand}; | ||
|
||
const LIST_ABOUT: &str = "List logs from your organization"; | ||
|
||
#[derive(Args)] | ||
pub(super) struct LogsArgs { | ||
#[command(subcommand)] | ||
subcommand: LogsSubcommand, | ||
} | ||
|
||
#[derive(Subcommand)] | ||
#[command(about = "Manage logs in Sentry")] | ||
#[command(long_about = "Manage and query logs in Sentry. \ | ||
This command provides access to log entries and supports live-tailing functionality.")] | ||
vgrozdanic marked this conversation as resolved.
Show resolved
Hide resolved
|
||
enum LogsSubcommand { | ||
#[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."))] | ||
vgrozdanic marked this conversation as resolved.
Show resolved
Hide resolved
|
||
List(ListLogsArgs), | ||
} | ||
|
||
pub(super) fn make_command(command: Command) -> Command { | ||
LogsSubcommand::augment_subcommands(command) | ||
} | ||
|
||
pub(super) fn execute(_: &ArgMatches) -> Result<()> { | ||
let SentryCLICommand::Logs(LogsArgs { subcommand }) = SentryCLI::parse().command else { | ||
unreachable!("expected logs subcommand"); | ||
}; | ||
|
||
match subcommand { | ||
LogsSubcommand::List(args) => list::execute(args), | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
$ sentry-cli logs --help | ||
? success | ||
Manage logs in Sentry. | ||
|
||
Usage: sentry-cli[EXE] logs [OPTIONS] <COMMAND> | ||
|
||
Commands: | ||
list List logs from your organization. | ||
help Print this message or the help of the given subcommand(s) | ||
|
||
Options: | ||
-o, --org <ORG> | ||
The organization ID or slug. | ||
|
||
-p, --project <PROJECT> | ||
The project ID or slug. | ||
|
||
--live | ||
Live-tail logs (not implemented yet). | ||
|
||
--header <KEY:VALUE> | ||
Custom headers that should be attached to all requests | ||
in key:value format. | ||
|
||
--auth-token <AUTH_TOKEN> | ||
Use the given Sentry auth token. | ||
|
||
--log-level <LOG_LEVEL> | ||
Set the log output verbosity. [possible values: trace, debug, info, warn, error] | ||
|
||
--quiet | ||
Do not print any output while preserving correct exit code. This flag is currently | ||
implemented only for selected subcommands. | ||
|
||
[aliases: silent] | ||
|
||
-h, --help | ||
Print help |
shellmayr marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
$ sentry-cli logs list --org wat-org --project wat-project --max-rows 0 | ||
? success | ||
No logs found | ||
cursor[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
Uh oh!
There was an error while loading. Please reload this page.