1+ use core:: cmp;
2+ use core:: fmt:: { self , Write } ;
3+ use core:: mem:: MaybeUninit ;
4+
5+ use crate :: ffi:: NGX_MAX_ERROR_STR ;
6+
7+ /// Size of the static buffer used to format log messages.
8+ ///
9+ /// Approximates the remaining space in `u_char[NGX_MAX_ERROR_STR]` after writing the standard
10+ /// prefix
11+ pub const LOG_BUFFER_SIZE : usize = NGX_MAX_ERROR_STR as usize - b"1970/01/01 00:00:00 [info] 1#1: " . len ( ) ;
12+
113/// Utility function to provide typed checking of the mask's field state.
214#[ inline( always) ]
315pub fn check_mask ( mask : DebugMask , log_level : usize ) -> bool {
@@ -8,6 +20,21 @@ pub fn check_mask(mask: DebugMask, log_level: usize) -> bool {
820 true
921}
1022
23+ /// Format args into a provided buffer
24+ // May produce incomplete UTF-8 sequences. But any writes to `ngx_log_t` already can be truncated,
25+ // so nothing we can do here.
26+ #[ inline]
27+ pub fn write_fmt < ' a > ( buf : & ' a mut [ MaybeUninit < u8 > ] , args : fmt:: Arguments < ' _ > ) -> & ' a [ u8 ] {
28+ if let Some ( str) = args. as_str ( ) {
29+ str. as_bytes ( )
30+ } else {
31+ let mut buf = LogBuf :: from ( buf) ;
32+ // nothing we can or want to do on errors
33+ let _ = buf. write_fmt ( args) ;
34+ buf. filled ( )
35+ }
36+ }
37+
1138/// Write to logger at a specified level.
1239///
1340/// See [Logging](https://nginx.org/en/docs/dev/development_guide.html#logging)
@@ -18,8 +45,8 @@ macro_rules! ngx_log_error {
1845 let log = $log;
1946 let level = $level as $crate:: ffi:: ngx_uint_t;
2047 if level < unsafe { ( * log) . log_level } {
21- let message = :: std :: format! ( $ ( $arg ) + ) ;
22- let message = message . as_bytes ( ) ;
48+ let mut buf = [ const { :: core :: mem :: MaybeUninit :: < u8 > :: uninit ( ) } ; $crate :: log :: LOG_BUFFER_SIZE ] ;
49+ let message = $crate :: log :: write_fmt ( & mut buf , format_args! ( $ ( $arg ) + ) ) ;
2350 unsafe {
2451 $crate:: ffi:: ngx_log_error_core( level, log, 0 , c"%*s" . as_ptr( ) , message. len( ) , message. as_ptr( ) ) ;
2552 }
@@ -34,8 +61,8 @@ macro_rules! ngx_conf_log_error {
3461 let cf: * mut $crate:: ffi:: ngx_conf_t = $cf;
3562 let level = $level as $crate:: ffi:: ngx_uint_t;
3663 if level < unsafe { ( * ( * cf) . log) . log_level } {
37- let message = :: std :: format! ( $ ( $arg ) + ) ;
38- let message = message . as_bytes ( ) ;
64+ let mut buf = [ const { :: core :: mem :: MaybeUninit :: < u8 > :: uninit ( ) } ; $crate :: log :: LOG_BUFFER_SIZE ] ;
65+ let message = $crate :: log :: write_fmt ( & mut buf , format_args! ( $ ( $arg ) + ) ) ;
3966 unsafe {
4067 $crate:: ffi:: ngx_conf_log_error( level, cf, 0 , c"%*s" . as_ptr( ) , message. len( ) , message. as_ptr( ) ) ;
4168 }
@@ -50,8 +77,8 @@ macro_rules! ngx_log_debug {
5077 let log = $log;
5178 if $crate:: log:: check_mask( $mask, unsafe { ( * log) . log_level } ) {
5279 let level = $crate:: ffi:: NGX_LOG_DEBUG as $crate:: ffi:: ngx_uint_t;
53- let message = format! ( $ ( $arg ) + ) ;
54- let message = message . as_bytes ( ) ;
80+ let mut buf = [ const { :: core :: mem :: MaybeUninit :: < u8 > :: uninit ( ) } ; $crate :: log :: LOG_BUFFER_SIZE ] ;
81+ let message = $crate :: log :: write_fmt ( & mut buf , format_args! ( $ ( $arg ) + ) ) ;
5582 unsafe {
5683 $crate:: ffi:: ngx_log_error_core( level, log, 0 , c"%*s" . as_ptr( ) , message. len( ) , message. as_ptr( ) ) ;
5784 }
@@ -161,6 +188,52 @@ impl From<DebugMask> for u32 {
161188 }
162189}
163190
191+ /// Minimal subset of unstable core::io::{BorrowedBuf,BorrowedCursor}
192+ struct LogBuf < ' data > {
193+ buf : & ' data mut [ MaybeUninit < u8 > ] ,
194+ filled : usize ,
195+ }
196+
197+ impl < ' data > LogBuf < ' data > {
198+ pub fn filled ( & self ) -> & ' data [ u8 ] {
199+ // SAFETY: valid bytes have been written to self.buf[..self.filled]
200+ unsafe {
201+ let buf = self . buf . get_unchecked ( ..self . filled ) ;
202+ // inlined MaybeUninit::slice_assume_init_ref
203+ & * ( buf as * const [ MaybeUninit < u8 > ] as * const [ u8 ] )
204+ }
205+ }
206+
207+ pub fn append ( & mut self , buf : & [ u8 ] ) -> & mut Self {
208+ let n = cmp:: min ( self . buf . len ( ) - self . filled , buf. len ( ) ) ;
209+ unsafe {
210+ // SAFETY: The source buf has at least n bytes
211+ let src = buf. get_unchecked ( ..n) ;
212+ // SAFETY: &[u8] and &[MaybeUninit<u8>] have the same layout
213+ let src: & [ MaybeUninit < u8 > ] = core:: mem:: transmute ( src) ;
214+ // SAFETY: self.buf has at least n bytes available after self.filled
215+ self . buf
216+ . get_unchecked_mut ( self . filled ..self . filled + n)
217+ . copy_from_slice ( src) ;
218+ }
219+ self . filled += n;
220+ self
221+ }
222+ }
223+
224+ impl < ' data > From < & ' data mut [ MaybeUninit < u8 > ] > for LogBuf < ' data > {
225+ fn from ( buf : & ' data mut [ MaybeUninit < u8 > ] ) -> Self {
226+ Self { buf, filled : 0 }
227+ }
228+ }
229+
230+ impl fmt:: Write for LogBuf < ' _ > {
231+ fn write_str ( & mut self , s : & str ) -> fmt:: Result {
232+ self . append ( s. as_bytes ( ) ) ;
233+ Ok ( ( ) )
234+ }
235+ }
236+
164237#[ cfg( test) ]
165238mod tests {
166239
@@ -187,4 +260,25 @@ mod tests {
187260 r = check_mask ( DebugMask :: Alloc , mock. log_level ) ;
188261 assert ! ( !r) ;
189262 }
263+
264+ #[ test]
265+ fn log_buffer ( ) {
266+ use core:: str;
267+
268+ let mut buf = [ const { MaybeUninit :: < u8 > :: uninit ( ) } ; 32 ] ;
269+ let mut buf = LogBuf :: from ( & mut buf[ ..] ) ;
270+ let words = [ "Hello" , "World" ] ;
271+
272+ // normal write
273+ write ! ( & mut buf, "{} {}!" , words[ 0 ] , words[ 1 ] ) . unwrap ( ) ;
274+ assert_eq ! ( str :: from_utf8( buf. filled( ) ) , Ok ( "Hello World!" ) ) ;
275+
276+ // overflow results in truncated output
277+ write ! ( & mut buf, " This is a test, {}" , usize :: MAX ) . unwrap ( ) ;
278+ assert_eq ! ( str :: from_utf8( buf. filled( ) ) , Ok ( "Hello World! This is a test, 184" ) ) ;
279+
280+ // and any following writes are still safe
281+ write ! ( & mut buf, "test" ) . unwrap ( ) ;
282+ assert_eq ! ( str :: from_utf8( buf. filled( ) ) , Ok ( "Hello World! This is a test, 184" ) ) ;
283+ }
190284}
0 commit comments