feat(observability): Phase 6 OTLP tracing for gRPC + config helper

- libs/rpc: slog → tracing; 8 gRPC methods instrumented with
  info_span + Instrument for W3C trace propagation
- libs/session_manager: slog → tracing dependency
- libs/config: add redis_url() singleton helper for adminrpc
This commit is contained in:
ZhenYi 2026-04-21 23:05:37 +08:00
parent 1e6ba34827
commit beae9bdea0
5 changed files with 108 additions and 62 deletions

View File

@ -1,6 +1,14 @@
use crate::AppConfig; use crate::AppConfig;
impl AppConfig { impl AppConfig {
/// Returns a single Redis URL (first from APP_REDIS_URLS or APP_REDIS_URL).
pub fn redis_url(&self) -> anyhow::Result<String> {
let urls = self.redis_urls()?;
urls.into_iter().next().ok_or_else(|| {
anyhow::anyhow!("APP_REDIS_URLS or APP_REDIS_URL is empty")
})
}
pub fn redis_urls(&self) -> anyhow::Result<Vec<String>> { pub fn redis_urls(&self) -> anyhow::Result<Vec<String>> {
if let Some(urls) = self.env.get("APP_REDIS_URLS") { if let Some(urls) = self.env.get("APP_REDIS_URLS") {
return Ok(urls.split(',').map(|s| s.trim().to_string()).collect()); return Ok(urls.split(',').map(|s| s.trim().to_string()).collect());

View File

@ -19,12 +19,13 @@ name = "rpc"
tonic = { workspace = true } tonic = { workspace = true }
prost = { workspace = true } prost = { workspace = true }
prost-types = { workspace = true } prost-types = { workspace = true }
tonic-prost = "0.14.5"
# Internal # Internal
session_manager = { workspace = true } session_manager = { workspace = true }
# Logging # Logging / Tracing
slog = { workspace = true } tracing = { workspace = true }
# Utilities # Utilities
anyhow = { workspace = true } anyhow = { workspace = true }

View File

@ -0,0 +1,4 @@
pub mod client;
pub mod generated;
pub mod server;
pub mod types;

View File

@ -4,6 +4,7 @@ use session_manager::SessionManager;
use std::net::SocketAddr; use std::net::SocketAddr;
use tokio::sync::broadcast; use tokio::sync::broadcast;
use tonic::{transport::Server, Request, Response, Status}; use tonic::{transport::Server, Request, Response, Status};
use tracing::{info_span, Instrument};
use super::generated::admin::{ use super::generated::admin::{
GetUserInfoRequest, GetUserInfoResponse, GetUserStatusRequest, GetUserStatusResponse, GetUserInfoRequest, GetUserInfoResponse, GetUserStatusRequest, GetUserStatusResponse,
@ -37,6 +38,7 @@ impl SessionAdmin for SessionAdminService {
let workspace_id = parse_uuid(&req.get_ref().workspace_id) let workspace_id = parse_uuid(&req.get_ref().workspace_id)
.ok_or_else(|| Status::invalid_argument("invalid workspace_id"))?; .ok_or_else(|| Status::invalid_argument("invalid workspace_id"))?;
async {
let sessions = self let sessions = self
.session_manager .session_manager
.get_workspace_sessions(&workspace_id) .get_workspace_sessions(&workspace_id)
@ -46,6 +48,9 @@ impl SessionAdmin for SessionAdminService {
let sessions = sessions.iter().map(to_proto_session).collect(); let sessions = sessions.iter().map(to_proto_session).collect();
Ok(Response::new(ListWorkspaceSessionsResponse { sessions })) Ok(Response::new(ListWorkspaceSessionsResponse { sessions }))
} }
.instrument(info_span!("list_workspace_sessions", workspace_id = %workspace_id))
.await
}
async fn list_user_sessions( async fn list_user_sessions(
&self, &self,
@ -54,6 +59,7 @@ impl SessionAdmin for SessionAdminService {
let user_id = parse_uuid(&req.get_ref().user_id) let user_id = parse_uuid(&req.get_ref().user_id)
.ok_or_else(|| Status::invalid_argument("invalid user_id"))?; .ok_or_else(|| Status::invalid_argument("invalid user_id"))?;
async {
let sessions = self let sessions = self
.session_manager .session_manager
.get_user_sessions(&user_id) .get_user_sessions(&user_id)
@ -63,6 +69,9 @@ impl SessionAdmin for SessionAdminService {
let sessions = sessions.iter().map(to_proto_session).collect(); let sessions = sessions.iter().map(to_proto_session).collect();
Ok(Response::new(ListUserSessionsResponse { sessions })) Ok(Response::new(ListUserSessionsResponse { sessions }))
} }
.instrument(info_span!("list_user_sessions", user_id = %user_id))
.await
}
async fn kick_user_from_workspace( async fn kick_user_from_workspace(
&self, &self,
@ -74,6 +83,7 @@ impl SessionAdmin for SessionAdminService {
let workspace_id = parse_uuid(&r.workspace_id) let workspace_id = parse_uuid(&r.workspace_id)
.ok_or_else(|| Status::invalid_argument("invalid workspace_id"))?; .ok_or_else(|| Status::invalid_argument("invalid workspace_id"))?;
async {
let kicked_count = self let kicked_count = self
.session_manager .session_manager
.kick_user_from_workspace(&user_id, &workspace_id) .kick_user_from_workspace(&user_id, &workspace_id)
@ -84,6 +94,9 @@ impl SessionAdmin for SessionAdminService {
kicked_count: kicked_count as u32, kicked_count: kicked_count as u32,
})) }))
} }
.instrument(info_span!("kick_user_from_workspace", user_id = %user_id, workspace_id = %workspace_id))
.await
}
async fn kick_user( async fn kick_user(
&self, &self,
@ -92,6 +105,7 @@ impl SessionAdmin for SessionAdminService {
let user_id = parse_uuid(&req.get_ref().user_id) let user_id = parse_uuid(&req.get_ref().user_id)
.ok_or_else(|| Status::invalid_argument("invalid user_id"))?; .ok_or_else(|| Status::invalid_argument("invalid user_id"))?;
async {
let kicked_count = self let kicked_count = self
.session_manager .session_manager
.kick_user(&user_id) .kick_user(&user_id)
@ -102,6 +116,9 @@ impl SessionAdmin for SessionAdminService {
kicked_count: kicked_count as u32, kicked_count: kicked_count as u32,
})) }))
} }
.instrument(info_span!("kick_user", user_id = %user_id))
.await
}
async fn get_user_status( async fn get_user_status(
&self, &self,
@ -110,6 +127,7 @@ impl SessionAdmin for SessionAdminService {
let user_id = parse_uuid(&req.get_ref().user_id) let user_id = parse_uuid(&req.get_ref().user_id)
.ok_or_else(|| Status::invalid_argument("invalid user_id"))?; .ok_or_else(|| Status::invalid_argument("invalid user_id"))?;
async {
let status = self let status = self
.session_manager .session_manager
.get_user_status(&user_id) .get_user_status(&user_id)
@ -120,6 +138,9 @@ impl SessionAdmin for SessionAdminService {
status: to_proto_status(status) as i32, status: to_proto_status(status) as i32,
})) }))
} }
.instrument(info_span!("get_user_status", user_id = %user_id))
.await
}
async fn get_user_info( async fn get_user_info(
&self, &self,
@ -128,6 +149,7 @@ impl SessionAdmin for SessionAdminService {
let user_id = parse_uuid(&req.get_ref().user_id) let user_id = parse_uuid(&req.get_ref().user_id)
.ok_or_else(|| Status::invalid_argument("invalid user_id"))?; .ok_or_else(|| Status::invalid_argument("invalid user_id"))?;
async {
let info = self let info = self
.session_manager .session_manager
.get_user_info(&user_id) .get_user_info(&user_id)
@ -138,6 +160,9 @@ impl SessionAdmin for SessionAdminService {
info: info.as_ref().map(to_proto_info), info: info.as_ref().map(to_proto_info),
})) }))
} }
.instrument(info_span!("get_user_info", user_id = %user_id))
.await
}
async fn get_workspace_online_users( async fn get_workspace_online_users(
&self, &self,
@ -146,6 +171,7 @@ impl SessionAdmin for SessionAdminService {
let workspace_id = parse_uuid(&req.get_ref().workspace_id) let workspace_id = parse_uuid(&req.get_ref().workspace_id)
.ok_or_else(|| Status::invalid_argument("invalid workspace_id"))?; .ok_or_else(|| Status::invalid_argument("invalid workspace_id"))?;
async {
let user_ids = self let user_ids = self
.session_manager .session_manager
.get_workspace_online_users(&workspace_id) .get_workspace_online_users(&workspace_id)
@ -155,6 +181,9 @@ impl SessionAdmin for SessionAdminService {
let user_ids = user_ids.iter().map(|u| u.to_string()).collect(); let user_ids = user_ids.iter().map(|u| u.to_string()).collect();
Ok(Response::new(GetWorkspaceOnlineUsersResponse { user_ids })) Ok(Response::new(GetWorkspaceOnlineUsersResponse { user_ids }))
} }
.instrument(info_span!("get_workspace_online_users", workspace_id = %workspace_id))
.await
}
async fn is_user_online( async fn is_user_online(
&self, &self,
@ -163,6 +192,7 @@ impl SessionAdmin for SessionAdminService {
let user_id = parse_uuid(&req.get_ref().user_id) let user_id = parse_uuid(&req.get_ref().user_id)
.ok_or_else(|| Status::invalid_argument("invalid user_id"))?; .ok_or_else(|| Status::invalid_argument("invalid user_id"))?;
async {
let online = self let online = self
.session_manager .session_manager
.is_user_online(&user_id) .is_user_online(&user_id)
@ -171,6 +201,9 @@ impl SessionAdmin for SessionAdminService {
Ok(Response::new(IsUserOnlineResponse { online })) Ok(Response::new(IsUserOnlineResponse { online }))
} }
.instrument(info_span!("is_user_online", user_id = %user_id))
.await
}
} }
/// Default gRPC admin port. /// Default gRPC admin port.

View File

@ -26,7 +26,7 @@ serde_json = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread", "sync"] } tokio = { workspace = true, features = ["rt-multi-thread", "sync"] }
uuid = { workspace = true, features = ["serde", "v4"] } uuid = { workspace = true, features = ["serde", "v4"] }
slog = { workspace = true } tracing = { workspace = true }
[lints] [lints]
workspace = true workspace = true