diff --git a/Cargo.toml b/Cargo.toml index d6e03173d..86801ad61 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,11 +30,10 @@ http-service = "0.2.0" serde = "1.0.91" serde_derive = "1.0.91" serde_urlencoded = "0.5.5" -slog = "2.4.1" -slog-async = "2.3.0" -slog-term = "2.4.0" tide-cookies = { path = "./tide-cookies", optional = true } tide-core = { path = "./tide-core" } +tide-headers = { path = "./tide-headers" } +tide-log = { path = "./tide-log" } [dependencies.multipart] default-features = false @@ -52,13 +51,19 @@ mime = "0.3.13" mime_guess = "2.0.0-alpha.6" percent-encoding = "1.0.1" serde = { version = "1.0.91", features = ["derive"] } +tide-log = { path = "./tide-log" } +env_logger = "0.6.1" +log4rs = "0.8.3" +log = "0.4.6" [workspace] members = [ "tide-compression", "tide-cookies", "tide-core", + "tide-headers", "tide-log", + "tide-slog", ] [patch.crates-io] diff --git a/examples/hello_envlog.rs b/examples/hello_envlog.rs new file mode 100644 index 000000000..d57a5a4bd --- /dev/null +++ b/examples/hello_envlog.rs @@ -0,0 +1,8 @@ +#![feature(async_await)] +fn main() { + env_logger::from_env(env_logger::Env::default().default_filter_or("info")).init(); + let mut app = tide::App::new(); + app.middleware(tide::middleware::RequestLogger::new()); + app.at("/").get(async move |_| "Hello, world!"); + app.run("127.0.0.1:8000").unwrap(); +} diff --git a/examples/hello_logrs.rs b/examples/hello_logrs.rs new file mode 100644 index 000000000..d32f2696e --- /dev/null +++ b/examples/hello_logrs.rs @@ -0,0 +1,18 @@ +#![feature(async_await)] +fn main() { + use log::LevelFilter; + use log4rs::append::console::ConsoleAppender; + use log4rs::config::{Appender, Config, Root}; + + let stdout = ConsoleAppender::builder().build(); + let config = Config::builder() + .appender(Appender::builder().build("stdout", Box::new(stdout))) + .build(Root::builder().appender("stdout").build(LevelFilter::Info)) + .unwrap(); + let _handle = log4rs::init_config(config).unwrap(); + + let mut app = tide::App::new(); + app.middleware(tide::middleware::RequestLogger::new()); + app.at("/").get(async move |_| "Hello, world!"); + app.run("127.0.0.1:8000").unwrap(); +} diff --git a/src/middleware/mod.rs b/src/middleware.rs similarity index 53% rename from src/middleware/mod.rs rename to src/middleware.rs index aaa20d48b..581792a7a 100644 --- a/src/middleware/mod.rs +++ b/src/middleware.rs @@ -1,7 +1,9 @@ -mod default_headers; -mod logger; +// Core +pub use tide_core::middleware::{Middleware, Next}; + +// Exports from tide repo. +pub use tide_headers::DefaultHeaders; +pub use tide_log::RequestLogger; -pub use self::{default_headers::DefaultHeaders, logger::RootLogger}; #[cfg(feature = "cookies")] pub use tide_cookies::CookiesMiddleware; -pub use tide_core::middleware::{Middleware, Next}; diff --git a/tide-compression/Cargo.toml b/tide-compression/Cargo.toml index f00c417d5..395fff270 100644 --- a/tide-compression/Cargo.toml +++ b/tide-compression/Cargo.toml @@ -27,4 +27,5 @@ features = ["stream", "gzip", "zlib", "brotli", "zstd"] version = "0.1.0-alpha.1" [dev-dependencies] +tide = { path = "../" } http-service-mock = "0.2.0" diff --git a/tide-cookies/Cargo.toml b/tide-cookies/Cargo.toml index 34b388977..6c0742f4e 100644 --- a/tide-cookies/Cargo.toml +++ b/tide-cookies/Cargo.toml @@ -5,8 +5,8 @@ edition = "2018" authors = [ "Tide Developers", ] -description = "Cookie management for Tide web framework" -documentation = "https://docs.rs/tide-core" +description = "Cookie middleware and extensions for Tide" +documentation = "https://docs.rs/tide-cookies" license = "MIT OR Apache-2.0" repository = "https://github.com/rustasync/tide" @@ -18,4 +18,5 @@ http-service = "0.2.0" tide-core = { path = "../tide-core" } [dev-dependencies] +tide = { path = "../" } http-service-mock = "0.2.0" diff --git a/tide-headers/Cargo.toml b/tide-headers/Cargo.toml new file mode 100644 index 000000000..294bc7791 --- /dev/null +++ b/tide-headers/Cargo.toml @@ -0,0 +1,26 @@ +[package] +authors = [ + "Tide Developers" +] +description = "Header related middleware for Tide" +documentation = "https://docs.rs/tide-headers" +keywords = ["tide", "web", "async", "middleware", "headers"] +categories = [ + "network-programming", + "web-programming::http-server", +] +edition = "2018" +license = "MIT OR Apache-2.0" +name = "tide-headers" +readme = "README.md" +repository = "https://github.com/rustasync/tide" +version = "0.1.0" + + [dependencies] +tide-core = { path = "../tide-core" } +futures-preview = "0.3.0-alpha.16" +http = "0.1" +log = "0.4.6" + +[dev-dependencies] +tide = { path = "../" } diff --git a/src/middleware/default_headers.rs b/tide-headers/src/lib.rs similarity index 79% rename from src/middleware/default_headers.rs rename to tide-headers/src/lib.rs index d76dbb192..36b080567 100644 --- a/src/middleware/default_headers.rs +++ b/tide-headers/src/lib.rs @@ -1,12 +1,23 @@ +//! Crate that provides helpers, and/or middlewares for tide +//! related to http headers. +#![feature(async_await)] +#![warn( + nonstandard_style, + rust_2018_idioms, + future_incompatible, + missing_debug_implementations +)] + use futures::future::BoxFuture; use futures::prelude::*; +use log::trace; use http::{ header::{HeaderValue, IntoHeaderName}, HeaderMap, HttpTryFrom, }; -use crate::{ +use tide_core::{ middleware::{Middleware, Next}, Context, Response, }; @@ -20,10 +31,9 @@ pub struct DefaultHeaders { impl DefaultHeaders { /// Construct a new instance with an empty list of headers. pub fn new() -> DefaultHeaders { - DefaultHeaders::default() + Self::default() } - #[inline] /// Add a header to the default header list. pub fn header(mut self, key: K, value: V) -> Self where @@ -35,7 +45,6 @@ impl DefaultHeaders { .expect("Cannot create default header"); self.headers.append(key, value); - self } } @@ -44,9 +53,9 @@ impl Middleware for DefaultHeaders { fn handle<'a>(&'a self, cx: Context, next: Next<'a, Data>) -> BoxFuture<'a, Response> { FutureExt::boxed(async move { let mut res = next.run(cx).await; - let headers = res.headers_mut(); for (key, value) in self.headers.iter() { + trace!("add default: {} {:?}", &key, &value); headers.entry(key).unwrap().or_insert_with(|| value.clone()); } res diff --git a/tide-log/Cargo.toml b/tide-log/Cargo.toml index 515dda102..4a2074b85 100644 --- a/tide-log/Cargo.toml +++ b/tide-log/Cargo.toml @@ -2,7 +2,7 @@ authors = [ "Tide Developers" ] -description = "Tide middleware for logging" +description = "Logging middleware for Tide" documentation = "https://docs.rs/tide-log" keywords = ["tide", "web", "async", "middleware", "logging"] categories = [ @@ -18,7 +18,10 @@ repository = "https://github.com/rustasync/tide" version = "0.1.0" [dependencies] -tide = { path = "../" } +tide-core = { path = "../tide-core" } futures-preview = "0.3.0-alpha.16" http = "0.1" -log = "0.4.6" +log = "0.4.6" + +[dev-dependencies] +tide = { path = "../" } diff --git a/tide-log/src/lib.rs b/tide-log/src/lib.rs index 91f057031..88edf072b 100644 --- a/tide-log/src/lib.rs +++ b/tide-log/src/lib.rs @@ -1,3 +1,6 @@ +//! Crate that provides helpers and/or middlewares for tide +//! related to logging. +//! #![feature(async_await)] #![warn( nonstandard_style, @@ -9,7 +12,8 @@ use futures::future::BoxFuture; use futures::prelude::*; use log::{info, trace}; -use tide::{ + +use tide_core::{ middleware::{Middleware, Next}, Context, Response, }; @@ -24,25 +28,38 @@ use tide::{ /// app.middleware(tide_log::RequestLogger::new()); /// ``` #[derive(Debug, Clone, Default)] -pub struct RequestLogger; +pub struct RequestLogger { + target: String, +} impl RequestLogger { + /// Create a new instance of logger with default target as + /// "requests" pub fn new() -> Self { - Self::default() + Self { + target: "requests".to_owned(), + } + } + + /// Create a new instance of logger with supplied `target` for + /// logging. + pub fn with_target(target: String) -> Self { + Self { target } } async fn log_basic<'a, Data: Send + Sync + 'static>( &'a self, ctx: Context, next: Next<'a, Data>, - ) -> tide::Response { + ) -> Response { let path = ctx.uri().path().to_owned(); let method = ctx.method().as_str().to_owned(); - trace!("IN => {} {}", method, path); + trace!(target: &self.target, "IN => {} {}", method, path); let start = std::time::Instant::now(); let res = next.run(ctx).await; let status = res.status(); info!( + target: &self.target, "{} {} {} {}ms", method, path, diff --git a/tide-slog/Cargo.toml b/tide-slog/Cargo.toml new file mode 100644 index 000000000..ca23079b8 --- /dev/null +++ b/tide-slog/Cargo.toml @@ -0,0 +1,30 @@ +[package] +authors = [ + "Tide Developers" +] +description = "Logging middleware for Tide based on slog" +documentation = "https://docs.rs/tide-slog" +keywords = ["tide", "web", "async", "middleware", "logging", "slog"] +categories = [ + "logging", + "network-programming", + "web-programming::http-server", +] +edition = "2018" +license = "MIT OR Apache-2.0" +name = "tide-slog" +readme = "README.md" +repository = "https://github.com/rustasync/tide" +version = "0.1.0" + + [dependencies] +tide-core = { path = "../tide-core" } +futures-preview = "0.3.0-alpha.16" +http = "0.1" +log = "0.4.6" +slog = "2.4.1" +slog-async = "2.3.0" +slog-term = "2.4.0" + +[dev-dependencies] +tide = { path = "../" } diff --git a/src/middleware/logger.rs b/tide-slog/src/lib.rs similarity index 51% rename from src/middleware/logger.rs rename to tide-slog/src/lib.rs index 8036cf585..1b1724fa7 100644 --- a/src/middleware/logger.rs +++ b/tide-slog/src/lib.rs @@ -1,50 +1,70 @@ -use slog::{info, o, Drain}; +#![feature(async_await)] +#![warn( + nonstandard_style, + rust_2018_idioms, + future_incompatible, + missing_debug_implementations +)] + +use slog::{info, o, trace, Drain}; use slog_async; use slog_term; use futures::future::BoxFuture; use futures::prelude::*; -use crate::{ +use tide_core::{ middleware::{Middleware, Next}, Context, Response, }; -/// Root logger for Tide. Wraps over logger provided by slog.SimpleLogger +/// RequestLogger based on slog.SimpleLogger #[derive(Debug)] -pub struct RootLogger { +pub struct RequestLogger { // drain: dyn slog::Drain, - inner_logger: slog::Logger, + inner: slog::Logger, } -impl RootLogger { - pub fn new() -> RootLogger { - let decorator = slog_term::TermDecorator::new().build(); - let drain = slog_term::CompactFormat::new(decorator).build().fuse(); - let drain = slog_async::Async::new(drain).build().fuse(); +impl RequestLogger { + pub fn new() -> Self { + Default::default() + } - let log = slog::Logger::root(drain, o!()); - RootLogger { inner_logger: log } + pub fn with_logger(logger: slog::Logger) -> Self { + Self { inner: logger } } } -impl Default for RootLogger { +impl Default for RequestLogger { fn default() -> Self { - Self::new() + let decorator = slog_term::TermDecorator::new().build(); + let drain = slog_term::CompactFormat::new(decorator).build().fuse(); + let drain = slog_async::Async::new(drain).build().fuse(); + + let log = slog::Logger::root(drain, o!()); + Self { inner: log } } } /// Stores information during request phase and logs information once the response /// is generated. -impl Middleware for RootLogger { +impl Middleware for RequestLogger { fn handle<'a>(&'a self, cx: Context, next: Next<'a, Data>) -> BoxFuture<'a, Response> { FutureExt::boxed(async move { let path = cx.uri().path().to_owned(); let method = cx.method().as_str().to_owned(); - + trace!(self.inner, "IN => {} {}", method, path); + let start = std::time::Instant::now(); let res = next.run(cx).await; let status = res.status(); - info!(self.inner_logger, "{} {} {}", method, path, status.as_str()); + info!( + self.inner, + "{} {} {} {}ms", + method, + path, + status.as_str(), + start.elapsed().as_millis() + ); res }) }