refactor(observability,queue): apply rustfmt formatting
This commit is contained in:
parent
8fd6dbb68b
commit
52e1831452
@ -89,61 +89,205 @@ pub const ACTIVE_ROOM_PARTICIPANTS: &str = "active_room_participants";
|
||||
/// Called from `install_recorder()` in `prometheus_exporter.rs`.
|
||||
pub fn describe_business_metrics() {
|
||||
// Project
|
||||
metrics::describe_counter!(PROJECTS_CREATED_TOTAL, metrics::Unit::Count, "Projects created");
|
||||
metrics::describe_counter!(PROJECTS_DELETED_TOTAL, metrics::Unit::Count, "Projects deleted");
|
||||
metrics::describe_counter!(PROJECT_MEMBERS_ADDED_TOTAL, metrics::Unit::Count, "Project members added");
|
||||
metrics::describe_counter!(PROJECT_MEMBERS_REMOVED_TOTAL, metrics::Unit::Count, "Project members removed");
|
||||
metrics::describe_counter!(PROJECT_MEMBERS_ROLE_CHANGED_TOTAL, metrics::Unit::Count, "Project member role changes");
|
||||
metrics::describe_counter!(
|
||||
PROJECTS_CREATED_TOTAL,
|
||||
metrics::Unit::Count,
|
||||
"Projects created"
|
||||
);
|
||||
metrics::describe_counter!(
|
||||
PROJECTS_DELETED_TOTAL,
|
||||
metrics::Unit::Count,
|
||||
"Projects deleted"
|
||||
);
|
||||
metrics::describe_counter!(
|
||||
PROJECT_MEMBERS_ADDED_TOTAL,
|
||||
metrics::Unit::Count,
|
||||
"Project members added"
|
||||
);
|
||||
metrics::describe_counter!(
|
||||
PROJECT_MEMBERS_REMOVED_TOTAL,
|
||||
metrics::Unit::Count,
|
||||
"Project members removed"
|
||||
);
|
||||
metrics::describe_counter!(
|
||||
PROJECT_MEMBERS_ROLE_CHANGED_TOTAL,
|
||||
metrics::Unit::Count,
|
||||
"Project member role changes"
|
||||
);
|
||||
metrics::describe_counter!(PROJECT_LIKES_TOTAL, metrics::Unit::Count, "Project likes");
|
||||
metrics::describe_counter!(PROJECT_UNLIKES_TOTAL, metrics::Unit::Count, "Project unlikes");
|
||||
metrics::describe_counter!(PROJECT_WATCHES_TOTAL, metrics::Unit::Count, "Project watches");
|
||||
metrics::describe_counter!(PROJECT_UNWATCHES_TOTAL, metrics::Unit::Count, "Project unwatches");
|
||||
metrics::describe_counter!(
|
||||
PROJECT_UNLIKES_TOTAL,
|
||||
metrics::Unit::Count,
|
||||
"Project unlikes"
|
||||
);
|
||||
metrics::describe_counter!(
|
||||
PROJECT_WATCHES_TOTAL,
|
||||
metrics::Unit::Count,
|
||||
"Project watches"
|
||||
);
|
||||
metrics::describe_counter!(
|
||||
PROJECT_UNWATCHES_TOTAL,
|
||||
metrics::Unit::Count,
|
||||
"Project unwatches"
|
||||
);
|
||||
|
||||
// Issue
|
||||
metrics::describe_counter!(ISSUES_OPENED_TOTAL, metrics::Unit::Count, "Issues opened");
|
||||
metrics::describe_counter!(ISSUES_CLOSED_TOTAL, metrics::Unit::Count, "Issues closed");
|
||||
metrics::describe_counter!(ISSUES_REOPENED_TOTAL, metrics::Unit::Count, "Issues reopened");
|
||||
metrics::describe_counter!(
|
||||
ISSUES_REOPENED_TOTAL,
|
||||
metrics::Unit::Count,
|
||||
"Issues reopened"
|
||||
);
|
||||
metrics::describe_counter!(ISSUES_DELETED_TOTAL, metrics::Unit::Count, "Issues deleted");
|
||||
metrics::describe_counter!(ISSUES_UPDATED_TOTAL, metrics::Unit::Count, "Issues updated");
|
||||
metrics::describe_counter!(ISSUE_COMMENTS_CREATED_TOTAL, metrics::Unit::Count, "Issue comments created");
|
||||
metrics::describe_counter!(ISSUE_COMMENTS_DELETED_TOTAL, metrics::Unit::Count, "Issue comments deleted");
|
||||
metrics::describe_counter!(
|
||||
ISSUE_COMMENTS_CREATED_TOTAL,
|
||||
metrics::Unit::Count,
|
||||
"Issue comments created"
|
||||
);
|
||||
metrics::describe_counter!(
|
||||
ISSUE_COMMENTS_DELETED_TOTAL,
|
||||
metrics::Unit::Count,
|
||||
"Issue comments deleted"
|
||||
);
|
||||
|
||||
// Pull Request
|
||||
metrics::describe_counter!(PRS_OPENED_TOTAL, metrics::Unit::Count, "Pull requests opened");
|
||||
metrics::describe_counter!(PRS_MERGED_TOTAL, metrics::Unit::Count, "Pull requests merged");
|
||||
metrics::describe_counter!(PRS_CLOSED_TOTAL, metrics::Unit::Count, "Pull requests closed (without merge)");
|
||||
metrics::describe_counter!(PRS_UPDATED_TOTAL, metrics::Unit::Count, "Pull requests updated");
|
||||
metrics::describe_counter!(PR_REVIEWS_SUBMITTED_TOTAL, metrics::Unit::Count, "PR reviews submitted");
|
||||
metrics::describe_counter!(PR_REVIEW_COMMENTS_TOTAL, metrics::Unit::Count, "PR review comments");
|
||||
metrics::describe_counter!(
|
||||
PRS_OPENED_TOTAL,
|
||||
metrics::Unit::Count,
|
||||
"Pull requests opened"
|
||||
);
|
||||
metrics::describe_counter!(
|
||||
PRS_MERGED_TOTAL,
|
||||
metrics::Unit::Count,
|
||||
"Pull requests merged"
|
||||
);
|
||||
metrics::describe_counter!(
|
||||
PRS_CLOSED_TOTAL,
|
||||
metrics::Unit::Count,
|
||||
"Pull requests closed (without merge)"
|
||||
);
|
||||
metrics::describe_counter!(
|
||||
PRS_UPDATED_TOTAL,
|
||||
metrics::Unit::Count,
|
||||
"Pull requests updated"
|
||||
);
|
||||
metrics::describe_counter!(
|
||||
PR_REVIEWS_SUBMITTED_TOTAL,
|
||||
metrics::Unit::Count,
|
||||
"PR reviews submitted"
|
||||
);
|
||||
metrics::describe_counter!(
|
||||
PR_REVIEW_COMMENTS_TOTAL,
|
||||
metrics::Unit::Count,
|
||||
"PR review comments"
|
||||
);
|
||||
|
||||
// Room
|
||||
metrics::describe_counter!(ROOMS_CREATED_TOTAL, metrics::Unit::Count, "Chat rooms created");
|
||||
metrics::describe_counter!(ROOMS_DELETED_TOTAL, metrics::Unit::Count, "Chat rooms deleted");
|
||||
metrics::describe_counter!(ROOMS_UPDATED_TOTAL, metrics::Unit::Count, "Chat rooms updated");
|
||||
metrics::describe_counter!(ROOM_MESSAGES_SENT_TOTAL, metrics::Unit::Count, "Room messages sent (human)");
|
||||
metrics::describe_counter!(ROOM_MESSAGES_AI_TOTAL, metrics::Unit::Count, "Room messages sent (AI)");
|
||||
metrics::describe_counter!(ROOM_THREADS_CREATED_TOTAL, metrics::Unit::Count, "Room threads created");
|
||||
metrics::describe_counter!(
|
||||
ROOMS_CREATED_TOTAL,
|
||||
metrics::Unit::Count,
|
||||
"Chat rooms created"
|
||||
);
|
||||
metrics::describe_counter!(
|
||||
ROOMS_DELETED_TOTAL,
|
||||
metrics::Unit::Count,
|
||||
"Chat rooms deleted"
|
||||
);
|
||||
metrics::describe_counter!(
|
||||
ROOMS_UPDATED_TOTAL,
|
||||
metrics::Unit::Count,
|
||||
"Chat rooms updated"
|
||||
);
|
||||
metrics::describe_counter!(
|
||||
ROOM_MESSAGES_SENT_TOTAL,
|
||||
metrics::Unit::Count,
|
||||
"Room messages sent (human)"
|
||||
);
|
||||
metrics::describe_counter!(
|
||||
ROOM_MESSAGES_AI_TOTAL,
|
||||
metrics::Unit::Count,
|
||||
"Room messages sent (AI)"
|
||||
);
|
||||
metrics::describe_counter!(
|
||||
ROOM_THREADS_CREATED_TOTAL,
|
||||
metrics::Unit::Count,
|
||||
"Room threads created"
|
||||
);
|
||||
|
||||
// Repo / Git
|
||||
metrics::describe_counter!(REPOS_CREATED_TOTAL, metrics::Unit::Count, "Repos created");
|
||||
metrics::describe_counter!(GIT_COMMITS_PUSHED_TOTAL, metrics::Unit::Count, "Git commits pushed");
|
||||
metrics::describe_counter!(GIT_BRANCHES_CREATED_TOTAL, metrics::Unit::Count, "Git branches created");
|
||||
metrics::describe_counter!(GIT_BRANCHES_DELETED_TOTAL, metrics::Unit::Count, "Git branches deleted");
|
||||
metrics::describe_counter!(GIT_TAGS_CREATED_TOTAL, metrics::Unit::Count, "Git tags created");
|
||||
metrics::describe_counter!(GIT_TAGS_DELETED_TOTAL, metrics::Unit::Count, "Git tags deleted");
|
||||
metrics::describe_counter!(GIT_CLONES_TOTAL, metrics::Unit::Count, "Git clone/fetch operations");
|
||||
metrics::describe_counter!(
|
||||
GIT_COMMITS_PUSHED_TOTAL,
|
||||
metrics::Unit::Count,
|
||||
"Git commits pushed"
|
||||
);
|
||||
metrics::describe_counter!(
|
||||
GIT_BRANCHES_CREATED_TOTAL,
|
||||
metrics::Unit::Count,
|
||||
"Git branches created"
|
||||
);
|
||||
metrics::describe_counter!(
|
||||
GIT_BRANCHES_DELETED_TOTAL,
|
||||
metrics::Unit::Count,
|
||||
"Git branches deleted"
|
||||
);
|
||||
metrics::describe_counter!(
|
||||
GIT_TAGS_CREATED_TOTAL,
|
||||
metrics::Unit::Count,
|
||||
"Git tags created"
|
||||
);
|
||||
metrics::describe_counter!(
|
||||
GIT_TAGS_DELETED_TOTAL,
|
||||
metrics::Unit::Count,
|
||||
"Git tags deleted"
|
||||
);
|
||||
metrics::describe_counter!(
|
||||
GIT_CLONES_TOTAL,
|
||||
metrics::Unit::Count,
|
||||
"Git clone/fetch operations"
|
||||
);
|
||||
|
||||
// Billing
|
||||
metrics::describe_counter!(BILLING_CREDITS_USED_TOTAL, metrics::Unit::Count, "Billing credits consumed");
|
||||
metrics::describe_counter!(
|
||||
BILLING_CREDITS_USED_TOTAL,
|
||||
metrics::Unit::Count,
|
||||
"Billing credits consumed"
|
||||
);
|
||||
metrics::describe_counter!(BILLING_ERRORS_TOTAL, metrics::Unit::Count, "Billing errors");
|
||||
metrics::describe_counter!(BILLING_CREDITS_ADDED_TOTAL, metrics::Unit::Count, "Billing credits added (top-up)");
|
||||
metrics::describe_counter!(
|
||||
BILLING_CREDITS_ADDED_TOTAL,
|
||||
metrics::Unit::Count,
|
||||
"Billing credits added (top-up)"
|
||||
);
|
||||
|
||||
// AI
|
||||
metrics::describe_counter!(AI_ROOM_CALLS_TOTAL, metrics::Unit::Count, "AI calls in room context");
|
||||
metrics::describe_counter!(AI_CHAT_CONVERSATIONS_CREATED, metrics::Unit::Count, "AI chat conversations created");
|
||||
metrics::describe_counter!(AI_CHAT_MESSAGES_SENT, metrics::Unit::Count, "AI chat messages sent");
|
||||
metrics::describe_counter!(
|
||||
AI_ROOM_CALLS_TOTAL,
|
||||
metrics::Unit::Count,
|
||||
"AI calls in room context"
|
||||
);
|
||||
metrics::describe_counter!(
|
||||
AI_CHAT_CONVERSATIONS_CREATED,
|
||||
metrics::Unit::Count,
|
||||
"AI chat conversations created"
|
||||
);
|
||||
metrics::describe_counter!(
|
||||
AI_CHAT_MESSAGES_SENT,
|
||||
metrics::Unit::Count,
|
||||
"AI chat messages sent"
|
||||
);
|
||||
|
||||
// Gauges
|
||||
metrics::describe_gauge!(ACTIVE_CONNECTIONS, metrics::Unit::Count, "Active WebSocket connections");
|
||||
metrics::describe_gauge!(ACTIVE_ROOM_PARTICIPANTS, metrics::Unit::Count, "Active room participants");
|
||||
metrics::describe_gauge!(
|
||||
ACTIVE_CONNECTIONS,
|
||||
metrics::Unit::Count,
|
||||
"Active WebSocket connections"
|
||||
);
|
||||
metrics::describe_gauge!(
|
||||
ACTIVE_ROOM_PARTICIPANTS,
|
||||
metrics::Unit::Count,
|
||||
"Active room participants"
|
||||
);
|
||||
}
|
||||
@ -3,24 +3,24 @@
|
||||
//! Call `observability::init_tracing_subscriber(level)` once at startup.
|
||||
//! All services then use `tracing::info!`, `tracing::warn!`, etc. directly.
|
||||
|
||||
pub mod business_metrics;
|
||||
pub mod metrics_middleware;
|
||||
pub mod msg_json_fmt;
|
||||
pub mod otlp;
|
||||
pub mod prometheus_exporter;
|
||||
pub mod push;
|
||||
pub mod tracing_fmt;
|
||||
pub mod tracing_init;
|
||||
pub mod msg_json_fmt;
|
||||
pub mod metrics_middleware;
|
||||
pub mod prometheus_exporter;
|
||||
pub mod business_metrics;
|
||||
pub mod otlp;
|
||||
pub mod tracing_middleware;
|
||||
pub mod push;
|
||||
|
||||
pub use tracing_fmt::{init_tracing_subscriber, instance_id};
|
||||
pub use metrics_middleware::{HttpMetrics, MetricsMiddleware};
|
||||
pub use msg_json_fmt::set_span_msg;
|
||||
pub use metrics_middleware::{MetricsMiddleware, HttpMetrics};
|
||||
pub use prometheus_exporter::{
|
||||
install_recorder, prometheus_handler, spawn_http_metrics_poller,
|
||||
HttpMetricsSnapshot, HttpSnapshotGuard, render_to_hashmap,
|
||||
HttpMetricsSnapshot, HttpSnapshotGuard, install_recorder, prometheus_handler,
|
||||
render_to_hashmap, spawn_http_metrics_poller,
|
||||
};
|
||||
pub use tracing_fmt::{init_tracing_subscriber, instance_id};
|
||||
pub type PrometheusHandle = metrics_exporter_prometheus::PrometheusHandle;
|
||||
pub use otlp::{init_otlp, OtelGuard};
|
||||
pub use tracing_middleware::TracingSpanMiddleware;
|
||||
pub use business_metrics::*;
|
||||
pub use otlp::{OtelGuard, init_otlp};
|
||||
pub use tracing_middleware::TracingSpanMiddleware;
|
||||
|
||||
@ -37,7 +37,10 @@ impl HttpMetrics {
|
||||
/// Increment the counter for a specific HTTP endpoint (method + path).
|
||||
pub fn incr_endpoint(&self, method: &str, path: &str) {
|
||||
let key = format!("{} {}", method, path);
|
||||
let mut map = self.endpoint_counts.write().unwrap_or_else(|e| e.into_inner());
|
||||
let mut map = self
|
||||
.endpoint_counts
|
||||
.write()
|
||||
.unwrap_or_else(|e| e.into_inner());
|
||||
let counter = map.entry(key).or_insert_with(|| AtomicU64::new(0));
|
||||
counter.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
@ -45,19 +48,40 @@ impl HttpMetrics {
|
||||
/// Returns a snapshot of all current counter values.
|
||||
pub fn snapshot(&self) -> HashMap<String, serde_json::Value> {
|
||||
let mut m = HashMap::new();
|
||||
m.insert("http_requests_total".into(), serde_json::json!(self.request_count.load(Ordering::Relaxed)));
|
||||
m.insert("http_request_duration_ms_total".into(), serde_json::json!(self.total_duration_ms.load(Ordering::Relaxed)));
|
||||
m.insert("http_requests_2xx".into(), serde_json::json!(self.status_2xx.load(Ordering::Relaxed)));
|
||||
m.insert("http_requests_4xx".into(), serde_json::json!(self.status_4xx.load(Ordering::Relaxed)));
|
||||
m.insert("http_requests_5xx".into(), serde_json::json!(self.status_5xx.load(Ordering::Relaxed)));
|
||||
m.insert(
|
||||
"http_requests_total".into(),
|
||||
serde_json::json!(self.request_count.load(Ordering::Relaxed)),
|
||||
);
|
||||
m.insert(
|
||||
"http_request_duration_ms_total".into(),
|
||||
serde_json::json!(self.total_duration_ms.load(Ordering::Relaxed)),
|
||||
);
|
||||
m.insert(
|
||||
"http_requests_2xx".into(),
|
||||
serde_json::json!(self.status_2xx.load(Ordering::Relaxed)),
|
||||
);
|
||||
m.insert(
|
||||
"http_requests_4xx".into(),
|
||||
serde_json::json!(self.status_4xx.load(Ordering::Relaxed)),
|
||||
);
|
||||
m.insert(
|
||||
"http_requests_5xx".into(),
|
||||
serde_json::json!(self.status_5xx.load(Ordering::Relaxed)),
|
||||
);
|
||||
|
||||
// Per-endpoint counters
|
||||
let map = self.endpoint_counts.read().unwrap_or_else(|e| e.into_inner());
|
||||
let map = self
|
||||
.endpoint_counts
|
||||
.read()
|
||||
.unwrap_or_else(|e| e.into_inner());
|
||||
for (key, counter) in map.iter() {
|
||||
// Sanitize key for use as metric name: replace spaces and slashes with underscores
|
||||
let sanitized = key.replace([' ', '/'], "_").to_lowercase();
|
||||
let metric_key = format!("http_endpoint_{}", sanitized);
|
||||
m.insert(metric_key, serde_json::json!(counter.load(Ordering::Relaxed)));
|
||||
m.insert(
|
||||
metric_key,
|
||||
serde_json::json!(counter.load(Ordering::Relaxed)),
|
||||
);
|
||||
}
|
||||
|
||||
m
|
||||
@ -139,7 +163,9 @@ where
|
||||
|
||||
// Update counters atomically.
|
||||
metrics.request_count.fetch_add(1, Ordering::Relaxed);
|
||||
metrics.total_duration_ms.fetch_add(elapsed_ms, Ordering::Relaxed);
|
||||
metrics
|
||||
.total_duration_ms
|
||||
.fetch_add(elapsed_ms, Ordering::Relaxed);
|
||||
metrics.incr_endpoint(&method, &path);
|
||||
|
||||
match status_code {
|
||||
|
||||
@ -63,8 +63,10 @@ impl Visit for FieldCollector {
|
||||
if field.name() == "message" {
|
||||
self.message = Some(value.to_string());
|
||||
} else {
|
||||
self.fields
|
||||
.insert(field.name().to_string(), serde_json::Value::String(value.to_string()));
|
||||
self.fields.insert(
|
||||
field.name().to_string(),
|
||||
serde_json::Value::String(value.to_string()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,10 +93,8 @@ impl Visit for FieldCollector {
|
||||
}
|
||||
|
||||
fn record_bool(&mut self, field: &Field, value: bool) {
|
||||
self.fields.insert(
|
||||
field.name().to_string(),
|
||||
serde_json::Value::Bool(value),
|
||||
);
|
||||
self.fields
|
||||
.insert(field.name().to_string(), serde_json::Value::Bool(value));
|
||||
}
|
||||
}
|
||||
|
||||
@ -145,7 +145,10 @@ where
|
||||
);
|
||||
}
|
||||
if let Some(file) = event.metadata().file() {
|
||||
ordered.insert("file".to_string(), serde_json::Value::String(file.to_string()));
|
||||
ordered.insert(
|
||||
"file".to_string(),
|
||||
serde_json::Value::String(file.to_string()),
|
||||
);
|
||||
}
|
||||
if let Some(line) = event.metadata().line() {
|
||||
ordered.insert("line".to_string(), serde_json::Value::Number(line.into()));
|
||||
|
||||
@ -13,7 +13,9 @@ use crate::msg_json_fmt::MsgJsonFormat;
|
||||
use opentelemetry::trace::TracerProvider;
|
||||
use opentelemetry_otlp::{SpanExporter, WithExportConfig};
|
||||
use opentelemetry_sdk::trace as sdktrace;
|
||||
use tracing_subscriber::{fmt, fmt::Layer as FmtLayer, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
|
||||
use tracing_subscriber::{
|
||||
EnvFilter, fmt, fmt::Layer as FmtLayer, layer::SubscriberExt, util::SubscriberInitExt,
|
||||
};
|
||||
|
||||
/// Guard that shuts down the OTLP pipeline on drop.
|
||||
#[must_use]
|
||||
@ -58,11 +60,10 @@ pub fn init_otlp(
|
||||
.build()
|
||||
.map_err(|e| InitOtlError::ExporterInit(e.to_string()))?;
|
||||
|
||||
let env_filter = EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| EnvFilter::new(log_level));
|
||||
let env_filter =
|
||||
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(log_level));
|
||||
|
||||
let fmt_layer: FmtLayer<_, _, _, _> = fmt::layer()
|
||||
.event_format(MsgJsonFormat);
|
||||
let fmt_layer: FmtLayer<_, _, _, _> = fmt::layer().event_format(MsgJsonFormat);
|
||||
|
||||
let tracer_provider = sdktrace::SdkTracerProvider::builder()
|
||||
.with_batch_exporter(exporter)
|
||||
@ -82,7 +83,9 @@ pub fn init_otlp(
|
||||
|
||||
tracing::debug!(endpoint = %endpoint, "OTLP tracer installed");
|
||||
|
||||
Ok(Some(OtelGuard { provider: tracer_provider }))
|
||||
Ok(Some(OtelGuard {
|
||||
provider: tracer_provider,
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
|
||||
@ -12,8 +12,8 @@
|
||||
//! `RoomMetrics` must be constructed **after** `install_recorder()` is called,
|
||||
//! because its `register_*` macro calls require a global recorder to be set.
|
||||
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use metrics::{describe_counter, describe_histogram, Unit};
|
||||
use actix_web::{HttpRequest, HttpResponse, web};
|
||||
use metrics::{Unit, describe_counter, describe_histogram};
|
||||
use metrics_exporter_prometheus::PrometheusBuilder;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::atomic::Ordering;
|
||||
@ -97,7 +97,6 @@ pub fn render_to_hashmap(body: &str) -> HashMap<String, serde_json::Value> {
|
||||
/// Re-export `HttpMetrics` so callers don't need to import from `metrics_middleware`.
|
||||
pub use crate::metrics_middleware::HttpMetrics;
|
||||
|
||||
|
||||
/// Live HTTP metric values updated by the background poller.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct HttpMetricsSnapshot {
|
||||
|
||||
@ -18,8 +18,8 @@ use std::time::Duration;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::metrics_middleware::HttpMetrics;
|
||||
use crate::instance_id;
|
||||
use crate::metrics_middleware::HttpMetrics;
|
||||
|
||||
// ── Payload types ──────────────────────────────────────────────────────────────
|
||||
|
||||
@ -310,7 +310,9 @@ mod tests {
|
||||
fn payload_serialises_to_json() {
|
||||
let _pusher = MetricsPusher::new("http://localhost:9090", "test-app");
|
||||
let metrics = HttpMetrics::new();
|
||||
metrics.request_count.fetch_add(42, std::sync::atomic::Ordering::Relaxed);
|
||||
metrics
|
||||
.request_count
|
||||
.fetch_add(42, std::sync::atomic::Ordering::Relaxed);
|
||||
let _handle = metrics_exporter_prometheus::PrometheusBuilder::new()
|
||||
.build_recorder()
|
||||
.handle();
|
||||
@ -318,7 +320,10 @@ mod tests {
|
||||
// Just check the HTTP payload portion.
|
||||
let snapshot = metrics.snapshot();
|
||||
let http = HttpPayload {
|
||||
requests_total: snapshot.get("http_requests_total").and_then(|v| v.as_u64()).unwrap_or(0),
|
||||
requests_total: snapshot
|
||||
.get("http_requests_total")
|
||||
.and_then(|v| v.as_u64())
|
||||
.unwrap_or(0),
|
||||
request_duration_ms_total: 0,
|
||||
requests_2xx: 0,
|
||||
requests_4xx: 0,
|
||||
|
||||
@ -11,10 +11,10 @@ use std::io::IsTerminal;
|
||||
use std::str::FromStr;
|
||||
use tracing_subscriber::Layer;
|
||||
use tracing_subscriber::{
|
||||
EnvFilter,
|
||||
fmt::{self, format::FmtSpan},
|
||||
layer::SubscriberExt,
|
||||
util::SubscriberInitExt,
|
||||
EnvFilter,
|
||||
};
|
||||
|
||||
/// Global instance identifier, resolved once at startup.
|
||||
|
||||
@ -3,16 +3,15 @@
|
||||
//! Call `init_tracing()` during application startup to set up the
|
||||
//! tracing-subscriber fmt layer (writes human-readable spans to stderr).
|
||||
|
||||
use tracing_subscriber::{fmt, EnvFilter};
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::{EnvFilter, fmt};
|
||||
|
||||
/// Initialize tracing with a fmt layer.
|
||||
///
|
||||
/// The `EnvFilter` reads the `RUST_LOG` environment variable to
|
||||
/// set the log level (e.g. `RUST_LOG=info`).
|
||||
pub fn init_tracing() {
|
||||
let env_filter = EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| EnvFilter::new("info"));
|
||||
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
|
||||
|
||||
let fmt_layer = fmt::layer()
|
||||
.with_target(true)
|
||||
|
||||
@ -62,7 +62,9 @@ where
|
||||
{
|
||||
type Response = ServiceResponse<B>;
|
||||
type Error = actix_web::Error;
|
||||
type Future = std::pin::Pin<Box<dyn std::future::Future<Output = Result<Self::Response, Self::Error>> + 'static>>;
|
||||
type Future = std::pin::Pin<
|
||||
Box<dyn std::future::Future<Output = Result<Self::Response, Self::Error>> + 'static>,
|
||||
>;
|
||||
|
||||
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.service.poll_ready(cx)
|
||||
|
||||
@ -12,6 +12,6 @@ pub use types::{
|
||||
ReactionGroup, RoomMessageEnvelope, RoomMessageEvent, RoomMessageStreamChunkEvent, TypingEvent,
|
||||
};
|
||||
pub use worker::{
|
||||
room_worker_task, start as start_worker, start_email_worker, EmailSendFn, EmailSendFut,
|
||||
NatsConsumeFn, PersistFn,
|
||||
EmailSendFn, EmailSendFut, NatsConsumeFn, PersistFn, room_worker_task, start as start_worker,
|
||||
start_email_worker,
|
||||
};
|
||||
@ -66,10 +66,7 @@ impl NatsClient {
|
||||
if !is {
|
||||
attempts += 1;
|
||||
if attempts > 12 {
|
||||
tracing::error!(
|
||||
"NATS disconnected for {}s",
|
||||
attempts * 5
|
||||
);
|
||||
tracing::error!("NATS disconnected for {}s", attempts * 5);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -136,11 +133,7 @@ impl NatsClient {
|
||||
|
||||
/// Publish to core NATS for real-time broadcast. Fire-and-forget.
|
||||
pub async fn core_publish(&self, subject: String, payload: Vec<u8>) {
|
||||
if let Err(e) = self
|
||||
.client
|
||||
.publish(subject.clone(), payload.into())
|
||||
.await
|
||||
{
|
||||
if let Err(e) = self.client.publish(subject.clone(), payload.into()).await {
|
||||
tracing::warn!(subject = %subject, error = %e, "NATS core publish failed");
|
||||
}
|
||||
}
|
||||
@ -166,10 +159,7 @@ impl NatsClient {
|
||||
}
|
||||
|
||||
/// Create a core NATS subscription. Returns a subscriber receiver.
|
||||
pub async fn subscribe(
|
||||
&self,
|
||||
subject: &str,
|
||||
) -> anyhow::Result<async_nats::Subscriber> {
|
||||
pub async fn subscribe(&self, subject: &str) -> anyhow::Result<async_nats::Subscriber> {
|
||||
self.client
|
||||
.subscribe(subject.to_string())
|
||||
.await
|
||||
|
||||
@ -18,21 +18,26 @@ pub type NatsPublishResult = u64;
|
||||
pub struct MessageProducer {
|
||||
/// JetStream publish function for durable/persisted messages.
|
||||
pub jetstream_publish: Arc<
|
||||
dyn Fn(String, Vec<u8>) -> std::pin::Pin<
|
||||
Box<dyn std::future::Future<Output = anyhow::Result<u64>> + Send>,
|
||||
> + Send
|
||||
dyn Fn(
|
||||
String,
|
||||
Vec<u8>,
|
||||
)
|
||||
-> std::pin::Pin<Box<dyn std::future::Future<Output = anyhow::Result<u64>> + Send>>
|
||||
+ Send
|
||||
+ Sync,
|
||||
>,
|
||||
/// Core NATS publish function for real-time broadcast (fire-and-forget).
|
||||
pub core_publish: Arc<
|
||||
dyn Fn(String, Vec<u8>) -> std::pin::Pin<
|
||||
Box<dyn std::future::Future<Output = ()> + Send>,
|
||||
> + Send
|
||||
dyn Fn(String, Vec<u8>) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send>>
|
||||
+ Send
|
||||
+ Sync,
|
||||
>,
|
||||
/// Redis connection getter — kept for cache/seq access (notification count, etc.)
|
||||
pub get_redis:
|
||||
Arc<dyn Fn() -> tokio::task::JoinHandle<anyhow::Result<deadpool_redis::cluster::Connection>> + Send + Sync>,
|
||||
pub get_redis: Arc<
|
||||
dyn Fn() -> tokio::task::JoinHandle<anyhow::Result<deadpool_redis::cluster::Connection>>
|
||||
+ Send
|
||||
+ Sync,
|
||||
>,
|
||||
/// Direct NATS client reference for subscriptions (watch endpoints, etc.)
|
||||
pub nats: Option<Arc<NatsClient>>,
|
||||
}
|
||||
@ -47,7 +52,10 @@ impl MessageProducer {
|
||||
>,
|
||||
) -> Self {
|
||||
let js_fn: Arc<
|
||||
dyn Fn(String, Vec<u8>) -> std::pin::Pin<
|
||||
dyn Fn(
|
||||
String,
|
||||
Vec<u8>,
|
||||
) -> std::pin::Pin<
|
||||
Box<dyn std::future::Future<Output = anyhow::Result<u64>> + Send>,
|
||||
> + Send
|
||||
+ Sync,
|
||||
@ -64,9 +72,12 @@ impl MessageProducer {
|
||||
};
|
||||
|
||||
let core_fn: Arc<
|
||||
dyn Fn(String, Vec<u8>) -> std::pin::Pin<
|
||||
Box<dyn std::future::Future<Output = ()> + Send>,
|
||||
> + Send
|
||||
dyn Fn(
|
||||
String,
|
||||
Vec<u8>,
|
||||
)
|
||||
-> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send>>
|
||||
+ Send
|
||||
+ Sync,
|
||||
> = if let Some(ref n) = nats {
|
||||
let n = n.clone();
|
||||
@ -75,9 +86,7 @@ impl MessageProducer {
|
||||
Box::pin(async move { n.core_publish(subject, payload).await }) as _
|
||||
})
|
||||
} else {
|
||||
Arc::new(|_subject: String, _payload: Vec<u8>| {
|
||||
Box::pin(async move {}) as _
|
||||
})
|
||||
Arc::new(|_subject: String, _payload: Vec<u8>| Box::pin(async move {}) as _)
|
||||
};
|
||||
|
||||
Self {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
//! Room message worker: NATS JetStream durable pull consumer.
|
||||
|
||||
use crate::types::{EmailEnvelope, RoomMessageEvent, RoomMessageEnvelope};
|
||||
use crate::types::{EmailEnvelope, RoomMessageEnvelope, RoomMessageEvent};
|
||||
use futures::StreamExt;
|
||||
use metrics::counter;
|
||||
use std::sync::Arc;
|
||||
@ -19,7 +19,14 @@ pub type NatsConsumeFn = Arc<
|
||||
Output = anyhow::Result<
|
||||
Vec<(
|
||||
Vec<u8>,
|
||||
Box<dyn Fn() -> std::pin::Pin<Box<dyn std::future::Future<Output = anyhow::Result<()>> + Send>> + Send>,
|
||||
Box<
|
||||
dyn Fn() -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<Output = anyhow::Result<()>>
|
||||
+ Send,
|
||||
>,
|
||||
> + Send,
|
||||
>,
|
||||
)>,
|
||||
>,
|
||||
> + Send,
|
||||
@ -30,7 +37,8 @@ pub type NatsConsumeFn = Arc<
|
||||
|
||||
/// Function that persists a batch of room message envelopes to the database.
|
||||
pub type PersistFn = Arc<dyn Fn(Vec<RoomMessageEnvelope>) -> PersistFut + Send + Sync>;
|
||||
pub type PersistFut = std::pin::Pin<Box<dyn std::future::Future<Output = anyhow::Result<()>> + Send>>;
|
||||
pub type PersistFut =
|
||||
std::pin::Pin<Box<dyn std::future::Future<Output = anyhow::Result<()>> + Send>>;
|
||||
|
||||
/// Start the room message worker that consumes from NATS JetStream per room.
|
||||
pub async fn start(
|
||||
@ -131,14 +139,8 @@ async fn consume_once(
|
||||
|
||||
// Fetch up to BATCH_SIZE messages
|
||||
for _ in 0..BATCH_SIZE {
|
||||
match tokio::time::timeout(
|
||||
std::time::Duration::from_millis(500),
|
||||
messages.next(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(Some(Ok(msg))) => {
|
||||
match serde_json::from_slice::<RoomMessageEvent>(&msg.payload) {
|
||||
match tokio::time::timeout(std::time::Duration::from_millis(500), messages.next()).await {
|
||||
Ok(Some(Ok(msg))) => match serde_json::from_slice::<RoomMessageEvent>(&msg.payload) {
|
||||
Ok(event) => {
|
||||
let env = RoomMessageEnvelope::from(event);
|
||||
batch.push(env);
|
||||
@ -148,8 +150,7 @@ async fn consume_once(
|
||||
tracing::warn!(error = %e, "malformed envelope");
|
||||
let _ = msg.ack().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Ok(Some(Err(e))) => {
|
||||
tracing::warn!(error = %e, "message error");
|
||||
}
|
||||
@ -181,7 +182,8 @@ async fn consume_once(
|
||||
|
||||
/// Email send function type.
|
||||
pub type EmailSendFn = Arc<dyn Fn(Vec<EmailEnvelope>) -> EmailSendFut + Send + Sync>;
|
||||
pub type EmailSendFut = std::pin::Pin<Box<dyn std::future::Future<Output = anyhow::Result<()>> + Send>>;
|
||||
pub type EmailSendFut =
|
||||
std::pin::Pin<Box<dyn std::future::Future<Output = anyhow::Result<()>> + Send>>;
|
||||
|
||||
/// Start the email worker that consumes from NATS JetStream.
|
||||
pub async fn start_email_worker(
|
||||
@ -255,14 +257,8 @@ async fn email_consume_once(
|
||||
let mut acks: Vec<async_nats::jetstream::message::Message> = Vec::new();
|
||||
|
||||
for _ in 0..BATCH_SIZE {
|
||||
match tokio::time::timeout(
|
||||
std::time::Duration::from_millis(500),
|
||||
messages.next(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(Some(Ok(msg))) => {
|
||||
match serde_json::from_slice::<EmailEnvelope>(&msg.payload) {
|
||||
match tokio::time::timeout(std::time::Duration::from_millis(500), messages.next()).await {
|
||||
Ok(Some(Ok(msg))) => match serde_json::from_slice::<EmailEnvelope>(&msg.payload) {
|
||||
Ok(env) => {
|
||||
batch.push(env);
|
||||
acks.push(msg);
|
||||
@ -271,8 +267,7 @@ async fn email_consume_once(
|
||||
tracing::warn!(error = %e, "malformed email envelope");
|
||||
let _ = msg.ack().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Ok(Some(Err(e))) => {
|
||||
tracing::warn!(error = %e, "email message error");
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user