(
State(worker_ctx): State<S>,
Path(LogsParams { name_or_identity }): Path<LogsParams>,
Query(LogsQuery { num_lines, follow }): Query<LogsQuery>,
Extension(auth): Extension<SpacetimeAu
| 618 | } |
| 619 | |
| 620 | pub async fn logs<S>( |
| 621 | State(worker_ctx): State<S>, |
| 622 | Path(LogsParams { name_or_identity }): Path<LogsParams>, |
| 623 | Query(LogsQuery { num_lines, follow }): Query<LogsQuery>, |
| 624 | Extension(auth): Extension<SpacetimeAuth>, |
| 625 | ) -> axum::response::Result<impl IntoResponse> |
| 626 | where |
| 627 | S: ControlStateDelegate + NodeDelegate + Authorization, |
| 628 | { |
| 629 | // You should not be able to read the logs from a database that you do not own |
| 630 | // so, unless you are the owner, this will fail. |
| 631 | |
| 632 | let database_identity: Identity = name_or_identity.resolve(&worker_ctx).await?; |
| 633 | let database = worker_ctx_find_database(&worker_ctx, &database_identity) |
| 634 | .await? |
| 635 | .ok_or(NO_SUCH_DATABASE)?; |
| 636 | |
| 637 | worker_ctx |
| 638 | .authorize_action(auth.claims.identity, database.database_identity, Action::ViewModuleLogs) |
| 639 | .await?; |
| 640 | |
| 641 | fn log_err(database: Identity) -> impl Fn(&io::Error) { |
| 642 | move |e| warn!("error serving module logs for database {database}: {e:#}") |
| 643 | } |
| 644 | |
| 645 | let body = match worker_ctx.leader(database.id).await { |
| 646 | Ok(host) => { |
| 647 | let module = host.module().await.map_err(log_and_500)?; |
| 648 | let logs = module.database_logger().tail(num_lines, follow).await.map_err(|e| { |
| 649 | warn!("database={database_identity} unable to tail logs: {e:#}"); |
| 650 | (StatusCode::SERVICE_UNAVAILABLE, "Logs are temporarily not available") |
| 651 | })?; |
| 652 | Body::from_stream(logs.inspect_err(log_err(database_identity))) |
| 653 | } |
| 654 | Err(e) if e.is_misdirected() => return Err(MISDIRECTED.into()), |
| 655 | // If this is the right node for the current or last-known leader, |
| 656 | // we may still be able to serve logs from disk, |
| 657 | // even if we can't get hold of a running [ModuleHost]. |
| 658 | Err(e) => { |
| 659 | warn!("could not obtain leader host for module logs: {e:#}"); |
| 660 | let Some(replica) = worker_ctx.get_leader_replica_by_database(database.id).await else { |
| 661 | return Err(MISDIRECTED.into()); |
| 662 | }; |
| 663 | let logs_dir = worker_ctx.module_logs_dir(replica.id); |
| 664 | if !logs_dir.0.try_exists().map_err(log_and_500)? { |
| 665 | // Probably an in-memory database. |
| 666 | // Logs may become available at a later time. |
| 667 | return Err(( |
| 668 | StatusCode::SERVICE_UNAVAILABLE, |
| 669 | "Database is not running and doesn't have persistent logs", |
| 670 | ) |
| 671 | .into()); |
| 672 | } |
| 673 | let logs = DatabaseLogger::read_latest_on_disk(logs_dir, num_lines); |
| 674 | Body::from_stream(logs.inspect_err(log_err(database_identity))) |
| 675 | } |
| 676 | }; |
| 677 |
nothing calls this directly
no test coverage detected
searching dependent graphs…