Import upstream v0.16.22, stripped
Upstream commit: 474dd0229cb20cf513036619781ed97bd8073c3f Enterprise-only files removed or emptied: 63 Enterprise-only snippets removed: 117 in 50 files Dangling module declarations removed: 5 Cargo edits turning enterprise off: 14 Verification: clean Enterprise feature gates left for rebuilt features: 19 in 18 files Produced by tools/fork/strip.py. The full report is in docs/fork/strip-reports/ on main.
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{LONG_1Y_SLUMBER, config::telemetry::WebhookTracer};
|
||||
use aws_lc_rs::hmac;
|
||||
use base64::{Engine, engine::general_purpose::STANDARD};
|
||||
use serde::Serialize;
|
||||
use std::{
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{AtomicBool, Ordering},
|
||||
},
|
||||
time::Instant,
|
||||
};
|
||||
use store::write::now;
|
||||
use tokio::sync::mpsc;
|
||||
use trc::{
|
||||
Event, EventDetails, ServerEvent, TelemetryEvent,
|
||||
ipc::subscriber::{EventBatch, SubscriberBuilder},
|
||||
serializers::json::JsonEventSerializer,
|
||||
};
|
||||
|
||||
pub(crate) fn spawn_webhook_tracer(builder: SubscriberBuilder, settings: WebhookTracer) {
|
||||
let (tx, mut rx) = builder.register();
|
||||
tokio::spawn(async move {
|
||||
let settings = Arc::new(settings);
|
||||
let mut wakeup_time = LONG_1Y_SLUMBER;
|
||||
let discard_after = settings.discard_after.as_secs();
|
||||
let mut pending_events = Vec::new();
|
||||
let mut next_delivery = Instant::now();
|
||||
let in_flight = Arc::new(AtomicBool::new(false));
|
||||
|
||||
loop {
|
||||
// Wait for the next event or timeout
|
||||
let event_or_timeout = tokio::time::timeout(wakeup_time, rx.recv()).await;
|
||||
let now = now();
|
||||
|
||||
match event_or_timeout {
|
||||
Ok(Some(events)) => {
|
||||
let mut discard_count = 0;
|
||||
for event in events {
|
||||
if now.saturating_sub(event.inner.timestamp) < discard_after {
|
||||
pending_events.push(event)
|
||||
} else {
|
||||
discard_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if discard_count > 0 {
|
||||
trc::event!(
|
||||
Telemetry(TelemetryEvent::WebhookError),
|
||||
Details = "Discarded stale events",
|
||||
Total = discard_count
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(None) => {
|
||||
break;
|
||||
}
|
||||
Err(_) => (),
|
||||
}
|
||||
|
||||
// Process events
|
||||
let mut next_retry = None;
|
||||
let now = Instant::now();
|
||||
if next_delivery <= now {
|
||||
if !pending_events.is_empty() {
|
||||
next_delivery = now + settings.throttle;
|
||||
if !in_flight.load(Ordering::Relaxed) {
|
||||
spawn_webhook_handler(
|
||||
settings.clone(),
|
||||
in_flight.clone(),
|
||||
std::mem::take(&mut pending_events),
|
||||
tx.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if !pending_events.is_empty() {
|
||||
// Retry later
|
||||
let this_retry = next_delivery - now;
|
||||
match next_retry {
|
||||
Some(next_retry) if this_retry >= next_retry => {}
|
||||
_ => {
|
||||
next_retry = Some(this_retry);
|
||||
}
|
||||
}
|
||||
}
|
||||
wakeup_time = next_retry.unwrap_or(LONG_1Y_SLUMBER);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct EventWrapper {
|
||||
events: JsonEventSerializer<Vec<Arc<Event<EventDetails>>>>,
|
||||
}
|
||||
|
||||
fn spawn_webhook_handler(
|
||||
settings: Arc<WebhookTracer>,
|
||||
in_flight: Arc<AtomicBool>,
|
||||
events: EventBatch,
|
||||
webhook_tx: mpsc::Sender<EventBatch>,
|
||||
) {
|
||||
tokio::spawn(async move {
|
||||
in_flight.store(true, Ordering::Relaxed);
|
||||
let wrapper = EventWrapper {
|
||||
events: JsonEventSerializer::new(events).with_id().with_spans(),
|
||||
};
|
||||
|
||||
if let Err(err) = post_webhook_events(&settings, &wrapper).await {
|
||||
trc::event!(Telemetry(TelemetryEvent::WebhookError), Details = err);
|
||||
|
||||
if webhook_tx.send(wrapper.events.into_inner()).await.is_err() {
|
||||
trc::event!(
|
||||
Server(ServerEvent::ThreadError),
|
||||
Details = "Failed to send failed webhook events back to main thread",
|
||||
CausedBy = trc::location!()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
in_flight.store(false, Ordering::Relaxed);
|
||||
});
|
||||
}
|
||||
|
||||
async fn post_webhook_events(
|
||||
settings: &WebhookTracer,
|
||||
events: &EventWrapper,
|
||||
) -> Result<(), String> {
|
||||
// Serialize body
|
||||
let body = serde_json::to_string(events)
|
||||
.map_err(|err| format!("Failed to serialize events: {}", err))?;
|
||||
|
||||
// Add HMAC-SHA256 signature
|
||||
let mut headers = settings.headers.clone();
|
||||
if !settings.key.is_empty() {
|
||||
let key = hmac::Key::new(hmac::HMAC_SHA256, settings.key.as_bytes());
|
||||
let tag = hmac::sign(&key, body.as_bytes());
|
||||
|
||||
headers.insert(
|
||||
"X-Signature",
|
||||
STANDARD.encode(tag.as_ref()).parse().unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
// Send request
|
||||
let response = settings
|
||||
.client
|
||||
.post(&settings.url)
|
||||
.timeout(settings.timeout)
|
||||
.headers(headers)
|
||||
.body(body)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|err| format!("Webhook request to {} failed: {err}", settings.url))?;
|
||||
|
||||
if response.status().is_success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!(
|
||||
"Webhook request to {} failed with code {}: {}",
|
||||
settings.url,
|
||||
response.status().as_u16(),
|
||||
response.status().canonical_reason().unwrap_or("Unknown")
|
||||
))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user