shors 0.13.0

Transport layer for cartridge + tarantool-module projects.
Documentation
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
            })
        }))
    }))
}

/// Access logs in (nginx)[https://nginx.org/ru/docs/http/ngx_http_log_module.html] format.
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)
            }
        }))
    }))
}