@@ -71,6 +71,11 @@ impl Logger {
7171 }
7272 }
7373
74+ /// Creates a builder for configuring a `Logger`.
75+ pub fn builder ( ) -> LoggerBuilder {
76+ LoggerBuilder :: default ( )
77+ }
78+
7479 /// Returns the global logger (thread-safe). Initializes with a default
7580 /// console handler if not explicitly initialized via `init`.
7681 pub fn global ( ) -> & ' static Arc < Self > {
@@ -176,6 +181,93 @@ impl Logger {
176181pub enum InitError {
177182 AlreadyInitialized ,
178183}
184+
185+ /// Logger builder for ergonomic configuration.
186+ pub struct LoggerBuilder {
187+ name : String ,
188+ level : LogLevel ,
189+ handlers : Vec < Box < dyn handler:: Handler > > ,
190+ }
191+
192+ impl Default for LoggerBuilder {
193+ fn default ( ) -> Self {
194+ Self {
195+ name : "lambda-rs" . to_string ( ) ,
196+ level : LogLevel :: INFO ,
197+ handlers : Vec :: new ( ) ,
198+ }
199+ }
200+ }
201+
202+ impl LoggerBuilder {
203+ pub fn name ( mut self , name : & str ) -> Self {
204+ self . name = name. to_string ( ) ;
205+ self
206+ }
207+
208+ pub fn level ( mut self , level : LogLevel ) -> Self {
209+ self . level = level;
210+ self
211+ }
212+
213+ pub fn with_handler ( mut self , handler : Box < dyn handler:: Handler > ) -> Self {
214+ self . handlers . push ( handler) ;
215+ self
216+ }
217+
218+ pub fn build ( self ) -> Logger {
219+ let logger = Logger :: new ( self . level , & self . name ) ;
220+ for h in self . handlers {
221+ logger. add_handler ( h) ;
222+ }
223+ logger
224+ }
225+ }
226+
227+ /// Environment configuration helpers.
228+ pub mod env {
229+ use super :: {
230+ LogLevel ,
231+ Logger ,
232+ } ;
233+
234+ /// Parse a log level from a string like "trace", "debug", ...
235+ pub fn parse_level ( s : & str ) -> Option < LogLevel > {
236+ match s. trim ( ) . to_ascii_lowercase ( ) . as_str ( ) {
237+ "trace" => Some ( LogLevel :: TRACE ) ,
238+ "debug" => Some ( LogLevel :: DEBUG ) ,
239+ "info" => Some ( LogLevel :: INFO ) ,
240+ "warn" | "warning" => Some ( LogLevel :: WARN ) ,
241+ "error" => Some ( LogLevel :: ERROR ) ,
242+ "fatal" => Some ( LogLevel :: FATAL ) ,
243+ _ => None ,
244+ }
245+ }
246+
247+ /// Applies a level from the environment to the provided logger.
248+ ///
249+ /// Reads the specified `var` (default: "LAMBDA_LOG"). If it parses to a level,
250+ /// updates the logger's level.
251+ pub fn apply_env_level ( logger : & Logger , var : Option < & str > ) {
252+ let key = var. unwrap_or ( "LAMBDA_LOG" ) ;
253+ if let Ok ( val) = std:: env:: var ( key) {
254+ if let Some ( level) = parse_level ( & val) {
255+ logger. set_level ( level) ;
256+ }
257+ }
258+ }
259+
260+ /// Initialize a global logger with a console handler and apply env level.
261+ pub fn init_global_from_env ( ) -> Result < ( ) , super :: InitError > {
262+ let logger = Logger :: builder ( )
263+ . name ( "lambda-rs" )
264+ . level ( LogLevel :: INFO )
265+ . with_handler ( Box :: new ( crate :: handler:: ConsoleHandler :: new ( "lambda-rs" ) ) )
266+ . build ( ) ;
267+ apply_env_level ( & logger, Some ( "LAMBDA_LOG" ) ) ;
268+ super :: Logger :: init ( logger)
269+ }
270+ }
179271/// Returns whether the global logger would log at `level`.
180272pub fn enabled ( level : LogLevel ) -> bool {
181273 Logger :: global ( ) . compare_levels ( level)
@@ -425,4 +517,57 @@ mod tests {
425517 // If guard fails, formatting Boom would panic.
426518 super :: trace!( "{}" , Boom ) ;
427519 }
520+
521+ #[ test]
522+ fn builder_sets_name_level_and_handlers ( ) {
523+ #[ derive( Default ) ]
524+ struct Capture {
525+ out : Arc < Mutex < Vec < String > > > ,
526+ }
527+ impl handler:: Handler for Capture {
528+ fn log ( & self , record : & Record ) {
529+ self
530+ . out
531+ . lock ( )
532+ . unwrap ( )
533+ . push ( format ! ( "{}:{}" , record. target, record. level as u8 ) ) ;
534+ }
535+ }
536+
537+ let out = Arc :: new ( Mutex :: new ( Vec :: new ( ) ) ) ;
538+ let logger = Logger :: builder ( )
539+ . name ( "builder-app" )
540+ . level ( LogLevel :: WARN )
541+ . with_handler ( Box :: new ( Capture { out : out. clone ( ) } ) )
542+ . build ( ) ;
543+
544+ logger. info ( "drop" . to_string ( ) ) ;
545+ logger. error ( "keep" . to_string ( ) ) ;
546+
547+ let v = out. lock ( ) . unwrap ( ) ;
548+ assert_eq ! ( v. len( ) , 1 ) ;
549+ assert_eq ! ( v[ 0 ] , "builder-app:4" ) ; // ERROR => 4 per to_u8 mapping
550+ }
551+
552+ #[ test]
553+ fn env_parse_and_apply_level ( ) {
554+ // no panic if env missing
555+ super :: env:: apply_env_level (
556+ & Logger :: new ( LogLevel :: TRACE , "tmp" ) ,
557+ Some ( "__NOT_SET__" ) ,
558+ ) ;
559+
560+ assert_eq ! ( super :: env:: parse_level( "trace" ) , Some ( LogLevel :: TRACE ) ) ;
561+ assert_eq ! ( super :: env:: parse_level( "DEBUG" ) , Some ( LogLevel :: DEBUG ) ) ;
562+ assert_eq ! ( super :: env:: parse_level( "warning" ) , Some ( LogLevel :: WARN ) ) ;
563+ assert_eq ! ( super :: env:: parse_level( "nope" ) , None ) ;
564+
565+ // apply
566+ let logger = Logger :: new ( LogLevel :: ERROR , "tmp" ) ;
567+ std:: env:: set_var ( "LAMBDA_LOG" , "info" ) ;
568+ super :: env:: apply_env_level ( & logger, Some ( "LAMBDA_LOG" ) ) ;
569+ assert ! ( logger. compare_levels( LogLevel :: INFO ) ) ;
570+ // restore
571+ std:: env:: remove_var ( "LAMBDA_LOG" ) ;
572+ }
428573}
0 commit comments