use crate::transport::http::route::{Handler, Middleware};
use crate::transport::http::Response;
use crate::{shors_debug, shors_info};
use opentelemetry::propagation::Injector;
use opentelemetry::sdk::trace::Tracer as OTELTracer;
use opentelemetry::trace::{SpanBuilder, StatusCode, TraceContextExt, Tracer};
use opentelemetry::Context as OTELContext;
use std::fmt::Debug;
pub fn debug<E: 'static>(handler: Handler<E>) -> Handler<E> {
Handler(Box::new(move |ctx, request| -> Result<Response, E> {
shors_debug!("call {:?} with {:?}", ctx.endpoint_path(), request);
handler(ctx, request)
}))
}
const TRACE_HEADER: &str = "with-trace";
pub fn otel<E: 'static + Debug>(tracer: &'static OTELTracer) -> Middleware<E> {
otel_inner(tracer, false)
}
pub fn otel_conditional<E: 'static + Debug>(tracer: &'static OTELTracer) -> Middleware<E> {
otel_inner(tracer, true)
}
fn otel_inner<E: 'static + Debug>(tracer: &'static OTELTracer, conditional: bool) -> Middleware<E> {
Middleware(Box::new(move |handler: Handler<E>| {
Handler(Box::new(move |ctx, request| {
if conditional && !request.headers.contains_key(TRACE_HEADER) {
ctx.set(
"request_id",
crate::tarantool::uuid::Uuid::random().to_string(),
);
return handler(ctx, request);
}
let span = {
let builder = SpanBuilder::from_name(ctx.endpoint_path().to_string());
tracer.build(builder)
};
let trace_ctx = &OTELContext::current_with_span(span);
ctx.inject(trace_ctx);
ctx.enable_tracing();
ctx.set(
"request_id",
trace_ctx.span().span_context().trace_id().to_string(),
);
let response: Result<_, _> = handler(ctx, request);
response.map_err(|e| {
trace_ctx
.span()
.set_status(StatusCode::Error, format!("{:?}", e));
e
})
}))
}))
}
pub fn access_logs<E, COND>(condition: COND) -> Middleware<E>
where
E: 'static,
COND: Fn() -> bool + 'static + Copy,
{
Middleware(Box::new(move |handler: Handler<E>| {
Handler(Box::new(move |ctx, request| {
if condition() {
let req_time = chrono::Local::now();
let remote_addr = request.peer.host.as_deref().unwrap_or("-").to_string();
let remote_user = "-";
let time_local = req_time.format("%d/%b/%Y:%H:%M:%S %z");
let request_str = format!("{} {} {}", request.method, request.path, request.proto);
let http_referer = request
.headers
.get("referer")
.cloned()
.unwrap_or_else(|| "-".to_string());
let user_agent = request
.headers
.get("user-agent")
.cloned()
.unwrap_or_else(|| "-".to_string());
let resp = handler(ctx, request);
let (status, body_bytes_len) = if let Ok(ref r) = resp {
(Some(r.status), Some(r.body.len()))
} else {
(None, None)
};
let status = status
.map(|n| n.to_string())
.unwrap_or_else(|| "-".to_string());
let body_bytes_len = body_bytes_len
.map(|n| n.to_string())
.unwrap_or_else(|| "-".to_string());
let log_record = format!("{remote_addr} - {remote_user} [{time_local}] \"{request_str}\" {status} {body_bytes_len} \"{http_referer}\" \"{user_agent}\"");
shors_info!(
ctx: ctx,
target: "http",
"{}",
log_record,
);
resp
} else {
handler(ctx, request)
}
}))
}))
}