mirror of
https://github.com/scottlamb/moonfire-nvr.git
synced 2025-12-08 08:42:41 -05:00
switch from log to tracing
I think this is a big improvement in readability. I removed the `lnav` config, which is a little sad, but I don't think it supports this structured logging format well. Still seems worthwhile on balance.
This commit is contained in:
@@ -28,9 +28,10 @@ use http::header::{self, HeaderValue};
|
||||
use http::{status::StatusCode, Request, Response};
|
||||
use http_serve::dir::FsDir;
|
||||
use hyper::body::Bytes;
|
||||
use log::{debug, warn};
|
||||
use std::net::IpAddr;
|
||||
use std::sync::Arc;
|
||||
use tracing::warn;
|
||||
use tracing::Instrument;
|
||||
use url::form_urlencoded;
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -245,6 +246,7 @@ impl Service {
|
||||
async fn serve_inner(
|
||||
self: Arc<Self>,
|
||||
req: Request<::hyper::Body>,
|
||||
authreq: auth::Request,
|
||||
conn_data: ConnData,
|
||||
) -> ResponseResult {
|
||||
let p = Path::decode(req.uri().path());
|
||||
@@ -252,7 +254,15 @@ impl Service {
|
||||
p,
|
||||
Path::NotFound | Path::Request | Path::Login | Path::Logout | Path::Static
|
||||
);
|
||||
let caller = self.authenticate(&req, &conn_data, always_allow_unauthenticated);
|
||||
let caller = self.authenticate(&req, &authreq, &conn_data, always_allow_unauthenticated);
|
||||
if let Some(username) = caller
|
||||
.as_ref()
|
||||
.ok()
|
||||
.and_then(|c| c.user.as_ref())
|
||||
.map(|u| &u.name)
|
||||
{
|
||||
tracing::Span::current().record("auth.user", tracing::field::display(username));
|
||||
}
|
||||
|
||||
// WebSocket stuff is handled separately, because most authentication
|
||||
// errors are returned as text messages over the protocol, rather than
|
||||
@@ -264,14 +274,16 @@ impl Service {
|
||||
}
|
||||
|
||||
let caller = caller?;
|
||||
debug!("request on: {}: {:?}", req.uri(), p);
|
||||
let (cache, mut response) = match p {
|
||||
Path::InitSegment(sha1, debug) => (
|
||||
CacheControl::PrivateStatic,
|
||||
self.init_segment(sha1, debug, &req)?,
|
||||
),
|
||||
Path::TopLevel => (CacheControl::PrivateDynamic, self.top_level(&req, caller)?),
|
||||
Path::Request => (CacheControl::PrivateDynamic, self.request(&req, caller)?),
|
||||
Path::Request => (
|
||||
CacheControl::PrivateDynamic,
|
||||
self.request(&req, &authreq, caller)?,
|
||||
),
|
||||
Path::Camera(uuid) => (CacheControl::PrivateDynamic, self.camera(&req, uuid)?),
|
||||
Path::StreamRecordings(uuid, type_) => (
|
||||
CacheControl::PrivateDynamic,
|
||||
@@ -289,8 +301,14 @@ impl Service {
|
||||
unreachable!("StreamLiveMp4Segments should have already been handled")
|
||||
}
|
||||
Path::NotFound => return Err(not_found("path not understood")),
|
||||
Path::Login => (CacheControl::PrivateDynamic, self.login(req).await?),
|
||||
Path::Logout => (CacheControl::PrivateDynamic, self.logout(req).await?),
|
||||
Path::Login => (
|
||||
CacheControl::PrivateDynamic,
|
||||
self.login(req, authreq).await?,
|
||||
),
|
||||
Path::Logout => (
|
||||
CacheControl::PrivateDynamic,
|
||||
self.logout(req, authreq).await?,
|
||||
),
|
||||
Path::Signals => (
|
||||
CacheControl::PrivateDynamic,
|
||||
self.signals(req, caller).await?,
|
||||
@@ -321,6 +339,7 @@ impl Service {
|
||||
}
|
||||
|
||||
/// Serves an HTTP request.
|
||||
///
|
||||
/// An error return from this method causes hyper to abruptly drop the
|
||||
/// HTTP connection rather than respond. That's not terribly useful, so this
|
||||
/// method always returns `Ok`. It delegates to a `serve_inner` which is
|
||||
@@ -331,10 +350,63 @@ impl Service {
|
||||
req: Request<::hyper::Body>,
|
||||
conn_data: ConnData,
|
||||
) -> Result<Response<Body>, std::convert::Infallible> {
|
||||
Ok(self
|
||||
.serve_inner(req, conn_data)
|
||||
let id = ulid::Ulid::new();
|
||||
let authreq = auth::Request {
|
||||
when_sec: Some(self.db.clocks().realtime().sec),
|
||||
addr: if self.trust_forward_hdrs {
|
||||
req.headers()
|
||||
.get("X-Real-IP")
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.and_then(|v| IpAddr::from_str(v).ok())
|
||||
} else {
|
||||
conn_data.client_addr.map(|a| a.ip())
|
||||
},
|
||||
user_agent: req
|
||||
.headers()
|
||||
.get(header::USER_AGENT)
|
||||
.map(|ua| ua.as_bytes().to_vec()),
|
||||
};
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
// https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/http/
|
||||
let span = tracing::info_span!(
|
||||
"request",
|
||||
%id,
|
||||
net.sock.peer.uid = conn_data.client_unix_uid.map(tracing::field::display),
|
||||
http.client_ip = authreq.addr.map(tracing::field::display),
|
||||
http.method = %req.method(),
|
||||
http.target = %req.uri(),
|
||||
http.status_code = tracing::field::Empty,
|
||||
auth.user = tracing::field::Empty,
|
||||
);
|
||||
tracing::debug!(parent: &span, "received request headers");
|
||||
let response = self
|
||||
.serve_inner(req, authreq, conn_data)
|
||||
.instrument(span.clone())
|
||||
.await
|
||||
.unwrap_or_else(|e| e.0))
|
||||
.unwrap_or_else(|e| e.0);
|
||||
span.record("http.status_code", response.status().as_u16());
|
||||
let latency = std::time::Instant::now().duration_since(start);
|
||||
if response.status().is_server_error() {
|
||||
tracing::error!(
|
||||
parent: &span,
|
||||
latency = format_args!("{:.6}s", latency.as_secs_f32()),
|
||||
"sending response headers",
|
||||
);
|
||||
} else if response.status().is_client_error() {
|
||||
tracing::warn!(
|
||||
parent: &span,
|
||||
latency = format_args!("{:.6}s", latency.as_secs_f32()),
|
||||
"sending response headers",
|
||||
);
|
||||
} else {
|
||||
tracing::info!(
|
||||
parent: &span,
|
||||
latency = format_args!("{:.6}s", latency.as_secs_f32()),
|
||||
"sending response headers",
|
||||
);
|
||||
}
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
fn top_level(&self, req: &Request<::hyper::Body>, caller: Caller) -> ResponseResult {
|
||||
@@ -478,26 +550,12 @@ impl Service {
|
||||
}
|
||||
}
|
||||
|
||||
fn authreq(&self, req: &Request<::hyper::Body>) -> auth::Request {
|
||||
auth::Request {
|
||||
when_sec: Some(self.db.clocks().realtime().sec),
|
||||
addr: if self.trust_forward_hdrs {
|
||||
req.headers()
|
||||
.get("X-Real-IP")
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.and_then(|v| IpAddr::from_str(v).ok())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
user_agent: req
|
||||
.headers()
|
||||
.get(header::USER_AGENT)
|
||||
.map(|ua| ua.as_bytes().to_vec()),
|
||||
}
|
||||
}
|
||||
|
||||
fn request(&self, req: &Request<::hyper::Body>, caller: Caller) -> ResponseResult {
|
||||
let authreq = self.authreq(req);
|
||||
fn request(
|
||||
&self,
|
||||
req: &Request<::hyper::Body>,
|
||||
authreq: &auth::Request,
|
||||
caller: Caller,
|
||||
) -> ResponseResult {
|
||||
let host = req
|
||||
.headers()
|
||||
.get(header::HOST)
|
||||
@@ -561,13 +619,16 @@ impl Service {
|
||||
fn authenticate(
|
||||
&self,
|
||||
req: &Request<hyper::Body>,
|
||||
authreq: &auth::Request,
|
||||
conn_data: &ConnData,
|
||||
unauth_path: bool,
|
||||
) -> Result<Caller, base::Error> {
|
||||
if let Some(sid) = extract_sid(req) {
|
||||
let authreq = self.authreq(req);
|
||||
|
||||
match self.db.lock().authenticate_session(authreq, &sid.hash()) {
|
||||
match self
|
||||
.db
|
||||
.lock()
|
||||
.authenticate_session(authreq.clone(), &sid.hash())
|
||||
{
|
||||
Ok((s, u)) => {
|
||||
return Ok(Caller {
|
||||
permissions: s.permissions.clone(),
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
|
||||
use db::auth;
|
||||
use http::{header, HeaderValue, Method, Request, Response, StatusCode};
|
||||
use log::{info, warn};
|
||||
use memchr::memchr;
|
||||
use tracing::{info, warn};
|
||||
|
||||
use crate::json;
|
||||
|
||||
@@ -18,14 +18,17 @@ use super::{
|
||||
use std::convert::TryFrom;
|
||||
|
||||
impl Service {
|
||||
pub(super) async fn login(&self, mut req: Request<::hyper::Body>) -> ResponseResult {
|
||||
pub(super) async fn login(
|
||||
&self,
|
||||
mut req: Request<::hyper::Body>,
|
||||
authreq: auth::Request,
|
||||
) -> ResponseResult {
|
||||
if *req.method() != Method::POST {
|
||||
return Err(plain_response(StatusCode::METHOD_NOT_ALLOWED, "POST expected").into());
|
||||
}
|
||||
let r = extract_json_body(&mut req).await?;
|
||||
let r: json::LoginRequest =
|
||||
serde_json::from_slice(&r).map_err(|e| bad_req(e.to_string()))?;
|
||||
let authreq = self.authreq(&req);
|
||||
let host = req
|
||||
.headers()
|
||||
.get(header::HOST)
|
||||
@@ -69,7 +72,11 @@ impl Service {
|
||||
.unwrap())
|
||||
}
|
||||
|
||||
pub(super) async fn logout(&self, mut req: Request<hyper::Body>) -> ResponseResult {
|
||||
pub(super) async fn logout(
|
||||
&self,
|
||||
mut req: Request<hyper::Body>,
|
||||
authreq: auth::Request,
|
||||
) -> ResponseResult {
|
||||
if *req.method() != Method::POST {
|
||||
return Err(plain_response(StatusCode::METHOD_NOT_ALLOWED, "POST expected").into());
|
||||
}
|
||||
@@ -79,7 +86,6 @@ impl Service {
|
||||
|
||||
let mut res = Response::new(b""[..].into());
|
||||
if let Some(sid) = extract_sid(&req) {
|
||||
let authreq = self.authreq(&req);
|
||||
let mut l = self.db.lock();
|
||||
let hash = sid.hash();
|
||||
let need_revoke = match l.authenticate_session(authreq.clone(), &hash) {
|
||||
@@ -142,7 +148,7 @@ fn encode_sid(sid: db::RawSessionId, flags: i32) -> String {
|
||||
mod tests {
|
||||
use db::testutil;
|
||||
use fnv::FnvHashMap;
|
||||
use log::info;
|
||||
use tracing::info;
|
||||
|
||||
use crate::web::tests::Server;
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
use base::bail_t;
|
||||
use db::recording::{self, rescale};
|
||||
use http::{Request, StatusCode};
|
||||
use log::trace;
|
||||
use nom::bytes::complete::{tag, take_while1};
|
||||
use nom::combinator::{all_consuming, map, map_res, opt};
|
||||
use nom::sequence::{preceded, tuple};
|
||||
@@ -17,6 +16,7 @@ use std::cmp;
|
||||
use std::convert::TryFrom;
|
||||
use std::ops::Range;
|
||||
use std::str::FromStr;
|
||||
use tracing::trace;
|
||||
use url::form_urlencoded;
|
||||
use uuid::Uuid;
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ use base::bail_t;
|
||||
use futures::{Future, SinkExt};
|
||||
use http::{header, Request, Response};
|
||||
use tokio_tungstenite::{tungstenite, WebSocketStream};
|
||||
use tracing::Instrument;
|
||||
|
||||
use super::{bad_req, ResponseResult};
|
||||
|
||||
@@ -37,26 +38,33 @@ where
|
||||
tungstenite::handshake::server::create_response_with_body(&req, hyper::Body::empty)
|
||||
.map_err(|e| bad_req(e.to_string()))?;
|
||||
let (parts, _) = response.into_parts();
|
||||
tokio::spawn(async move {
|
||||
let upgraded = match hyper::upgrade::on(req).await {
|
||||
Ok(u) => u,
|
||||
Err(e) => {
|
||||
log::error!("WebSocket upgrade failed: {e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let mut ws = tokio_tungstenite::WebSocketStream::from_raw_socket(
|
||||
upgraded,
|
||||
tungstenite::protocol::Role::Server,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
if let Err(e) = handler(&mut ws).await {
|
||||
// TODO: use a nice JSON message format for errors.
|
||||
log::error!("WebSocket stream terminating with error {e}");
|
||||
let _ = ws.send(tungstenite::Message::Text(e.to_string())).await;
|
||||
let span = tracing::info_span!("websocket");
|
||||
tokio::spawn(
|
||||
async move {
|
||||
let upgraded = match hyper::upgrade::on(req).await {
|
||||
Ok(u) => u,
|
||||
Err(err) => {
|
||||
tracing::error!(%err, "upgrade failed");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let mut ws = tokio_tungstenite::WebSocketStream::from_raw_socket(
|
||||
upgraded,
|
||||
tungstenite::protocol::Role::Server,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
if let Err(err) = handler(&mut ws).await {
|
||||
// TODO: use a nice JSON message format for errors.
|
||||
tracing::error!(%err, "closing with error");
|
||||
let _ = ws.send(tungstenite::Message::Text(err.to_string())).await;
|
||||
} else {
|
||||
tracing::info!("closing");
|
||||
};
|
||||
let _ = ws.close(None).await;
|
||||
}
|
||||
});
|
||||
.instrument(span),
|
||||
);
|
||||
Ok(Response::from_parts(parts, Body::from("")))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user