@ -43,6 +43,15 @@
- export ( [ config_to_id / 1 ] ) .
- export ( [ config_to_id / 1 ] ) .
- define ( DEFAULT_LOG_LEVEL , info ) .
- define ( DEFAULT_ROTATION_SIZE , 10485760 ) . % % 10 mb
- define ( DEFAULT_ROTATION_DATE , " $D0 " ) . % % midnight
- define ( DEFAULT_ROTATION_COUNT , 5 ) .
- define ( DEFAULT_SYNC_LEVEL , error ) .
- define ( DEFAULT_SYNC_INTERVAL , 1000 ) .
- define ( DEFAULT_SYNC_SIZE , 1024 * 64 ) . % % 64 kb
- define ( DEFAULT_CHECK_INTERVAL , 1000 ) .
- record ( state , {
- record ( state , {
name : : string ( ) ,
name : : string ( ) ,
level : : { 'mask' , integer ( ) } ,
level : : { 'mask' , integer ( ) } ,
@ -53,35 +62,53 @@
date ,
date ,
count = 10 ,
count = 10 ,
formatter ,
formatter ,
formatter_config
formatter_config ,
sync_on ,
check_interval = ? DEFAULT_CHECK_INTERVAL ,
sync_interval = ? DEFAULT_SYNC_INTERVAL ,
sync_size = ? DEFAULT_SYNC_SIZE ,
last_check = os : timestamp ( )
} ) .
} ) .
% % @ private
% % @ private
- spec init ( [ { string ( ) , lager : log_level ( ) } , . . . ] ) - > { ok , #state { } } .
- spec init ( [ { string ( ) , lager : log_level ( ) } , . . . ] ) - > { ok , #state { } } .
init ( { FileName , LogLevel } ) when is_list ( FileName ) , is_atom ( LogLevel ) - >
% % backwards compatability hack
init ( [ { file , FileName } , { level , LogLevel } ] ) ;
init ( { FileName , LogLevel , Size , Date , Count } ) when is_list ( FileName ) , is_atom ( LogLevel ) - >
% % backwards compatability hack
init ( [ { file , FileName } , { level , LogLevel } , { size , Size } , { date , Date } , { count , Count } ] ) ;
init ( [ { FileName , LogLevel , Size , Date , Count } , { Formatter , FormatterConfig } ] ) when is_list ( FileName ) , is_atom ( LogLevel ) , is_atom ( Formatter ) - >
% % backwards compatability hack
init ( [ { file , FileName } , { level , LogLevel } , { size , Size } , { date , Date } , { count , Count } , { formatter , Formatter } , { formatter_config , FormatterConfig } ] ) ;
init ( [ LogFile , { Formatter } ] ) - >
init ( [ LogFile , { Formatter } ] ) - >
% % backwards compatability hack
init ( [ LogFile , { Formatter , [ ] } ] ) ;
init ( [ LogFile , { Formatter , [ ] } ] ) ;
init ( [ LogFile , { Formatter , FormatterConfig } ] ) - >
case validate_logfile ( LogFile ) of
{ Name , Level , Size , Date , Count } - >
init ( [ { FileName , LogLevel } , { Formatter , FormatterConfig } ] ) when is_list ( FileName ) , is_atom ( LogLevel ) , is_atom ( Formatter ) - >
% % backwards compatability hack
init ( [ { file , FileName } , { level , LogLevel } , { formatter , Formatter } , { formatter_config , FormatterConfig } ] ) ;
init ( LogFileConfig ) when is_list ( LogFileConfig ) - >
case validate_logfile_proplist ( LogFileConfig ) of
false - >
% % falied to validate config
ignore ;
Config - >
% % probabably a better way to do this , but whatever
[ Name , Level , Date , Size , Count , SyncInterval , SyncSize , SyncOn , CheckInterval , Formatter , FormatterConfig ] =
[ proplists : get_value ( Key , Config ) | | Key < - [ file , level , date , size , count , sync_interval , sync_size , sync_on , check_interval , formatter , formatter_config ] ] ,
schedule_rotation ( Name , Date ) ,
schedule_rotation ( Name , Date ) ,
State = case lager_util : open_logfile ( Name , true ) of
State0 = #state { name = Name , level = Level , size = Size , date = Date , count = Count , formatter = Formatter ,
formatter_config = FormatterConfig , sync_on = SyncOn , sync_interval = SyncInterval , sync_size = SyncSize ,
check_interval = CheckInterval } ,
State = case lager_util : open_logfile ( Name , { SyncSize , SyncInterval } ) of
{ ok , { FD , Inode , _ } } - >
{ ok , { FD , Inode , _ } } - >
#state { name = Name , level = Level ,
fd = FD , inode = Inode , size = Size , date = Date , count = Count , formatter = Formatter , formatter_config = FormatterConfig } ;
State0 #state { fd = FD , inode = Inode } ;
{ error , Reason } - >
{ error , Reason } - >
? INT_LOG ( error , " Failed to open log file ~s with error ~s " ,
[ Name , file : format_error ( Reason ) ] ) ,
#state { name = Name , level = Level ,
flap = true , size = Size , date = Date , count = Count , formatter = Formatter , formatter_config = FormatterConfig }
? INT_LOG ( error , " Failed to open log file ~s with error ~s " , [ Name , file : format_error ( Reason ) ] ) ,
State0 #state { flap = true }
end ,
end ,
{ ok , State } ;
false - >
ignore
end ;
init ( LogFile ) - >
init ( [ LogFile , { lager_default_formatter , [ ] } ] ) .
{ ok , State }
end .
% % @ private
% % @ private
handle_call ( { set_loglevel , Level } , #state { name = Ident } = State ) - >
handle_call ( { set_loglevel , Level } , #state { name = Ident } = State ) - >
@ -102,7 +129,7 @@ handle_event({log, Message},
#state { name = Name , level = L , formatter = Formatter , formatter_config = FormatConfig } = State ) - >
#state { name = Name , level = L , formatter = Formatter , formatter_config = FormatConfig } = State ) - >
case lager_util : is_loggable ( Message , L , { lager_file_backend , Name } ) of
case lager_util : is_loggable ( Message , L , { lager_file_backend , Name } ) of
true - >
true - >
{ ok , write ( State , lager_msg : severity_as_int ( Message ) , Formatter : format ( Message , FormatConfig ) ) } ;
{ ok , write ( State , lager_msg : timestamp ( Message ) , lager_msg : severity_as_int( Message ) , Formatter : format ( Message , FormatConfig ) ) } ;
false - >
false - >
{ ok , State }
{ ok , State }
end ;
end ;
@ -129,93 +156,71 @@ code_change(_OldVsn, State, _Extra) ->
{ ok , State } .
{ ok , State } .
% % convert the config into a gen_event handler ID
% % convert the config into a gen_event handler ID
config_to_id ( { Name , _ Severity } ) - >
config_to_id ( { Name , _ Severity } ) when is_list ( Name ) - >
{ ? MODULE , Name } ;
{ ? MODULE , Name } ;
config_to_id ( { Name , _ Severity , _ Size , _ Rotation , _ Count } ) - >
config_to_id ( { Name , _ Severity , _ Size , _ Rotation , _ Count } ) - >
{ ? MODULE , Name } ;
{ ? MODULE , Name } ;
config_to_id ( [ { Name , _ Severity , _ Size , _ Rotation , _ Count } , _ Format ] ) - >
config_to_id ( [ { Name , _ Severity , _ Size , _ Rotation , _ Count } , _ Format ] ) - >
{ ? MODULE , Name } .
{ ? MODULE , Name } ;
config_to_id ( [ { Name , _ Severity } , _ Format ] ) when is_list ( Name ) - >
{ ? MODULE , Name } ;
config_to_id ( Config ) - >
case proplists : get_value ( file , Config ) of
undefined - >
erlang : error ( no_file ) ;
File - >
{ ? MODULE , File }
end .
write ( #state { name = Name , fd = FD , inode = Inode , flap = Flap , size = RotSize ,
write ( #state { name = Name , fd = FD , inode = Inode , flap = Flap , size = RotSize ,
count = Count } = State , Level , Msg ) - >
case lager_util : ensure_logfile ( Name , FD , Inode , true ) of
{ ok , { _ , _ , Size } } when RotSize / = 0 , Size > RotSize - >
lager_util : rotate_logfile ( Name , Count ) ,
write ( State , Level , Msg ) ;
{ ok , { NewFD , NewInode , _ } } - >
% % delayed_write doesn ' t report errors
_ = file : write ( NewFD , Msg ) ,
case Level of
_ when Level =< ? ERROR - >
% % force a sync on any message at error severity or above
Flap2 = case file : datasync ( NewFD ) of
{ error , Reason2 } when Flap == false - >
? INT_LOG ( error , " Failed to write log message to file ~s : ~s " ,
[ Name , file : format_error ( Reason2 ) ] ) ,
true ;
ok - >
false ;
count = Count } = State , Timestamp , Level , Msg ) - >
LastCheck = timer : now_diff ( os : timestamp ( ) , Timestamp ) div 1000 ,
case LastCheck > = State #state.check_interval orelse FD == undefined of
true - >
% % need to check for rotation
case lager_util : ensure_logfile ( Name , FD , Inode , { State #state.sync_size , State #state.sync_interval } ) of
{ ok , { _ , _ , Size } } when RotSize / = 0 , Size > RotSize - >
lager_util : rotate_logfile ( Name , Count ) ,
% % go around the loop again , we ' ll do another rotation check and hit the next clause here
write ( State , Timestamp , Level , Msg ) ;
{ ok , { NewFD , NewInode , _ } } - >
% % update our last check and try again
do_write ( State #state { last_check = Timestamp , fd = NewFD , inode = NewInode } , Level , Msg ) ;
{ error , Reason } - >
case Flap of
true - >
State ;
_ - >
_ - >
Flap
end ,
State #state { fd = NewFD , inode = NewInode , flap = Flap2 } ;
_ - >
State #state { fd = NewFD , inode = NewInode }
? INT_LOG ( error , " Failed to reopen log file ~s with error ~s " , [ Name , file : format_error ( Reason ) ] ) ,
State #state { flap = true }
end
end ;
end ;
{ error , Reason } - >
case Flap of
true - >
State ;
_ - >
? INT_LOG ( error , " Failed to reopen log file ~s with error ~s " ,
[ Name , file : format_error ( Reason ) ] ) ,
State #state { flap = true }
end
false - >
do_write ( State , Level , Msg )
end .
end .
validate_logfile ( { Name , Level } ) - >
case validate_loglevel ( Level ) of
false - >
? INT_LOG ( error , " Invalid log level of ~p for ~s . " ,
[ Level , Name ] ) ,
false ;
Levels - >
{ Name , Levels , 0 , undefined , 0 }
end ;
validate_logfile ( { Name , Level , Size , Date , Count } ) - >
ValidLevel = validate_loglevel ( Level ) ,
ValidSize = ( is_integer ( Size ) andalso Size > = 0 ) ,
ValidCount = ( is_integer ( Count ) andalso Count > = 0 ) ,
case { ValidLevel , ValidSize , ValidCount } of
{ false , _ , _ } - >
? INT_LOG ( error , " Invalid log level of ~p for ~s . " ,
[ Level , Name ] ) ,
false ;
{ _ , false , _ } - >
? INT_LOG ( error , " Invalid rotation size of ~p for ~s . " ,
[ Size , Name ] ) ,
false ;
{ _ , _ , false } - >
? INT_LOG ( error , " Invalid rotation count of ~p for ~s . " ,
[ Count , Name ] ) ,
false ;
{ Levels , true , true } - >
case lager_util : parse_rotation_date_spec ( Date ) of
{ ok , Spec } - >
{ Name , Levels , Size , Spec , Count } ;
{ error , _ } when Date == " " - >
% % blank ones are fine .
{ Name , Levels , Size , undefined , Count } ;
{ error , _ } - >
? INT_LOG ( error , " Invalid rotation date of ~p for ~s . " ,
[ Date , Name ] ) ,
false
end
end ;
validate_logfile ( H ) - >
? INT_LOG ( error , " Invalid log file config ~p . " , [ H ] ) ,
false .
do_write ( #state { fd = FD , name = Name , flap = Flap } = State , Level , Msg ) - >
% % delayed_write doesn ' t report errors
_ = file : write ( FD , Msg ) ,
{ mask , SyncLevel } = State #state.sync_on ,
case ( Level band SyncLevel ) / = 0 of
true - >
% % force a sync on any message that matches the 'sync_on' bitmask
Flap2 = case file : datasync ( FD ) of
{ error , Reason2 } when Flap == false - >
? INT_LOG ( error , " Failed to write log message to file ~s : ~s " ,
[ Name , file : format_error ( Reason2 ) ] ) ,
true ;
ok - >
false ;
_ - >
Flap
end ,
State #state { flap = Flap2 } ;
_ - >
State
end .
validate_loglevel ( Level ) - >
validate_loglevel ( Level ) - >
try lager_util : config_to_mask ( Level ) of
try lager_util : config_to_mask ( Level ) of
@ -226,6 +231,113 @@ validate_loglevel(Level) ->
false
false
end .
end .
validate_logfile_proplist ( List ) - >
try validate_logfile_proplist ( List , [ ] ) of
Res - >
case proplists : get_value ( file , Res ) of
undefined - >
? INT_LOG ( error , " Missing required file option " , [ ] ) ,
false ;
_ File - >
% % merge with the default options
{ ok , DefaultRotationDate } = lager_util : parse_rotation_date_spec ( ? DEFAULT_ROTATION_DATE ) ,
lists : keymerge ( 1 , lists : sort ( Res ) , lists : sort ( [
{ level , validate_loglevel ( ? DEFAULT_LOG_LEVEL ) } , { date , DefaultRotationDate } ,
{ size , ? DEFAULT_ROTATION_SIZE } , { count , ? DEFAULT_ROTATION_COUNT } ,
{ sync_on , validate_loglevel ( ? DEFAULT_SYNC_LEVEL ) } , { sync_interval , ? DEFAULT_SYNC_INTERVAL } ,
{ sync_size , ? DEFAULT_SYNC_SIZE } , { check_interval , ? DEFAULT_CHECK_INTERVAL } ,
{ formatter , lager_default_formatter } , { formatter_config , [ ] }
] ) )
end
catch
{ bad_config , Msg , Value } - >
? INT_LOG ( error , " ~s ~p for file ~p " ,
[ Msg , Value , proplists : get_value ( file , List ) ] ) ,
false
end .
validate_logfile_proplist ( [ ] , Acc ) - >
Acc ;
validate_logfile_proplist ( [ { file , File } | Tail ] , Acc ) - >
% % is there any reasonable validation we can do here ?
validate_logfile_proplist ( Tail , [ { file , File } | Acc ] ) ;
validate_logfile_proplist ( [ { level , Level } | Tail ] , Acc ) - >
case validate_loglevel ( Level ) of
false - >
throw ( { bad_config , " Invalid loglevel " , Level } ) ;
Res - >
validate_logfile_proplist ( Tail , [ { level , Res } | Acc ] )
end ;
validate_logfile_proplist ( [ { size , Size } | Tail ] , Acc ) - >
case Size of
S when is_integer ( S ) , S > = 0 - >
validate_logfile_proplist ( Tail , [ { size , Size } | Acc ] ) ;
_ - >
throw ( { bad_config , " Invalid rotation size " , Size } )
end ;
validate_logfile_proplist ( [ { count , Count } | Tail ] , Acc ) - >
case Count of
C when is_integer ( C ) , C > = 0 - >
validate_logfile_proplist ( Tail , [ { count , Count } | Acc ] ) ;
_ - >
throw ( { bad_config , " Invalid rotation count " , Count } )
end ;
validate_logfile_proplist ( [ { date , Date } | Tail ] , Acc ) - >
case lager_util : parse_rotation_date_spec ( Date ) of
{ ok , Spec } - >
validate_logfile_proplist ( Tail , [ { date , Spec } | Acc ] ) ;
{ error , _ } when Date == " " - >
% % legacy config allowed blanks
validate_logfile_proplist ( Tail , Acc ) ;
{ error , _ } - >
throw ( { bad_config , " Invalid rotation date " , Date } )
end ;
validate_logfile_proplist ( [ { sync_interval , SyncInt } | Tail ] , Acc ) - >
case SyncInt of
Val when is_integer ( Val ) , Val > = 0 - >
validate_logfile_proplist ( Tail , [ { sync_interval , Val } | Acc ] ) ;
_ - >
throw ( { bad_config , " Invalid sync interval " , SyncInt } )
end ;
validate_logfile_proplist ( [ { sync_size , SyncSize } | Tail ] , Acc ) - >
case SyncSize of
Val when is_integer ( Val ) , Val > = 0 - >
validate_logfile_proplist ( Tail , [ { sync_size , Val } | Acc ] ) ;
_ - >
throw ( { bad_config , " Invalid sync size " , SyncSize } )
end ;
validate_logfile_proplist ( [ { check_interval , CheckInt } | Tail ] , Acc ) - >
case CheckInt of
Val when is_integer ( Val ) , Val > = 0 - >
validate_logfile_proplist ( Tail , [ { check_interval , Val } | Acc ] ) ;
always - >
validate_logfile_proplist ( Tail , [ { check_interval , 0 } | Acc ] ) ;
_ - >
throw ( { bad_config , " Invalid check interval " , CheckInt } )
end ;
validate_logfile_proplist ( [ { sync_on , Level } | Tail ] , Acc ) - >
case validate_loglevel ( Level ) of
false - >
throw ( { bad_config , " Invalid sync on level " , Level } ) ;
Res - >
validate_logfile_proplist ( Tail , [ { sync_on , Res } | Acc ] )
end ;
validate_logfile_proplist ( [ { formatter , Fmt } | Tail ] , Acc ) - >
case is_atom ( Fmt ) of
true - >
validate_logfile_proplist ( Tail , [ { formatter , Fmt } | Acc ] ) ;
false - >
throw ( { bad_config , " Invalid formatter module " , Fmt } )
end ;
validate_logfile_proplist ( [ { formatter_config , FmtCfg } | Tail ] , Acc ) - >
case is_list ( FmtCfg ) of
true - >
validate_logfile_proplist ( Tail , [ { formatter_config , FmtCfg } | Acc ] ) ;
false - >
throw ( { bad_config , " Invalid formatter config " , FmtCfg } )
end ;
validate_logfile_proplist ( [ Other | _ Tail ] , _ Acc ) - >
throw ( { bad_config , " Invalid option " , Other } ) .
schedule_rotation ( _ , undefined ) - >
schedule_rotation ( _ , undefined ) - >
ok ;
ok ;
@ -244,16 +356,22 @@ get_loglevel_test() ->
? assertEqual ( Level2 , lager_util : config_to_mask ( warning ) ) .
? assertEqual ( Level2 , lager_util : config_to_mask ( warning ) ) .
rotation_test ( ) - >
rotation_test ( ) - >
{ ok , { FD , Inode , _ } } = lager_util : open_logfile ( " test.log " , true ) ,
SyncLevel = validate_loglevel ( ? DEFAULT_SYNC_LEVEL ) ,
SyncSize = ? DEFAULT_SYNC_SIZE ,
SyncInterval = ? DEFAULT_SYNC_INTERVAL ,
CheckInterval = 0 , % % hard to test delayed mode
{ ok , { FD , Inode , _ } } = lager_util : open_logfile ( " test.log " , { SyncSize , SyncInterval } ) ,
State0 = #state { name = " test.log " , level = ? DEBUG , fd = FD , inode = Inode , sync_on = SyncLevel ,
sync_size = SyncSize , sync_interval = SyncInterval , check_interval = CheckInterval } ,
? assertMatch ( #state { name = " test.log " , level = ? DEBUG , fd = FD , inode = Inode } ,
? assertMatch ( #state { name = " test.log " , level = ? DEBUG , fd = FD , inode = Inode } ,
write ( #state { name = " test.log " , level = ? DEBUG , fd = FD , inode = Inode } , 0 , " hello world " ) ) ,
write ( State0 , os : timestamp ( ) , ? DEBUG , " hello world " ) ) ,
file : delete ( " test.log " ) ,
file : delete ( " test.log " ) ,
Result = write ( #state { name = " test.log " , level = ? DEBUG , fd = FD , inode = Inode } , 0 , " hello world " ) ,
Result = write ( State0 , os : timestamp ( ) , ? DEBUG , " hello world " ) ,
% % assert file has changed
% % assert file has changed
? assert ( #state { name = " test.log " , level = ? DEBUG , fd = FD , inode = Inode } =/= Result ) ,
? assert ( #state { name = " test.log " , level = ? DEBUG , fd = FD , inode = Inode } =/= Result ) ,
? assertMatch ( #state { name = " test.log " , level = ? DEBUG } , Result ) ,
? assertMatch ( #state { name = " test.log " , level = ? DEBUG } , Result ) ,
file : rename ( " test.log " , " test.log.1 " ) ,
file : rename ( " test.log " , " test.log.1 " ) ,
Result2 = write ( Result , 0 , " hello world " ) ,
Result2 = write ( Result , os : timestamp ( ) , ? DEBUG , " hello world " ) ,
% % assert file has changed
% % assert file has changed
? assert ( Result =/= Result2 ) ,
? assert ( Result =/= Result2 ) ,
? assertMatch ( #state { name = " test.log " , level = ? DEBUG } , Result2 ) ,
? assertMatch ( #state { name = " test.log " , level = ? DEBUG } , Result2 ) ,
@ -271,13 +389,15 @@ filesystem_test_() ->
end ,
end ,
fun ( _ ) - >
fun ( _ ) - >
file : delete ( " test.log " ) ,
file : delete ( " test.log " ) ,
file : delete ( " test.log.0 " ) ,
file : delete ( " test.log.1 " ) ,
application : stop ( lager ) ,
application : stop ( lager ) ,
error_logger : tty ( true )
error_logger : tty ( true )
end ,
end ,
[
[
{ " under normal circumstances, file should be opened " ,
{ " under normal circumstances, file should be opened " ,
fun ( ) - >
fun ( ) - >
gen_event : add_handler ( lager_event , lager_file_backend , { " test.log " , info } ) ,
gen_event : add_handler ( lager_event , lager_file_backend , [ {" test.log " , info } , { lager_default_formatter } ] ) ,
lager : log ( error , self ( ) , " Test message " ) ,
lager : log ( error , self ( ) , " Test message " ) ,
{ ok , Bin } = file : read_file ( " test.log " ) ,
{ ok , Bin } = file : read_file ( " test.log " ) ,
Pid = pid_to_list ( self ( ) ) ,
Pid = pid_to_list ( self ( ) ) ,
@ -288,7 +408,7 @@ filesystem_test_() ->
fun ( ) - >
fun ( ) - >
{ ok , FInfo } = file : read_file_info ( " test.log " ) ,
{ ok , FInfo } = file : read_file_info ( " test.log " ) ,
file : write_file_info ( " test.log " , FInfo #file_info { mode = 0 } ) ,
file : write_file_info ( " test.log " , FInfo #file_info { mode = 0 } ) ,
gen_event : add_handler ( lager_event , lager_file_backend , { " test.log " , info } ) ,
gen_event : add_handler ( lager_event , lager_file_backend , { " test.log " , info , 10 * 1024 * 1024 , " $D0 " , 5 }) ,
? assertEqual ( 1 , lager_test_backend : count ( ) ) ,
? assertEqual ( 1 , lager_test_backend : count ( ) ) ,
{ _ Level , _ Time , Message , _ Metadata } = lager_test_backend : pop ( ) ,
{ _ Level , _ Time , Message , _ Metadata } = lager_test_backend : pop ( ) ,
? assertEqual ( " Failed to open log file test.log with error permission denied " , lists : flatten ( Message ) )
? assertEqual ( " Failed to open log file test.log with error permission denied " , lists : flatten ( Message ) )
@ -296,7 +416,7 @@ filesystem_test_() ->
} ,
} ,
{ " file that becomes unavailable at runtime should trigger an error message " ,
{ " file that becomes unavailable at runtime should trigger an error message " ,
fun ( ) - >
fun ( ) - >
gen_event : add_handler ( lager_event , lager_file_backend , { " test.log " , info } ) ,
gen_event : add_handler ( lager_event , lager_file_backend , [ {file , " test.log " } , { level , info } , { check_interval , 0 } ] ) ,
? assertEqual ( 0 , lager_test_backend : count ( ) ) ,
? assertEqual ( 0 , lager_test_backend : count ( ) ) ,
lager : log ( error , self ( ) , " Test message " ) ,
lager : log ( error , self ( ) , " Test message " ) ,
? assertEqual ( 1 , lager_test_backend : count ( ) ) ,
? assertEqual ( 1 , lager_test_backend : count ( ) ) ,
@ -317,7 +437,7 @@ filesystem_test_() ->
{ ok , FInfo } = file : read_file_info ( " test.log " ) ,
{ ok , FInfo } = file : read_file_info ( " test.log " ) ,
OldPerms = FInfo #file_info.mode ,
OldPerms = FInfo #file_info.mode ,
file : write_file_info ( " test.log " , FInfo #file_info { mode = 0 } ) ,
file : write_file_info ( " test.log " , FInfo #file_info { mode = 0 } ) ,
gen_event : add_handler ( lager_event , lager_file_backend , { " test.log " , info } ) ,
gen_event : add_handler ( lager_event , lager_file_backend , [ {file , " test.log " } , { check_interval , 0 } ] ) ,
? assertEqual ( 1 , lager_test_backend : count ( ) ) ,
? assertEqual ( 1 , lager_test_backend : count ( ) ) ,
{ _ Level , _ Time , Message , _ Metadata } = lager_test_backend : pop ( ) ,
{ _ Level , _ Time , Message , _ Metadata } = lager_test_backend : pop ( ) ,
? assertEqual ( " Failed to open log file test.log with error permission denied " , lists : flatten ( Message ) ) ,
? assertEqual ( " Failed to open log file test.log with error permission denied " , lists : flatten ( Message ) ) ,
@ -330,7 +450,7 @@ filesystem_test_() ->
} ,
} ,
{ " external logfile rotation/deletion should be handled " ,
{ " external logfile rotation/deletion should be handled " ,
fun ( ) - >
fun ( ) - >
gen_event : add_handler ( lager_event , lager_file_backend , { " test.log " , info } ) ,
gen_event : add_handler ( lager_event , lager_file_backend , [ {file , " test.log " } , { level , info } , { check_interval , 0 } ] ) ,
? assertEqual ( 0 , lager_test_backend : count ( ) ) ,
? assertEqual ( 0 , lager_test_backend : count ( ) ) ,
lager : log ( error , self ( ) , " Test message1 " ) ,
lager : log ( error , self ( ) , " Test message1 " ) ,
? assertEqual ( 1 , lager_test_backend : count ( ) ) ,
? assertEqual ( 1 , lager_test_backend : count ( ) ) ,
@ -346,6 +466,68 @@ filesystem_test_() ->
? assertMatch ( [ _ , _ , " [error] " , Pid , " Test message3 \n " ] , re : split ( Bin2 , " " , [ { return , list } , { parts , 5 } ] ) )
? assertMatch ( [ _ , _ , " [error] " , Pid , " Test message3 \n " ] , re : split ( Bin2 , " " , [ { return , list } , { parts , 5 } ] ) )
end
end
} ,
} ,
{ " internal size rotation should work " ,
fun ( ) - >
gen_event : add_handler ( lager_event , lager_file_backend , [ { file , " test.log " } , { level , info } , { check_interval , 0 } , { size , 10 } ] ) ,
lager : log ( error , self ( ) , " Test message1 " ) ,
lager : log ( error , self ( ) , " Test message1 " ) ,
? assert ( filelib : is_regular ( " test.log.0 " ) )
end
} ,
{ " internal time rotation should work " ,
fun ( ) - >
gen_event : add_handler ( lager_event , lager_file_backend , [ { file , " test.log " } , { level , info } , { check_interval , 1000 } ] ) ,
lager : log ( error , self ( ) , " Test message1 " ) ,
lager : log ( error , self ( ) , " Test message1 " ) ,
whereis ( lager_event ) ! { rotate , " test.log " } ,
lager : log ( error , self ( ) , " Test message1 " ) ,
? assert ( filelib : is_regular ( " test.log.0 " ) )
end
} ,
{ " sync_on option should work " ,
fun ( ) - >
gen_event : add_handler ( lager_event , lager_file_backend , [ { file , " test.log " } , { level , info } , { sync_on , " =info " } , { check_interval , 5000 } , { sync_interval , 5000 } ] ) ,
lager : log ( error , self ( ) , " Test message1 " ) ,
lager : log ( error , self ( ) , " Test message1 " ) ,
? assertEqual ( { ok , < < > > } , file : read_file ( " test.log " ) ) ,
lager : log ( info , self ( ) , " Test message1 " ) ,
{ ok , Bin } = file : read_file ( " test.log " ) ,
? assert ( < < > > / = Bin )
end
} ,
{ " sync_on none option should work (also tests sync_interval) " ,
fun ( ) - >
gen_event : add_handler ( lager_event , lager_file_backend , [ { file , " test.log " } , { level , info } , { sync_on , " none " } , { check_interval , 5000 } , { sync_interval , 1000 } ] ) ,
lager : log ( error , self ( ) , " Test message1 " ) ,
lager : log ( error , self ( ) , " Test message1 " ) ,
? assertEqual ( { ok , < < > > } , file : read_file ( " test.log " ) ) ,
lager : log ( info , self ( ) , " Test message1 " ) ,
? assertEqual ( { ok , < < > > } , file : read_file ( " test.log " ) ) ,
timer : sleep ( 1000 ) ,
{ ok , Bin } = file : read_file ( " test.log " ) ,
? assert ( < < > > / = Bin )
end
} ,
{ " sync_size option should work " ,
fun ( ) - >
gen_event : add_handler ( lager_event , lager_file_backend , [ { file , " test.log " } , { level , info } , { sync_on , " none " } , { check_interval , 5001 } , { sync_size , 640 } , { sync_interval , 5001 } ] ) ,
lager : log ( error , self ( ) , " Test messageis64bytes " ) ,
lager : log ( error , self ( ) , " Test messageis64bytes " ) ,
lager : log ( error , self ( ) , " Test messageis64bytes " ) ,
lager : log ( error , self ( ) , " Test messageis64bytes " ) ,
lager : log ( error , self ( ) , " Test messageis64bytes " ) ,
? assertEqual ( { ok , < < > > } , file : read_file ( " test.log " ) ) ,
lager : log ( error , self ( ) , " Test messageis64bytes " ) ,
lager : log ( error , self ( ) , " Test messageis64bytes " ) ,
lager : log ( error , self ( ) , " Test messageis64bytes " ) ,
lager : log ( error , self ( ) , " Test messageis64bytes " ) ,
? assertEqual ( { ok , < < > > } , file : read_file ( " test.log " ) ) ,
% % now we ' ve written enough bytes
lager : log ( error , self ( ) , " Test messageis64bytes " ) ,
{ ok , Bin } = file : read_file ( " test.log " ) ,
? assert ( < < > > / = Bin )
end
} ,
{ " runtime level changes " ,
{ " runtime level changes " ,
fun ( ) - >
fun ( ) - >
gen_event : add_handler ( lager_event , { lager_file_backend , " test.log " } , { " test.log " , info } ) ,
gen_event : add_handler ( lager_event , { lager_file_backend , " test.log " } , { " test.log " , info } ) ,
@ -365,7 +547,7 @@ filesystem_test_() ->
} ,
} ,
{ " invalid runtime level changes " ,
{ " invalid runtime level changes " ,
fun ( ) - >
fun ( ) - >
gen_event : add_handler ( lager_event , lager_file_backend , { " test.log " , info } ) ,
gen_event : add_handler ( lager_event , lager_file_backend , [ {" test.log " , info , 10 * 1024 * 1024 , " $D0 " , 5 }, { lager_default_formatter } ] ) ,
gen_event : add_handler ( lager_event , lager_file_backend , { " test3.log " , info } ) ,
gen_event : add_handler ( lager_event , lager_file_backend , { " test3.log " , info } ) ,
? assertEqual ( { error , bad_module } , lager : set_loglevel ( lager_file_backend , " test.log " , warning ) )
? assertEqual ( { error , bad_module } , lager : set_loglevel ( lager_file_backend , " test.log " , warning ) )
end
end
@ -389,7 +571,7 @@ filesystem_test_() ->
{ " tracing should not duplicate messages " ,
{ " tracing should not duplicate messages " ,
fun ( ) - >
fun ( ) - >
gen_event : add_handler ( lager_event , lager_file_backend ,
gen_event : add_handler ( lager_event , lager_file_backend ,
{ " test.log " , critical } ) ,
[ {file , " test.log " } , { level , critical } , { check_interval , always } ] ) ,
lager : critical ( " Test message " ) ,
lager : critical ( " Test message " ) ,
{ ok , Bin1 } = file : read_file ( " test.log " ) ,
{ ok , Bin1 } = file : read_file ( " test.log " ) ,
? assertMatch ( [ _ , _ , " [critical] " , _ , " Test message \n " ] , re : split ( Bin1 , " " , [ { return , list } , { parts , 5 } ] ) ) ,
? assertMatch ( [ _ , _ , " [critical] " , _ , " Test message \n " ] , re : split ( Bin1 , " " , [ { return , list } , { parts , 5 } ] ) ) ,
@ -449,5 +631,62 @@ formatting_test_() ->
}
}
] } .
] } .
config_validation_test_ ( ) - >
[
{ " missing file " ,
? _ assertEqual ( false ,
validate_logfile_proplist ( [ { level , info } , { size , 10 } ] ) )
} ,
{ " bad level " ,
? _ assertEqual ( false ,
validate_logfile_proplist ( [ { file , " test.log " } , { level , blah } , { size , 10 } ] ) )
} ,
{ " bad size " ,
? _ assertEqual ( false ,
validate_logfile_proplist ( [ { file , " test.log " } , { size , infinity } ] ) )
} ,
{ " bad count " ,
? _ assertEqual ( false ,
validate_logfile_proplist ( [ { file , " test.log " } , { count , infinity } ] ) )
} ,
{ " bad date " ,
? _ assertEqual ( false ,
validate_logfile_proplist ( [ { file , " test.log " } , { date , " midnight " } ] ) )
} ,
{ " blank date is ok " ,
? _ assertMatch ( [ _ | _ ] ,
validate_logfile_proplist ( [ { file , " test.log " } , { date , " " } ] ) )
} ,
{ " bad sync_interval " ,
? _ assertEqual ( false ,
validate_logfile_proplist ( [ { file , " test.log " } , { sync_interval , infinity } ] ) )
} ,
{ " bad sync_size " ,
? _ assertEqual ( false ,
validate_logfile_proplist ( [ { file , " test.log " } , { sync_size , infinity } ] ) )
} ,
{ " bad check_interval " ,
? _ assertEqual ( false ,
validate_logfile_proplist ( [ { file , " test.log " } , { check_interval , infinity } ] ) )
} ,
{ " bad sync_on level " ,
? _ assertEqual ( false ,
validate_logfile_proplist ( [ { file , " test.log " } , { sync_on , infinity } ] ) )
} ,
{ " bad formatter module " ,
? _ assertEqual ( false ,
validate_logfile_proplist ( [ { file , " test.log " } , { formatter , " io:format " } ] ) )
} ,
{ " bad formatter config " ,
? _ assertEqual ( false ,
validate_logfile_proplist ( [ { file , " test.log " } , { formatter_config , blah } ] ) )
} ,
{ " unknown option " ,
? _ assertEqual ( false ,
validate_logfile_proplist ( [ { file , " test.log " } , { rhubarb , spicy } ] ) )
}
] .
- endif .
- endif .