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