Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 69 additions & 10 deletions server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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<MySQLDumpConnection>,
) -> Result<impl IntoResponse> {
Expand Down Expand Up @@ -167,14 +181,26 @@ mod config {
pub port: u16,
}

#[derive(Debug, Clone, Deserialize)]
#[derive(Clone, Deserialize)]
pub struct MySQL {
pub host: String,
pub port: u16,
pub user: String,
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", &"<redacted>")
.finish()
}
}

pub struct Config {
pub http_port: HttpPort,
pub mysql: MySQL,
Expand Down Expand Up @@ -244,13 +270,16 @@ 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 で上書き可能にする
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"),
Expand Down Expand Up @@ -294,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();
}
Expand Down
Loading