2626//! }
2727//! ```
2828
29- use crate :: { Context , Executor , JoinHandle , Next , Result , State } ;
29+ use crate :: { Body , Context , Executor , JoinHandle , Next , Result , State } ;
3030use bytes:: Bytes ;
3131use bytesize:: ByteSize ;
3232use futures:: task:: { self , Poll } ;
3333use futures:: { Future , Stream } ;
3434use log:: { error, info} ;
35+ use roa_core:: http:: { Method , StatusCode } ;
3536use std:: io;
3637use std:: pin:: Pin ;
3738use std:: sync:: Arc ;
3839use std:: time:: Instant ;
3940
40- /// A finite-state machine to log information in each response.
41- enum Logger < S , F >
42- where
43- F : FnOnce ( u64 ) ,
44- {
45- Polling {
46- counter : u64 ,
47- stream : S ,
48- exec : Executor ,
49- task : Arc < F > ,
50- } ,
41+ /// A finite-state machine to log success information in each streaming response.
42+ enum StreamLogger < S > {
43+ Polling { stream : S , task : Box < LogTask > } ,
5144
5245 Logging ( JoinHandle < ( ) > ) ,
5346
5447 Complete ,
5548}
5649
57- impl < S , F > Stream for Logger < S , F >
50+ /// A task structure to log when polling is complete.
51+ #[ derive( Clone ) ]
52+ struct LogTask {
53+ counter : u64 ,
54+ method : Method ,
55+ status_code : StatusCode ,
56+ path : String ,
57+ start : Instant ,
58+ exec : Executor ,
59+ }
60+
61+ impl LogTask {
62+ fn log ( self ) -> JoinHandle < ( ) > {
63+ let LogTask {
64+ counter,
65+ method,
66+ status_code,
67+ path,
68+ start,
69+ exec,
70+ } = self ;
71+ exec. spawn_blocking ( || {
72+ info ! (
73+ "<-- {} {} {}ms {} {}" ,
74+ method,
75+ path,
76+ start. elapsed( ) . as_millis( ) ,
77+ ByteSize ( counter) ,
78+ status_code,
79+ )
80+ } )
81+ }
82+ }
83+
84+ impl < S > Stream for StreamLogger < S >
5885where
59- F : ' static + Send + Sync + Unpin + Fn ( u64 ) ,
6086 S : ' static + Send + Send + Unpin + Stream < Item = io:: Result < Bytes > > ,
6187{
6288 type Item = io:: Result < Bytes > ;
@@ -66,33 +92,28 @@ where
6692 cx : & mut task:: Context < ' _ > ,
6793 ) -> Poll < Option < Self :: Item > > {
6894 match & mut * self {
69- Logger :: Polling {
70- stream,
71- exec,
72- counter,
73- task,
74- } => match futures:: ready!( Pin :: new( stream) . poll_next( cx) ) {
75- Some ( Ok ( bytes) ) => {
76- * counter += bytes. len ( ) as u64 ;
77- Poll :: Ready ( Some ( Ok ( bytes) ) )
95+ StreamLogger :: Polling { stream, task } => {
96+ match futures:: ready!( Pin :: new( stream) . poll_next( cx) ) {
97+ Some ( Ok ( bytes) ) => {
98+ task. counter += bytes. len ( ) as u64 ;
99+ Poll :: Ready ( Some ( Ok ( bytes) ) )
100+ }
101+ None => {
102+ let handler = task. clone ( ) . log ( ) ;
103+ * self = StreamLogger :: Logging ( handler) ;
104+ self . poll_next ( cx)
105+ }
106+ err => Poll :: Ready ( err) ,
78107 }
79- None => {
80- let counter = * counter;
81- let task = task. clone ( ) ;
82- let handler = exec. spawn_blocking ( move || task ( counter) ) ;
83- * self = Logger :: Logging ( handler) ;
84- self . poll_next ( cx)
85- }
86- err => Poll :: Ready ( err) ,
87- } ,
108+ }
88109
89- Logger :: Logging ( handler) => {
110+ StreamLogger :: Logging ( handler) => {
90111 futures:: ready!( Pin :: new( handler) . poll( cx) ) ;
91- * self = Logger :: Complete ;
112+ * self = StreamLogger :: Complete ;
92113 self . poll_next ( cx)
93114 }
94115
95- Logger :: Complete => Poll :: Ready ( None ) ,
116+ StreamLogger :: Complete => Poll :: Ready ( None ) ,
96117 }
97118 }
98119}
@@ -108,47 +129,57 @@ pub async fn logger<S: State>(mut ctx: Context<S>, next: Next) -> Result {
108129
109130 let method = ctx. method ( ) . clone ( ) ;
110131 let path = ctx. uri ( ) . path ( ) . to_string ( ) ;
111- let counter = 0 ;
112132 let exec = ctx. exec . clone ( ) ;
113- match result {
114- Ok ( ( ) ) => {
115- let status_code = ctx. status ( ) ;
116- ctx. resp_mut ( ) . wrapped ( move |stream| Logger :: Polling {
117- counter,
118- stream,
119- exec,
120- task : Arc :: new ( move |counter| {
121- info ! (
122- "<-- {} {} {}ms {} {}" ,
133+ let status_code = ctx. status ( ) ;
134+
135+ match ( & result, & mut ctx. resp_mut ( ) . body ) {
136+ ( Err ( err) , _) => {
137+ let message = err. message . clone ( ) ;
138+ ctx. exec
139+ . spawn_blocking ( move || {
140+ error ! (
141+ "<-- {} {} {}ms {}\n {}" ,
123142 method,
124143 path,
125144 start. elapsed( ) . as_millis( ) ,
126- ByteSize ( counter) ,
127145 status_code,
146+ message,
128147 ) ;
129- } ) ,
130- } ) ;
148+ } )
149+ . await
131150 }
132- Err ( ref err) => {
133- let message = err. message . clone ( ) ;
134- let status_code = err. status_code ;
135- ctx. resp_mut ( ) . wrapped ( move |stream| Logger :: Polling {
136- counter,
137- stream,
138- exec,
139- task : Arc :: new ( move |_counter| {
140- error ! (
141- "<-- {} {} {}ms {}\n {}" ,
151+ ( OK ( _) , Body :: Bytes ( bytes) ) => {
152+ let size = bytes. size_hint ( ) ;
153+ ctx. exec
154+ . spawn_blocking ( move || {
155+ info ! (
156+ "<-- {} {} {}ms {} {}" ,
142157 method,
143158 path,
144159 start. elapsed( ) . as_millis( ) ,
160+ ByteSize ( size as u64 ) ,
145161 status_code,
146- message,
147162 ) ;
148- } ) ,
163+ } )
164+ . await
165+ }
166+ ( Ok ( _) , Body :: Stream ( stream) ) => {
167+ let task = Box :: new ( LogTask {
168+ counter : 0 ,
169+ method,
170+ path,
171+ status_code,
172+ start,
173+ exec : ctx. exec . clone ( ) ,
149174 } ) ;
175+ let logger = StreamLogger :: Polling {
176+ stream : std:: mem:: take ( stream) ,
177+ task,
178+ } ;
179+ ctx. resp_mut ( ) . write_stream ( logger) ;
150180 }
151- } ;
181+ }
182+
152183 result
153184}
154185
@@ -158,6 +189,7 @@ mod tests {
158189 use crate :: http:: StatusCode ;
159190 use crate :: preload:: * ;
160191 use crate :: { throw, App } ;
192+ use async_std:: fs:: File ;
161193 use async_std:: task:: spawn;
162194 use lazy_static:: lazy_static;
163195 use log:: { Level , LevelFilter , Metadata , Record , SetLoggerError } ;
@@ -193,7 +225,7 @@ mod tests {
193225 async fn log ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
194226 init ( ) ?;
195227
196- // info
228+ // bytes info
197229 let ( addr, server) = App :: new ( ( ) )
198230 . gate_fn ( logger)
199231 . end ( move |mut ctx| async move {
@@ -232,6 +264,28 @@ mod tests {
232264 assert_eq ! ( "ERROR" , records[ 3 ] . 0 ) ;
233265 assert ! ( records[ 3 ] . 1 . starts_with( "<-- GET /" ) ) ;
234266 assert ! ( records[ 3 ] . 1 . ends_with( "Hello, World!" ) ) ;
267+
268+ // stream info
269+ let ( addr, server) = App :: new ( ( ) )
270+ . gate_fn ( logger)
271+ . end ( move |mut ctx| async move {
272+ ctx. resp_mut ( )
273+ . write_reader ( File :: open ( "../assets/welcome.html" ) . await ?) ;
274+ Ok ( ( ) )
275+ } )
276+ . run ( ) ?;
277+ spawn ( server) ;
278+ let resp = reqwest:: get ( & format ! ( "http://{}" , addr) ) . await ?;
279+ assert_eq ! ( StatusCode :: OK , resp. status( ) ) ;
280+ assert_eq ! ( 236 , resp. text( ) . await ?. len( ) ) ;
281+ let records = LOGGER . records . read ( ) . unwrap ( ) . clone ( ) ;
282+ assert_eq ! ( 6 , records. len( ) ) ;
283+ assert_eq ! ( "INFO" , records[ 4 ] . 0 ) ;
284+ assert_eq ! ( "--> GET /" , records[ 4 ] . 1 ) ;
285+ assert_eq ! ( "INFO" , records[ 5 ] . 0 ) ;
286+ assert ! ( records[ 5 ] . 1 . starts_with( "<-- GET /" ) ) ;
287+ assert ! ( records[ 5 ] . 1 . contains( "236 B" ) ) ;
288+ assert ! ( records[ 5 ] . 1 . ends_with( "200 OK" ) ) ;
235289 Ok ( ( ) )
236290 }
237291}
0 commit comments