From 68e667533b2a525bcae2a211fc4e1b59c8f03fe9 Mon Sep 17 00:00:00 2001 From: outductor Date: Sun, 2 Aug 2026 09:29:10 +0900 Subject: [PATCH 1/2] =?UTF-8?q?refactor:=20Pyroscope=20=E3=81=AE=20applica?= =?UTF-8?q?tion=20=E5=90=8D=E3=82=92=20PYROSCOPE=5FAPPLICATION=5FNAME=20?= =?UTF-8?q?=E3=81=A7=E4=B8=8A=E6=9B=B8=E3=81=8D=E5=8F=AF=E8=83=BD=E3=81=AB?= =?UTF-8?q?=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit game-data-publisher の確立パターンと env の語彙を揃える Co-Authored-By: Claude Fable 5 --- server/src/main.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/src/main.rs b/server/src/main.rs index 536c92a..f3fc517 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -248,9 +248,12 @@ async fn main() { let _pyroscope_agent = std::env::var("PYROSCOPE_SERVER_ADDRESS") .ok() .and_then(|server_address| { + // application 名は game-data-publisher と同じく env で上書き可能にする + let application_name = std::env::var("PYROSCOPE_APPLICATION_NAME") + .unwrap_or_else(|_| "gachadata-server".to_owned()); let started = PyroscopeAgentBuilder::new( &server_address, - "gachadata-server", + &application_name, 100, "pyroscope-rs", env!("CARGO_PKG_VERSION"), From 3426032a77de5aa95d7da14f4dca623da49360aa Mon Sep 17 00:00:00 2001 From: outductor Date: Sun, 2 Aug 2026 14:13:08 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20span=20=E3=81=B8=E3=81=AE=E7=A7=98?= =?UTF-8?q?=E5=8C=BF=E6=83=85=E5=A0=B1=E6=BC=8F=E6=B4=A9=E3=82=92=E9=98=B2?= =?UTF-8?q?=E3=81=8E=20SIGTERM=20=E3=81=A7=E8=A6=B3=E6=B8=AC=E3=83=87?= =?UTF-8?q?=E3=83=BC=E3=82=BF=E3=82=92=20flush=20=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex レビュー (blocker + should-fix) 対応: - #[tracing::instrument] 3 箇所に skip(self) / skip(repository) を追加。 Debug 経由で MySQL パスワードとキャッシュ済み SQL dump 全体が span 属性としてトレース基盤へ送られていた - 多層防御として MySQL の Debug からパスワードを redact、 GachadataDump の Debug をバイト長のみに変更 - SIGTERM/Ctrl-C の graceful shutdown を追加し、終了前に Pyroscope の stop/shutdown と OTel provider の shutdown へ到達させる (従来は SIGTERM で flush 処理に一切到達しなかった) Co-Authored-By: Claude Fable 5 --- server/src/main.rs | 74 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 65 insertions(+), 9 deletions(-) diff --git a/server/src/main.rs b/server/src/main.rs index f3fc517..6ef4ac5 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -7,9 +7,18 @@ mod domain { use std::fmt::Debug; use std::time::SystemTime; - #[derive(Debug, Clone, Default)] + #[derive(Clone, Default)] pub struct GachadataDump(pub Bytes); + // dump の中身 (SQL 全文) を Debug 出力に含めない + impl Debug for GachadataDump { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("GachadataDump") + .field("len_bytes", &self.0.len()) + .finish() + } + } + #[derive(Debug, Clone, Default)] pub struct GachadataDumpWithTime { pub dump: GachadataDump, @@ -39,7 +48,9 @@ mod infra_repository_impls { } impl MySQLDumpConnection { - #[tracing::instrument] + // self を skip しないと Debug 経由で MySQL パスワードとキャッシュ済み + // dump 全体が span 属性としてトレース基盤へ送られる + #[tracing::instrument(skip(self))] pub async fn run_gachadata_dump(&self) -> anyhow::Result<()> { let MySQL { host: address, @@ -78,7 +89,8 @@ mod infra_repository_impls { #[async_trait::async_trait] impl GachaDataRepository for MySQLDumpConnection { - #[tracing::instrument] + // skip(self): run_gachadata_dump と同じ理由 + #[tracing::instrument(skip(self))] async fn update_gachadata(&self) -> anyhow::Result<()> { let is_after_more_than_quarter_hour = match self.dump.lock() { Ok(dump) => { @@ -112,7 +124,9 @@ mod presentation { use axum::http::StatusCode; use axum::response::{ErrorResponse, IntoResponse, Response, Result}; - #[tracing::instrument] + // skip(repository): Debug 経由で MySQL パスワードとキャッシュ済み dump が + // span 属性に入るのを防ぐ + #[tracing::instrument(skip(repository))] pub async fn get_gachadata_handler( State(repository): State, ) -> Result { @@ -167,7 +181,7 @@ mod config { pub port: u16, } - #[derive(Debug, Clone, Deserialize)] + #[derive(Clone, Deserialize)] pub struct MySQL { pub host: String, pub port: u16, @@ -175,6 +189,18 @@ mod config { pub password: String, } + // パスワードを Debug 出力に含めない (span/ログへ誤って載せた場合の多層防御) + impl std::fmt::Debug for MySQL { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("MySQL") + .field("host", &self.host) + .field("port", &self.port) + .field("user", &self.user) + .field("password", &"") + .finish() + } + } + pub struct Config { pub http_port: HttpPort, pub mysql: MySQL, @@ -244,8 +270,8 @@ async fn main() { // 継続プロファイリング (Grafana Pyroscope への push)。 // PYROSCOPE_SERVER_ADDRESS 未設定なら無効。起動失敗はサーバー本体を止めない。 - // agent はプロセスの生存期間中動かし続けるため束縛だけ保持する - let _pyroscope_agent = std::env::var("PYROSCOPE_SERVER_ADDRESS") + // agent はプロセスの生存期間中動かし続け、graceful shutdown 時に flush する + let pyroscope_agent = std::env::var("PYROSCOPE_SERVER_ADDRESS") .ok() .and_then(|server_address| { // application 名は game-data-publisher と同じく env で上書き可能にする @@ -297,9 +323,39 @@ async fn main() { let listener = TcpListener::bind(addr).await.unwrap(); - axum::serve(listener, router).await.unwrap(); + // SIGTERM (Kubernetes の Pod 停止) / Ctrl-C で serve を抜け、 + // 下の flush 処理へ到達させる + let shutdown_signal = async { + let ctrl_c = async { + tokio::signal::ctrl_c() + .await + .expect("failed to install Ctrl-C handler"); + }; + let terminate = async { + tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()) + .expect("failed to install SIGTERM handler") + .recv() + .await; + }; + tokio::select! { + () = ctrl_c => {}, + _ = terminate => {}, + } + tracing::info!("shutdown signal received"); + }; + + axum::serve(listener, router) + .with_graceful_shutdown(shutdown_signal) + .await + .unwrap(); - // serve が戻るのはシャットダウン時のみ。バッファ済みスパンを flush する + // 終了前に未送信のプロファイル・スパンを flush する + if let Some(agent) = pyroscope_agent { + match agent.stop() { + Ok(agent) => agent.shutdown(), + Err(error) => tracing::warn!(%error, "Pyroscope agent の停止に失敗しました"), + } + } if let Some(provider) = tracer_provider { let _ = provider.shutdown(); }