feat: add health endpoints and Prometheus metrics to git-hook and email-worker
Health monitoring: - gitserver: /health endpoint on port 8021 (DB + Redis ping) - git-hook: hyper health server on port 8083 with /health - email-worker: hyper health server on port 8084 with /health - K8s probes updated to httpGet for all three services Metrics (via /metrics endpoint): - git-hook: hook_tasks_total/success/failed/locked/retried/exhausted, hook_sync_branches/tags_changed_total - email: email_queued/consumed/sent/failed_total, email_validation_skipped/build_errors/send_attempts_total
This commit is contained in:
parent
8b47f677bb
commit
10836730ed
11
Cargo.lock
generated
11
Cargo.lock
generated
@ -2510,6 +2510,7 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"config",
|
"config",
|
||||||
"lettre",
|
"lettre",
|
||||||
|
"metrics",
|
||||||
"regex",
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
"tokio",
|
"tokio",
|
||||||
@ -2535,7 +2536,11 @@ dependencies = [
|
|||||||
"clap",
|
"clap",
|
||||||
"config",
|
"config",
|
||||||
"db",
|
"db",
|
||||||
|
"hyper 0.14.32",
|
||||||
|
"metrics-exporter-prometheus",
|
||||||
"observability",
|
"observability",
|
||||||
|
"sea-orm",
|
||||||
|
"serde_json",
|
||||||
"service",
|
"service",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
@ -3091,6 +3096,7 @@ dependencies = [
|
|||||||
"git2-hooks",
|
"git2-hooks",
|
||||||
"globset",
|
"globset",
|
||||||
"hex",
|
"hex",
|
||||||
|
"metrics",
|
||||||
"models",
|
"models",
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
"qdrant-client",
|
"qdrant-client",
|
||||||
@ -3122,8 +3128,12 @@ dependencies = [
|
|||||||
"config",
|
"config",
|
||||||
"db",
|
"db",
|
||||||
"git",
|
"git",
|
||||||
|
"hyper 0.14.32",
|
||||||
|
"metrics-exporter-prometheus",
|
||||||
"observability",
|
"observability",
|
||||||
"reqwest 0.13.2",
|
"reqwest 0.13.2",
|
||||||
|
"sea-orm",
|
||||||
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"tracing",
|
"tracing",
|
||||||
@ -6266,6 +6276,7 @@ dependencies = [
|
|||||||
"chrono",
|
"chrono",
|
||||||
"deadpool-redis",
|
"deadpool-redis",
|
||||||
"futures",
|
"futures",
|
||||||
|
"metrics",
|
||||||
"redis",
|
"redis",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|||||||
@ -169,6 +169,7 @@ base64 = "0.22.1"
|
|||||||
base64ct = "1"
|
base64ct = "1"
|
||||||
p256 = { version = "0.13", features = ["ecdsa", "std"] }
|
p256 = { version = "0.13", features = ["ecdsa", "std"] }
|
||||||
http = "1"
|
http = "1"
|
||||||
|
hyper = "0.14"
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
rig-core = "0.30"
|
rig-core = "0.30"
|
||||||
|
|
||||||
|
|||||||
@ -26,6 +26,10 @@ observability = { workspace = true }
|
|||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
clap = { workspace = true, features = ["derive"] }
|
clap = { workspace = true, features = ["derive"] }
|
||||||
chrono = { workspace = true, features = ["serde"] }
|
chrono = { workspace = true, features = ["serde"] }
|
||||||
|
hyper = { workspace = true }
|
||||||
|
serde_json = { workspace = true }
|
||||||
|
sea-orm = { workspace = true }
|
||||||
|
metrics-exporter-prometheus = "0.13"
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use config::AppConfig;
|
use config::AppConfig;
|
||||||
use observability::init_tracing_subscriber;
|
use metrics_exporter_prometheus::PrometheusHandle;
|
||||||
|
use observability::{init_tracing_subscriber, install_recorder};
|
||||||
|
use sea_orm::ConnectionTrait;
|
||||||
use service::AppService;
|
use service::AppService;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(name = "email-worker")]
|
#[command(name = "email-worker")]
|
||||||
@ -11,15 +14,64 @@ struct Args {
|
|||||||
log_level: String,
|
log_level: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn http_handler(
|
||||||
|
db: Arc<db::database::AppDatabase>,
|
||||||
|
cache: Arc<db::cache::AppCache>,
|
||||||
|
metrics: Arc<PrometheusHandle>,
|
||||||
|
req: hyper::Request<hyper::Body>,
|
||||||
|
) -> Result<hyper::Response<hyper::Body>, std::convert::Infallible> {
|
||||||
|
match req.uri().path() {
|
||||||
|
"/health" => {
|
||||||
|
let db_ok = db
|
||||||
|
.query_one_raw(sea_orm::Statement::from_string(
|
||||||
|
sea_orm::DbBackend::Postgres,
|
||||||
|
"SELECT 1",
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.is_ok();
|
||||||
|
let cache_ok = cache.conn().await.is_ok();
|
||||||
|
|
||||||
|
let body = serde_json::json!({
|
||||||
|
"status": if db_ok && cache_ok { "ok" } else { "unhealthy" },
|
||||||
|
"db": if db_ok { "ok" } else { "error" },
|
||||||
|
"cache": if cache_ok { "ok" } else { "error" },
|
||||||
|
});
|
||||||
|
|
||||||
|
let status = if db_ok && cache_ok { 200 } else { 503 };
|
||||||
|
Ok(hyper::Response::builder()
|
||||||
|
.status(status)
|
||||||
|
.header("content-type", "application/json")
|
||||||
|
.body(hyper::Body::from(serde_json::to_string(&body).unwrap()))
|
||||||
|
.unwrap())
|
||||||
|
}
|
||||||
|
"/metrics" => {
|
||||||
|
let body = metrics.render();
|
||||||
|
Ok(hyper::Response::builder()
|
||||||
|
.status(200)
|
||||||
|
.header("content-type", "text/plain; version=0.0.4; charset=utf-8")
|
||||||
|
.body(hyper::Body::from(body))
|
||||||
|
.unwrap())
|
||||||
|
}
|
||||||
|
_ => Ok(hyper::Response::builder()
|
||||||
|
.status(404)
|
||||||
|
.body(hyper::Body::from("not found"))
|
||||||
|
.unwrap()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
let cfg = AppConfig::load();
|
let cfg = AppConfig::load();
|
||||||
init_tracing_subscriber(&args.log_level, false);
|
init_tracing_subscriber(&args.log_level, false);
|
||||||
|
let metrics_handle = Arc::new(install_recorder());
|
||||||
|
|
||||||
tracing::info!("Starting email worker");
|
tracing::info!("Starting email worker");
|
||||||
let service = AppService::new(cfg).await?;
|
let service = AppService::new(cfg).await?;
|
||||||
|
|
||||||
|
let db = Arc::new(service.db.clone());
|
||||||
|
let cache = Arc::new(service.cache.clone());
|
||||||
|
|
||||||
let (shutdown_tx, shutdown_rx) = tokio::sync::broadcast::channel::<()>(1);
|
let (shutdown_tx, shutdown_rx) = tokio::sync::broadcast::channel::<()>(1);
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
tokio::signal::ctrl_c().await.ok();
|
tokio::signal::ctrl_c().await.ok();
|
||||||
@ -27,6 +79,29 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
let _ = shutdown_tx.send(());
|
let _ = shutdown_tx.send(());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Start health/metrics server on a dedicated port
|
||||||
|
let health_db = db.clone();
|
||||||
|
let health_cache = cache.clone();
|
||||||
|
let health_metrics = metrics_handle.clone();
|
||||||
|
let health_addr: std::net::SocketAddr = ([0, 0, 0, 0], 8084).into();
|
||||||
|
let health_service = hyper::service::make_service_fn(move |_| {
|
||||||
|
let db = health_db.clone();
|
||||||
|
let cache = health_cache.clone();
|
||||||
|
let metrics = health_metrics.clone();
|
||||||
|
let service = hyper::service::service_fn(move |req| {
|
||||||
|
http_handler(db.clone(), cache.clone(), metrics.clone(), req)
|
||||||
|
});
|
||||||
|
async move { Ok::<_, std::convert::Infallible>(service) }
|
||||||
|
});
|
||||||
|
|
||||||
|
let health_server = hyper::Server::bind(&health_addr).serve(health_service);
|
||||||
|
tracing::info!(port = 8084, "health/metrics server started");
|
||||||
|
tokio::spawn(async move {
|
||||||
|
if let Err(e) = health_server.await {
|
||||||
|
tracing::error!("health check server error: {}", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
service.start_email_workers(shutdown_rx).await?;
|
service.start_email_workers(shutdown_rx).await?;
|
||||||
tracing::info!("email worker stopped");
|
tracing::info!("email worker stopped");
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@ -23,5 +23,9 @@ tracing-subscriber = { workspace = true, features = ["json"] }
|
|||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
clap = { workspace = true, features = ["derive"] }
|
clap = { workspace = true, features = ["derive"] }
|
||||||
tokio-util = { workspace = true }
|
tokio-util = { workspace = true }
|
||||||
|
hyper = { workspace = true }
|
||||||
|
serde_json = { workspace = true }
|
||||||
|
sea-orm = { workspace = true }
|
||||||
|
metrics-exporter-prometheus = "0.13"
|
||||||
chrono = { workspace = true, features = ["serde"] }
|
chrono = { workspace = true, features = ["serde"] }
|
||||||
reqwest = { workspace = true }
|
reqwest = { workspace = true }
|
||||||
|
|||||||
@ -3,28 +3,77 @@ use config::AppConfig;
|
|||||||
use db::cache::AppCache;
|
use db::cache::AppCache;
|
||||||
use db::database::AppDatabase;
|
use db::database::AppDatabase;
|
||||||
use git::hook::HookService;
|
use git::hook::HookService;
|
||||||
use observability::init_tracing_subscriber;
|
use metrics_exporter_prometheus::PrometheusHandle;
|
||||||
|
use observability::{init_tracing_subscriber, install_recorder};
|
||||||
|
use sea_orm::ConnectionTrait;
|
||||||
|
use std::sync::Arc;
|
||||||
use tokio::signal;
|
use tokio::signal;
|
||||||
|
|
||||||
mod args;
|
mod args;
|
||||||
|
|
||||||
use args::HookArgs;
|
use args::HookArgs;
|
||||||
|
|
||||||
|
async fn http_handler(
|
||||||
|
db: Arc<AppDatabase>,
|
||||||
|
cache: Arc<AppCache>,
|
||||||
|
metrics: Arc<PrometheusHandle>,
|
||||||
|
req: hyper::Request<hyper::Body>,
|
||||||
|
) -> Result<hyper::Response<hyper::Body>, std::convert::Infallible> {
|
||||||
|
match req.uri().path() {
|
||||||
|
"/health" => {
|
||||||
|
let db_ok = db
|
||||||
|
.query_one_raw(sea_orm::Statement::from_string(
|
||||||
|
sea_orm::DbBackend::Postgres,
|
||||||
|
"SELECT 1",
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.is_ok();
|
||||||
|
let cache_ok = cache.conn().await.is_ok();
|
||||||
|
|
||||||
|
let body = serde_json::json!({
|
||||||
|
"status": if db_ok && cache_ok { "ok" } else { "unhealthy" },
|
||||||
|
"db": if db_ok { "ok" } else { "error" },
|
||||||
|
"cache": if cache_ok { "ok" } else { "error" },
|
||||||
|
});
|
||||||
|
|
||||||
|
let status = if db_ok && cache_ok { 200 } else { 503 };
|
||||||
|
Ok(hyper::Response::builder()
|
||||||
|
.status(status)
|
||||||
|
.header("content-type", "application/json")
|
||||||
|
.body(hyper::Body::from(serde_json::to_string(&body).unwrap()))
|
||||||
|
.unwrap())
|
||||||
|
}
|
||||||
|
"/metrics" => {
|
||||||
|
let body = metrics.render();
|
||||||
|
Ok(hyper::Response::builder()
|
||||||
|
.status(200)
|
||||||
|
.header("content-type", "text/plain; version=0.0.4; charset=utf-8")
|
||||||
|
.body(hyper::Body::from(body))
|
||||||
|
.unwrap())
|
||||||
|
}
|
||||||
|
_ => Ok(hyper::Response::builder()
|
||||||
|
.status(404)
|
||||||
|
.body(hyper::Body::from("not found"))
|
||||||
|
.unwrap()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
// 1. Load configuration
|
// 1. Load configuration
|
||||||
let cfg = AppConfig::load();
|
let cfg = AppConfig::load();
|
||||||
|
|
||||||
// 2. Init tracing logging
|
// 2. Init tracing + metrics
|
||||||
let log_level = cfg.log_level().unwrap_or_else(|_| "info".to_string());
|
let log_level = cfg.log_level().unwrap_or_else(|_| "info".to_string());
|
||||||
init_tracing_subscriber(&log_level, false);
|
init_tracing_subscriber(&log_level, false);
|
||||||
|
let metrics_handle = Arc::new(install_recorder());
|
||||||
|
|
||||||
// 3. Connect to database
|
// 3. Connect to database
|
||||||
let db = AppDatabase::init(&cfg).await?;
|
let db = Arc::new(AppDatabase::init(&cfg).await?);
|
||||||
tracing::info!("database connected");
|
tracing::info!("database connected");
|
||||||
|
|
||||||
// 4. Connect to Redis cache (also provides the cluster pool for hook queue)
|
// 4. Connect to Redis cache (also provides the cluster pool for hook queue)
|
||||||
let cache = AppCache::init(&cfg).await?;
|
let cache = Arc::new(AppCache::init(&cfg).await?);
|
||||||
tracing::info!("cache connected");
|
tracing::info!("cache connected");
|
||||||
|
|
||||||
// 5. Parse CLI args
|
// 5. Parse CLI args
|
||||||
@ -34,8 +83,8 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
// 6. Build and start git hook service
|
// 6. Build and start git hook service
|
||||||
let hooks = HookService::new(
|
let hooks = HookService::new(
|
||||||
db,
|
(*db).clone(),
|
||||||
cache.clone(),
|
(*cache).clone(),
|
||||||
cache.redis_pool().clone(),
|
cache.redis_pool().clone(),
|
||||||
cfg,
|
cfg,
|
||||||
);
|
);
|
||||||
@ -43,6 +92,30 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
let cancel = hooks.start_worker();
|
let cancel = hooks.start_worker();
|
||||||
let cancel_signal = cancel.clone();
|
let cancel_signal = cancel.clone();
|
||||||
|
|
||||||
|
// 7. Start health/metrics server on a dedicated port
|
||||||
|
let health_db = db.clone();
|
||||||
|
let health_cache = cache.clone();
|
||||||
|
let health_metrics = metrics_handle.clone();
|
||||||
|
let health_addr: std::net::SocketAddr =
|
||||||
|
([0, 0, 0, 0], 8083).into();
|
||||||
|
let health_service = hyper::service::make_service_fn(move |_| {
|
||||||
|
let db = health_db.clone();
|
||||||
|
let cache = health_cache.clone();
|
||||||
|
let metrics = health_metrics.clone();
|
||||||
|
let service = hyper::service::service_fn(move |req| {
|
||||||
|
http_handler(db.clone(), cache.clone(), metrics.clone(), req)
|
||||||
|
});
|
||||||
|
async move { Ok::<_, std::convert::Infallible>(service) }
|
||||||
|
});
|
||||||
|
|
||||||
|
let health_server = hyper::Server::bind(&health_addr).serve(health_service);
|
||||||
|
tracing::info!(port = 8083, "health/metrics server started");
|
||||||
|
tokio::spawn(async move {
|
||||||
|
if let Err(e) = health_server.await {
|
||||||
|
tracing::error!("health check server error: {}", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Spawn signal handler that cancels on SIGINT/SIGTERM
|
// Spawn signal handler that cancels on SIGINT/SIGTERM
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let ctrl_c = async {
|
let ctrl_c = async {
|
||||||
|
|||||||
@ -30,6 +30,10 @@ spec:
|
|||||||
- name: email-worker
|
- name: email-worker
|
||||||
image: "{{ .Values.image.registry }}/{{ .Values.emailWorker.image.repository }}:{{ .Values.emailWorker.image.tag }}"
|
image: "{{ .Values.image.registry }}/{{ .Values.emailWorker.image.repository }}:{{ .Values.emailWorker.image.tag }}"
|
||||||
imagePullPolicy: {{ .Values.emailWorker.image.pullPolicy | default .Values.image.pullPolicy }}
|
imagePullPolicy: {{ .Values.emailWorker.image.pullPolicy | default .Values.image.pullPolicy }}
|
||||||
|
ports:
|
||||||
|
- name: health
|
||||||
|
containerPort: 8084
|
||||||
|
protocol: TCP
|
||||||
envFrom:
|
envFrom:
|
||||||
- configMapRef:
|
- configMapRef:
|
||||||
name: {{ include "gitdata.fullname" . }}-config
|
name: {{ include "gitdata.fullname" . }}-config
|
||||||
@ -39,11 +43,17 @@ spec:
|
|||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if .Values.emailWorker.livenessProbe }}
|
{{- if .Values.emailWorker.livenessProbe }}
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
|
{{- if .Values.emailWorker.livenessProbe.httpGet }}
|
||||||
|
httpGet:
|
||||||
|
path: {{ .Values.emailWorker.livenessProbe.httpGet.path }}
|
||||||
|
port: {{ .Values.emailWorker.livenessProbe.httpGet.port }}
|
||||||
|
{{- else }}
|
||||||
exec:
|
exec:
|
||||||
command:
|
command:
|
||||||
{{- range .Values.emailWorker.livenessProbe.exec.command }}
|
{{- range .Values.emailWorker.livenessProbe.exec.command }}
|
||||||
- {{ . | quote }}
|
- {{ . | quote }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
initialDelaySeconds: {{ .Values.emailWorker.livenessProbe.initialDelaySeconds }}
|
initialDelaySeconds: {{ .Values.emailWorker.livenessProbe.initialDelaySeconds }}
|
||||||
periodSeconds: {{ .Values.emailWorker.livenessProbe.periodSeconds }}
|
periodSeconds: {{ .Values.emailWorker.livenessProbe.periodSeconds }}
|
||||||
timeoutSeconds: {{ .Values.emailWorker.livenessProbe.timeoutSeconds }}
|
timeoutSeconds: {{ .Values.emailWorker.livenessProbe.timeoutSeconds }}
|
||||||
@ -51,11 +61,17 @@ spec:
|
|||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if .Values.emailWorker.readinessProbe }}
|
{{- if .Values.emailWorker.readinessProbe }}
|
||||||
readinessProbe:
|
readinessProbe:
|
||||||
|
{{- if .Values.emailWorker.readinessProbe.httpGet }}
|
||||||
|
httpGet:
|
||||||
|
path: {{ .Values.emailWorker.readinessProbe.httpGet.path }}
|
||||||
|
port: {{ .Values.emailWorker.readinessProbe.httpGet.port }}
|
||||||
|
{{- else }}
|
||||||
exec:
|
exec:
|
||||||
command:
|
command:
|
||||||
{{- range .Values.emailWorker.readinessProbe.exec.command }}
|
{{- range .Values.emailWorker.readinessProbe.exec.command }}
|
||||||
- {{ . | quote }}
|
- {{ . | quote }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
initialDelaySeconds: {{ .Values.emailWorker.readinessProbe.initialDelaySeconds }}
|
initialDelaySeconds: {{ .Values.emailWorker.readinessProbe.initialDelaySeconds }}
|
||||||
periodSeconds: {{ .Values.emailWorker.readinessProbe.periodSeconds }}
|
periodSeconds: {{ .Values.emailWorker.readinessProbe.periodSeconds }}
|
||||||
timeoutSeconds: {{ .Values.emailWorker.readinessProbe.timeoutSeconds }}
|
timeoutSeconds: {{ .Values.emailWorker.readinessProbe.timeoutSeconds }}
|
||||||
|
|||||||
@ -30,6 +30,10 @@ spec:
|
|||||||
- name: git-hook
|
- name: git-hook
|
||||||
image: "{{ .Values.image.registry }}/{{ .Values.gitHook.image.repository }}:{{ .Values.gitHook.image.tag }}"
|
image: "{{ .Values.image.registry }}/{{ .Values.gitHook.image.repository }}:{{ .Values.gitHook.image.tag }}"
|
||||||
imagePullPolicy: {{ .Values.gitHook.image.pullPolicy | default .Values.image.pullPolicy }}
|
imagePullPolicy: {{ .Values.gitHook.image.pullPolicy | default .Values.image.pullPolicy }}
|
||||||
|
ports:
|
||||||
|
- name: health
|
||||||
|
containerPort: 8083
|
||||||
|
protocol: TCP
|
||||||
envFrom:
|
envFrom:
|
||||||
- configMapRef:
|
- configMapRef:
|
||||||
name: {{ include "gitdata.fullname" . }}-config
|
name: {{ include "gitdata.fullname" . }}-config
|
||||||
@ -42,11 +46,17 @@ spec:
|
|||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if .Values.gitHook.livenessProbe }}
|
{{- if .Values.gitHook.livenessProbe }}
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
|
{{- if .Values.gitHook.livenessProbe.httpGet }}
|
||||||
|
httpGet:
|
||||||
|
path: {{ .Values.gitHook.livenessProbe.httpGet.path }}
|
||||||
|
port: {{ .Values.gitHook.livenessProbe.httpGet.port }}
|
||||||
|
{{- else }}
|
||||||
exec:
|
exec:
|
||||||
command:
|
command:
|
||||||
{{- range .Values.gitHook.livenessProbe.exec.command }}
|
{{- range .Values.gitHook.livenessProbe.exec.command }}
|
||||||
- {{ . | quote }}
|
- {{ . | quote }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
initialDelaySeconds: {{ .Values.gitHook.livenessProbe.initialDelaySeconds }}
|
initialDelaySeconds: {{ .Values.gitHook.livenessProbe.initialDelaySeconds }}
|
||||||
periodSeconds: {{ .Values.gitHook.livenessProbe.periodSeconds }}
|
periodSeconds: {{ .Values.gitHook.livenessProbe.periodSeconds }}
|
||||||
timeoutSeconds: {{ .Values.gitHook.livenessProbe.timeoutSeconds }}
|
timeoutSeconds: {{ .Values.gitHook.livenessProbe.timeoutSeconds }}
|
||||||
@ -54,11 +64,17 @@ spec:
|
|||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if .Values.gitHook.readinessProbe }}
|
{{- if .Values.gitHook.readinessProbe }}
|
||||||
readinessProbe:
|
readinessProbe:
|
||||||
|
{{- if .Values.gitHook.readinessProbe.httpGet }}
|
||||||
|
httpGet:
|
||||||
|
path: {{ .Values.gitHook.readinessProbe.httpGet.path }}
|
||||||
|
port: {{ .Values.gitHook.readinessProbe.httpGet.port }}
|
||||||
|
{{- else }}
|
||||||
exec:
|
exec:
|
||||||
command:
|
command:
|
||||||
{{- range .Values.gitHook.readinessProbe.exec.command }}
|
{{- range .Values.gitHook.readinessProbe.exec.command }}
|
||||||
- {{ . | quote }}
|
- {{ . | quote }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
initialDelaySeconds: {{ .Values.gitHook.readinessProbe.initialDelaySeconds }}
|
initialDelaySeconds: {{ .Values.gitHook.readinessProbe.initialDelaySeconds }}
|
||||||
periodSeconds: {{ .Values.gitHook.readinessProbe.periodSeconds }}
|
periodSeconds: {{ .Values.gitHook.readinessProbe.periodSeconds }}
|
||||||
timeoutSeconds: {{ .Values.gitHook.readinessProbe.timeoutSeconds }}
|
timeoutSeconds: {{ .Values.gitHook.readinessProbe.timeoutSeconds }}
|
||||||
|
|||||||
@ -44,6 +44,29 @@ spec:
|
|||||||
- name: ssh
|
- name: ssh
|
||||||
containerPort: {{ $svc.service.ssh.port }}
|
containerPort: {{ $svc.service.ssh.port }}
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
|
- name: health
|
||||||
|
containerPort: 8021
|
||||||
|
protocol: TCP
|
||||||
|
{{- if $svc.livenessProbe }}
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: {{ $svc.livenessProbe.path }}
|
||||||
|
port: {{ $svc.livenessProbe.port }}
|
||||||
|
initialDelaySeconds: {{ $svc.livenessProbe.initialDelaySeconds }}
|
||||||
|
periodSeconds: {{ $svc.livenessProbe.periodSeconds }}
|
||||||
|
timeoutSeconds: {{ $svc.livenessProbe.timeoutSeconds | default 3 }}
|
||||||
|
failureThreshold: {{ $svc.livenessProbe.failureThreshold | default 3 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if $svc.readinessProbe }}
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: {{ $svc.readinessProbe.path }}
|
||||||
|
port: {{ $svc.readinessProbe.port }}
|
||||||
|
initialDelaySeconds: {{ $svc.readinessProbe.initialDelaySeconds }}
|
||||||
|
periodSeconds: {{ $svc.readinessProbe.periodSeconds }}
|
||||||
|
timeoutSeconds: {{ $svc.readinessProbe.timeoutSeconds | default 3 }}
|
||||||
|
failureThreshold: {{ $svc.readinessProbe.failureThreshold | default 3 }}
|
||||||
|
{{- end }}
|
||||||
envFrom:
|
envFrom:
|
||||||
- configMapRef:
|
- configMapRef:
|
||||||
name: {{ $fullName }}-config
|
name: {{ $fullName }}-config
|
||||||
|
|||||||
@ -255,6 +255,24 @@ gitserver:
|
|||||||
|
|
||||||
env: []
|
env: []
|
||||||
|
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 8021
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 3
|
||||||
|
failureThreshold: 3
|
||||||
|
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 8021
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 5
|
||||||
|
timeoutSeconds: 3
|
||||||
|
failureThreshold: 3
|
||||||
|
|
||||||
nodeSelector: {}
|
nodeSelector: {}
|
||||||
tolerations: []
|
tolerations: []
|
||||||
affinity: {}
|
affinity: {}
|
||||||
@ -270,22 +288,18 @@ emailWorker:
|
|||||||
tag: latest
|
tag: latest
|
||||||
|
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
exec:
|
httpGet:
|
||||||
command:
|
path: /health
|
||||||
- /bin/sh
|
port: 8084
|
||||||
- -c
|
|
||||||
- "kill -0 1 || exit 1"
|
|
||||||
initialDelaySeconds: 10
|
initialDelaySeconds: 10
|
||||||
periodSeconds: 30
|
periodSeconds: 30
|
||||||
timeoutSeconds: 5
|
timeoutSeconds: 5
|
||||||
failureThreshold: 3
|
failureThreshold: 3
|
||||||
|
|
||||||
readinessProbe:
|
readinessProbe:
|
||||||
exec:
|
httpGet:
|
||||||
command:
|
path: /health
|
||||||
- /bin/sh
|
port: 8084
|
||||||
- -c
|
|
||||||
- "kill -0 1 || exit 1"
|
|
||||||
initialDelaySeconds: 5
|
initialDelaySeconds: 5
|
||||||
periodSeconds: 15
|
periodSeconds: 15
|
||||||
timeoutSeconds: 3
|
timeoutSeconds: 3
|
||||||
@ -319,22 +333,18 @@ gitHook:
|
|||||||
minAvailable: 1
|
minAvailable: 1
|
||||||
|
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
exec:
|
httpGet:
|
||||||
command:
|
path: /health
|
||||||
- /bin/sh
|
port: 8083
|
||||||
- -c
|
|
||||||
- "kill -0 1 || exit 1"
|
|
||||||
initialDelaySeconds: 10
|
initialDelaySeconds: 10
|
||||||
periodSeconds: 15
|
periodSeconds: 15
|
||||||
timeoutSeconds: 5
|
timeoutSeconds: 5
|
||||||
failureThreshold: 3
|
failureThreshold: 3
|
||||||
|
|
||||||
readinessProbe:
|
readinessProbe:
|
||||||
exec:
|
httpGet:
|
||||||
command:
|
path: /health
|
||||||
- /bin/sh
|
port: 8083
|
||||||
- -c
|
|
||||||
- "kill -0 1 || exit 1"
|
|
||||||
initialDelaySeconds: 5
|
initialDelaySeconds: 5
|
||||||
periodSeconds: 10
|
periodSeconds: 10
|
||||||
timeoutSeconds: 3
|
timeoutSeconds: 3
|
||||||
|
|||||||
@ -22,5 +22,6 @@ serde = { workspace = true, features = ["derive"] }
|
|||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
regex = { workspace = true }
|
regex = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
|
metrics = { workspace = true }
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
@ -2,6 +2,7 @@ use config::AppConfig;
|
|||||||
use lettre::message::Mailbox;
|
use lettre::message::Mailbox;
|
||||||
use lettre::transport::smtp::{PoolConfig, SmtpTransport};
|
use lettre::transport::smtp::{PoolConfig, SmtpTransport};
|
||||||
use lettre::Transport;
|
use lettre::Transport;
|
||||||
|
use metrics::counter;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
@ -64,6 +65,7 @@ impl AppEmail {
|
|||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
while let Some(msg) = rx.recv().await {
|
while let Some(msg) = rx.recv().await {
|
||||||
if !EMAIL_REGEX.is_match(&msg.clone().to) {
|
if !EMAIL_REGEX.is_match(&msg.clone().to) {
|
||||||
|
counter!("email_validation_skipped_total").increment(1);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,12 +77,14 @@ impl AppEmail {
|
|||||||
{
|
{
|
||||||
Ok(e) => e,
|
Ok(e) => e,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
|
counter!("email_build_errors_total").increment(1);
|
||||||
tracing::warn!(to = %msg.to, "Email build error");
|
tracing::warn!(to = %msg.to, "Email build error");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut success = false;
|
let mut success = false;
|
||||||
for i in 0..3 {
|
for i in 0..3 {
|
||||||
|
counter!("email_send_attempts_total").increment(1);
|
||||||
let mailer = mailer.clone();
|
let mailer = mailer.clone();
|
||||||
let email = email.clone();
|
let email = email.clone();
|
||||||
let result = tokio::task::spawn_blocking(move || mailer.send(&email)).await;
|
let result = tokio::task::spawn_blocking(move || mailer.send(&email)).await;
|
||||||
@ -92,6 +96,7 @@ impl AppEmail {
|
|||||||
}
|
}
|
||||||
Ok(Err(e)) => {
|
Ok(Err(e)) => {
|
||||||
if i == 2 {
|
if i == 2 {
|
||||||
|
counter!("email_send_failures_total").increment(1);
|
||||||
tracing::error!(to = %msg.to, error = %e, "Email send failed after retries");
|
tracing::error!(to = %msg.to, error = %e, "Email send failed after retries");
|
||||||
}
|
}
|
||||||
tokio::time::sleep(Duration::from_secs((1 << i) as u64)).await;
|
tokio::time::sleep(Duration::from_secs((1 << i) as u64)).await;
|
||||||
@ -103,8 +108,8 @@ impl AppEmail {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !success {
|
if success {
|
||||||
tracing::warn!(to = %msg.to, "Email send permanently failed");
|
counter!("email_sent_total").increment(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -50,5 +50,6 @@ ssh-key = { workspace = true }
|
|||||||
actix-web = { workspace = true }
|
actix-web = { workspace = true }
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
reqwest = { workspace = true }
|
reqwest = { workspace = true }
|
||||||
|
metrics = { workspace = true }
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
@ -4,6 +4,7 @@ use crate::hook::pool::types::{HookTask, TaskType};
|
|||||||
use crate::hook::sync::HookMetaDataSync;
|
use crate::hook::sync::HookMetaDataSync;
|
||||||
use db::cache::AppCache;
|
use db::cache::AppCache;
|
||||||
use db::database::AppDatabase;
|
use db::database::AppDatabase;
|
||||||
|
use metrics::counter;
|
||||||
use models::EntityTrait;
|
use models::EntityTrait;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@ -92,6 +93,8 @@ impl HookWorker {
|
|||||||
tracing::info!("task started task_id={} task_type={} repo_id={}",
|
tracing::info!("task started task_id={} task_type={} repo_id={}",
|
||||||
task.id, task.task_type, task.repo_id);
|
task.id, task.task_type, task.repo_id);
|
||||||
|
|
||||||
|
counter!("hook_tasks_total", "task_type" => task.task_type.to_string()).increment(1);
|
||||||
|
|
||||||
let result = match task.task_type {
|
let result = match task.task_type {
|
||||||
TaskType::Sync => self.run_sync(&task.repo_id).await,
|
TaskType::Sync => self.run_sync(&task.repo_id).await,
|
||||||
TaskType::Fsck => self.run_fsck(&task.repo_id).await,
|
TaskType::Fsck => self.run_fsck(&task.repo_id).await,
|
||||||
@ -100,6 +103,7 @@ impl HookWorker {
|
|||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
|
counter!("hook_tasks_success_total", "task_type" => task.task_type.to_string()).increment(1);
|
||||||
if let Err(e) = self.consumer.ack(work_key, task_json).await {
|
if let Err(e) = self.consumer.ack(work_key, task_json).await {
|
||||||
tracing::warn!("failed to ack task: {}", e);
|
tracing::warn!("failed to ack task: {}", e);
|
||||||
}
|
}
|
||||||
@ -109,20 +113,24 @@ impl HookWorker {
|
|||||||
let is_locked = matches!(e, crate::GitError::Locked(_));
|
let is_locked = matches!(e, crate::GitError::Locked(_));
|
||||||
|
|
||||||
if is_locked {
|
if is_locked {
|
||||||
|
counter!("hook_tasks_locked_total").increment(1);
|
||||||
// Another worker holds the lock — requeue without counting as retry.
|
// Another worker holds the lock — requeue without counting as retry.
|
||||||
tracing::info!("repo locked by another worker, requeueing task_id={}", task.id);
|
tracing::info!("repo locked by another worker, requeueing task_id={}", task.id);
|
||||||
if let Err(nak_err) = self.consumer.nak(work_key, queue_key, task_json).await {
|
if let Err(nak_err) = self.consumer.nak(work_key, queue_key, task_json).await {
|
||||||
tracing::warn!("failed to requeue locked task: {}", nak_err);
|
tracing::warn!("failed to requeue locked task: {}", nak_err);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
counter!("hook_tasks_failed_total", "task_type" => task.task_type.to_string()).increment(1);
|
||||||
tracing::warn!("task failed task_id={} task_type={} repo_id={} error={}",
|
tracing::warn!("task failed task_id={} task_type={} repo_id={} error={}",
|
||||||
task.id, task.task_type, task.repo_id, e);
|
task.id, task.task_type, task.repo_id, e);
|
||||||
|
|
||||||
if task.retry_count >= self.max_retries {
|
if task.retry_count >= self.max_retries {
|
||||||
|
counter!("hook_tasks_exhausted_total").increment(1);
|
||||||
tracing::warn!("task exhausted retries, discarding task_id={} retry_count={}",
|
tracing::warn!("task exhausted retries, discarding task_id={} retry_count={}",
|
||||||
task.id, task.retry_count);
|
task.id, task.retry_count);
|
||||||
let _ = self.consumer.ack(work_key, task_json).await;
|
let _ = self.consumer.ack(work_key, task_json).await;
|
||||||
} else {
|
} else {
|
||||||
|
counter!("hook_tasks_retried_total").increment(1);
|
||||||
let mut task = task.clone();
|
let mut task = task.clone();
|
||||||
task.retry_count += 1;
|
task.retry_count += 1;
|
||||||
let retry_json =
|
let retry_json =
|
||||||
@ -225,6 +233,7 @@ impl HookWorker {
|
|||||||
|
|
||||||
// Dispatch branch webhooks and collect handles
|
// Dispatch branch webhooks and collect handles
|
||||||
let mut handles = Vec::new();
|
let mut handles = Vec::new();
|
||||||
|
let mut branch_changes: u64 = 0;
|
||||||
for (branch, after_oid) in after_branch_tips {
|
for (branch, after_oid) in after_branch_tips {
|
||||||
let before_oid = before_branch_tips
|
let before_oid = before_branch_tips
|
||||||
.iter()
|
.iter()
|
||||||
@ -232,6 +241,7 @@ impl HookWorker {
|
|||||||
.map(|(_, o)| o.as_str());
|
.map(|(_, o)| o.as_str());
|
||||||
let changed = before_oid.map(|o| o != after_oid.as_str()).unwrap_or(true);
|
let changed = before_oid.map(|o| o != after_oid.as_str()).unwrap_or(true);
|
||||||
if changed {
|
if changed {
|
||||||
|
branch_changes += 1;
|
||||||
let before_oid = before_oid.map_or("0", |v| v).to_string();
|
let before_oid = before_oid.map_or("0", |v| v).to_string();
|
||||||
let branch_name = branch.clone();
|
let branch_name = branch.clone();
|
||||||
let h = tokio::spawn({
|
let h = tokio::spawn({
|
||||||
@ -266,6 +276,7 @@ impl HookWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Dispatch tag webhooks and collect handles
|
// Dispatch tag webhooks and collect handles
|
||||||
|
let mut tag_changes: u64 = 0;
|
||||||
for (tag, after_oid) in after_tag_tips {
|
for (tag, after_oid) in after_tag_tips {
|
||||||
let before_oid = before_tag_tips
|
let before_oid = before_tag_tips
|
||||||
.iter()
|
.iter()
|
||||||
@ -274,6 +285,7 @@ impl HookWorker {
|
|||||||
let is_new = before_oid.is_none();
|
let is_new = before_oid.is_none();
|
||||||
let was_updated = before_oid.map(|o| o != after_oid.as_str()).unwrap_or(false);
|
let was_updated = before_oid.map(|o| o != after_oid.as_str()).unwrap_or(false);
|
||||||
if is_new || was_updated {
|
if is_new || was_updated {
|
||||||
|
tag_changes += 1;
|
||||||
let before_oid = before_oid.map_or("0", |v| v).to_string();
|
let before_oid = before_oid.map_or("0", |v| v).to_string();
|
||||||
let tag_name = tag.clone();
|
let tag_name = tag.clone();
|
||||||
let h = tokio::spawn({
|
let h = tokio::spawn({
|
||||||
@ -311,6 +323,9 @@ impl HookWorker {
|
|||||||
let _ = h.await;
|
let _ = h.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
counter!("hook_sync_branches_changed_total").increment(branch_changes);
|
||||||
|
counter!("hook_sync_tags_changed_total").increment(tag_changes);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
use crate::hook::HookService;
|
use crate::hook::HookService;
|
||||||
use actix_web::{App, HttpServer, web};
|
use actix_web::{App, HttpServer, HttpResponse, web};
|
||||||
use config::AppConfig;
|
use config::AppConfig;
|
||||||
use db::cache::AppCache;
|
use db::cache::AppCache;
|
||||||
use db::database::AppDatabase;
|
use db::database::AppDatabase;
|
||||||
|
use sea_orm::ConnectionTrait;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
@ -21,8 +22,35 @@ pub struct HttpAppState {
|
|||||||
pub rate_limiter: Arc<rate_limit::RateLimiter>,
|
pub rate_limiter: Arc<rate_limit::RateLimiter>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn health(state: web::Data<HttpAppState>) -> HttpResponse {
|
||||||
|
let db_ok = state
|
||||||
|
.db
|
||||||
|
.query_one_raw(sea_orm::Statement::from_string(
|
||||||
|
sea_orm::DbBackend::Postgres,
|
||||||
|
"SELECT 1",
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.is_ok();
|
||||||
|
let cache_ok = state.cache.conn().await.is_ok();
|
||||||
|
|
||||||
|
if db_ok && cache_ok {
|
||||||
|
HttpResponse::Ok().json(serde_json::json!({
|
||||||
|
"status": "ok",
|
||||||
|
"db": "ok",
|
||||||
|
"cache": "ok",
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
HttpResponse::ServiceUnavailable().json(serde_json::json!({
|
||||||
|
"status": "unhealthy",
|
||||||
|
"db": if db_ok { "ok" } else { "error" },
|
||||||
|
"cache": if cache_ok { "ok" } else { "error" },
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn git_http_cfg(cfg: &mut web::ServiceConfig) {
|
pub fn git_http_cfg(cfg: &mut web::ServiceConfig) {
|
||||||
cfg.route(
|
cfg.route("/health", web::get().to(health))
|
||||||
|
.route(
|
||||||
"/{namespace}/{repo_name}.git/info/refs",
|
"/{namespace}/{repo_name}.git/info/refs",
|
||||||
web::get().to(routes::info_refs),
|
web::get().to(routes::info_refs),
|
||||||
)
|
)
|
||||||
|
|||||||
@ -29,6 +29,7 @@ thiserror = { workspace = true }
|
|||||||
uuid = { workspace = true, features = ["v7", "v4", "serde"] }
|
uuid = { workspace = true, features = ["v7", "v4", "serde"] }
|
||||||
chrono = { workspace = true, features = ["serde"] }
|
chrono = { workspace = true, features = ["serde"] }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
|
metrics = { workspace = true }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
@ -5,6 +5,7 @@ use crate::types::{
|
|||||||
RoomMessageEvent,
|
RoomMessageEvent,
|
||||||
};
|
};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
|
use metrics::counter;
|
||||||
use deadpool_redis::cluster::Connection as RedisConn;
|
use deadpool_redis::cluster::Connection as RedisConn;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@ -217,6 +218,7 @@ impl MessageProducer {
|
|||||||
.context("XADD email to Redis Stream")?;
|
.context("XADD email to Redis Stream")?;
|
||||||
|
|
||||||
tracing::info!(to = %envelope.to, entry_id = %entry_id, "email queued to stream");
|
tracing::info!(to = %envelope.to, entry_id = %entry_id, "email queued to stream");
|
||||||
|
counter!("email_queued_total").increment(1);
|
||||||
|
|
||||||
Ok(entry_id)
|
Ok(entry_id)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
//! Redis Streams consumer — delegates persistence to the caller.
|
//! Redis Streams consumer — delegates persistence to the caller.
|
||||||
|
|
||||||
use crate::types::{EmailEnvelope, RoomMessageEnvelope};
|
use crate::types::{EmailEnvelope, RoomMessageEnvelope};
|
||||||
|
use metrics::counter;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -262,6 +263,10 @@ async fn email_run_once(
|
|||||||
return Ok(0);
|
return Ok(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let batch_size = batch.len();
|
||||||
|
counter!("email_consumed_total").increment(batch_size as u64);
|
||||||
|
counter!("email_batch_size").increment(batch_size as u64);
|
||||||
|
|
||||||
let entry_ids: Vec<String> = batch.iter().map(|(id, _)| id.clone()).collect();
|
let entry_ids: Vec<String> = batch.iter().map(|(id, _)| id.clone()).collect();
|
||||||
let envelopes: Vec<EmailEnvelope> = batch.into_iter().map(|(_, e)| e).collect();
|
let envelopes: Vec<EmailEnvelope> = batch.into_iter().map(|(_, e)| e).collect();
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user