Jon Gjengset 7bc97b1abe
fmt: Timestamped log messages (#96)
Currently there is no good way to get timestamped log entries with the
`tracing-fmt` subscriber. Even with `full`, timestamps are not included.
This is surprising to those coming from other logging crates. It is also
inconvenient to work around, as you would either need to add a custom
field to _all_ spans and events across all your crates, or write your
own subscriber which sort of reduces the usefulness of `fmt`.

This patch introduces a trait for event formatters (`FormatEvent`) so
that it is easier to write more complex formatters (previously they were
just `Fn(...) -> fmt::Result` which would require boxing a closure to
produce). It then implements this new trait for `default::Format`, which
is the new default formatter. It implements "compact"/full formatting
exactly like before, except that is also generic over a timer. The timer
must implement `default::FormatTime`, which has a single method that
writes the current time out to a `fmt::Write`. This method is
implemented for a two new unit structs `SystemTime` and `Uptime`.
`SystemTime` is pretty-printed using `chrono` with the new default
feature `chrono`, and `Debug` of `std::net::SystemTime` is used if the
feature is turned off. `Uptime` prints the fractional number of seconds
since an epoch. Users can also provide `()` as the time formatter to not
produce timestamps in logged entries (accessible through
`default::Format::without_time`).

This patch also switches the default output to the verbose format, with
`compact` offered to return to the compact output format.

Closes #94.
2019-06-26 16:41:31 -07:00

157 lines
5.7 KiB
Rust

extern crate futures;
extern crate hyper;
#[macro_use]
extern crate tracing;
extern crate tokio;
extern crate tracing_env_logger;
extern crate tracing_fmt;
extern crate tracing_futures;
use futures::future;
use hyper::rt::{Future, Stream};
use hyper::server::conn::Http;
use hyper::service::service_fn;
use hyper::{Body, Method, Request, Response, StatusCode};
use std::str;
use tracing::{field, Level};
use tracing_futures::{Instrument, Instrumented};
type BoxFut = Box<dyn Future<Item = Response<Body>, Error = hyper::Error> + Send>;
fn echo(req: Request<Body>) -> Instrumented<BoxFut> {
span!(
Level::TRACE,
"request",
method = &field::debug(req.method()),
uri = &field::debug(req.uri()),
headers = &field::debug(req.headers())
)
.enter(|| {
info!("received request");
let mut response = Response::new(Body::empty());
let (rsp_span, fut): (_, BoxFut) = match (req.method(), req.uri().path()) {
// Serve some instructions at /
(&Method::GET, "/") => {
const BODY: &'static str = "Try POSTing data to /echo";
*response.body_mut() = Body::from(BODY);
(
span!(Level::TRACE, "response", body = &field::display(&BODY)),
Box::new(future::ok(response)),
)
}
// Simply echo the body back to the client.
(&Method::POST, "/echo") => {
let body = req.into_body();
let span = span!(Level::TRACE, "response", response_kind = &"echo");
*response.body_mut() = body;
(span, Box::new(future::ok(response)))
}
// Convert to uppercase before sending back to client.
(&Method::POST, "/echo/uppercase") => {
let mapping = req.into_body().map(|chunk| {
let upper = chunk
.iter()
.map(|byte| byte.to_ascii_uppercase())
.collect::<Vec<u8>>();
debug!(
{
chunk = field::debug(str::from_utf8(&chunk[..])),
uppercased = field::debug(str::from_utf8(&upper[..]))
},
"uppercased request body"
);
upper
});
*response.body_mut() = Body::wrap_stream(mapping);
(
span!(Level::TRACE, "response", response_kind = "uppercase"),
Box::new(future::ok(response)),
)
}
// Reverse the entire body before sending back to the client.
//
// Since we don't know the end yet, we can't simply stream
// the chunks as they arrive. So, this returns a different
// future, waiting on concatenating the full body, so that
// it can be reversed. Only then can we return a `Response`.
(&Method::POST, "/echo/reversed") => {
let span = span!(Level::TRACE, "response", response_kind = "reversed");
let reversed = span.enter(|| {
req.into_body().concat2().map(move |chunk| {
let body = chunk.iter().rev().cloned().collect::<Vec<u8>>();
debug!(
{
chunk = field::debug(str::from_utf8(&chunk[..])),
body = field::debug(str::from_utf8(&body[..]))
},
"reversed request body");
*response.body_mut() = Body::from(body);
response
})
});
(span, Box::new(reversed))
}
// The 404 Not Found route...
_ => {
*response.status_mut() = StatusCode::NOT_FOUND;
(
span!(
Level::TRACE,
"response",
body = &field::debug(()),
status = &field::debug(&StatusCode::NOT_FOUND)
),
Box::new(future::ok(response)),
)
}
};
fut.instrument(rsp_span)
})
}
fn main() {
let subscriber = tracing_fmt::FmtSubscriber::builder().finish();
tracing_env_logger::try_init().expect("init log adapter");
tracing::subscriber::with_default(subscriber, || {
let addr: ::std::net::SocketAddr = ([127, 0, 0, 1], 3000).into();
let server_span = span!(Level::TRACE, "server", local = &field::debug(addr));
let server = tokio::net::TcpListener::bind(&addr)
.expect("bind")
.incoming()
.fold(Http::new(), move |http, sock| {
let span = span!(
Level::TRACE,
"connection",
remote = &field::debug(&sock.peer_addr().unwrap())
);
hyper::rt::spawn(
http.serve_connection(sock, service_fn(echo))
.map_err(|e| {
error!({ error = field::display(e) }, "serve error");
})
.instrument(span),
);
Ok::<_, ::std::io::Error>(http)
})
.map(|_| ())
.map_err(|e| {
error!({ error = field::display(e) }, "server error");
})
.instrument(server_span.clone());
server_span.enter(|| {
info!("listening...");
hyper::rt::run(server);
});
})
}