diff --git a/CHANGELOG.md b/CHANGELOG.md index e141a21..d638238 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Include repository, process exit status, and stream outcome in Git fetch + failure diagnostics; log client-cancelled upload-pack streams at debug level. - Pace descendant fallback cycles with a one-minute refresh delay while retaining overlap and immediate baselines for changed frontiers. Move routine batch-completion bookkeeping to debug logs. diff --git a/docs/explanation/architecture.md b/docs/explanation/architecture.md index de39864..af3a0fd 100644 --- a/docs/explanation/architecture.md +++ b/docs/explanation/architecture.md @@ -204,6 +204,11 @@ pub async fn handle_receive_pack( See [`src/git/handlers.rs:22-98`](src/git/handlers.rs:22-98) for the info-refs implementation. +The upload-pack streaming task retains a repository tracing span. Unexpected +process failures include the exit status and stream outcome even when Git +produces no stderr. A disconnected HTTP client cancels the child and is logged +at debug level; the operation remains unsuccessful in clone metrics. + #### [`authorization.rs`](src/git/authorization.rs) - Push Validation **Core Logic:** diff --git a/src/git/handlers.rs b/src/git/handlers.rs index 6da5178..26b8e69 100644 --- a/src/git/handlers.rs +++ b/src/git/handlers.rs @@ -14,7 +14,7 @@ use std::time::Duration; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::sync::mpsc; use tokio::time::MissedTickBehavior; -use tracing::{debug, error, info, warn}; +use tracing::{debug, error, info, warn, Instrument}; use super::protocol::{GitService, PktLine}; use super::storage::{FamilyKey, FamilyWriteLease, LocalGitStorage}; @@ -509,9 +509,13 @@ pub async fn handle_upload_pack( // until EOF so the request future can return the HTTP body immediately. let (tx, rx) = mpsc::channel::, io::Error>>(STREAM_CHANNEL_DEPTH); - tokio::spawn(async move { - stream_upload_pack_output(git, stdout, stderr, tx, repo_lifecycle_guard, metrics).await; - }); + let span = tracing::info_span!("git_upload_pack", repository = %repo_path.display()); + tokio::spawn( + async move { + stream_upload_pack_output(git, stdout, stderr, tx, repo_lifecycle_guard, metrics).await; + } + .instrument(span), + ); Ok(streaming_response(GitService::UploadPack, rx)) } @@ -554,6 +558,12 @@ async fn stream_upload_pack_output( None => Vec::new(), }; + if matches!(pump_result, PumpResult::ClientDisconnected) { + record_git_operation(&metrics, "clone", "error"); + debug!(exit_status = %status, "Git upload-pack cancelled after client disconnected"); + return; + } + if !status.success() && matches!(pump_result, PumpResult::Eof { sent_stdout: false }) { record_git_operation(&metrics, "clone", "error"); let stderr_str = String::from_utf8_lossy(&stderr_output); @@ -575,7 +585,8 @@ async fn stream_upload_pack_output( stderr_str ); } else { - error!("Git upload-pack failed: {}", stderr_str); + error!(exit_status = %status, stream_outcome = ?pump_result, + stderr = %stderr_str, "Git upload-pack failed"); } // The streaming response headers have already been sent. If Git failed @@ -600,8 +611,10 @@ async fn stream_upload_pack_output( ); } else { error!( - "Git upload-pack failed after streaming stdout: {}", - stderr_str + exit_status = %status, + stream_outcome = ?pump_result, + stderr = %stderr_str, + "Git upload-pack failed after streaming stdout" ); } } else if matches!(pump_result, PumpResult::Eof { .. }) {