Trog-Log-Syslog.t 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. use strict;
  2. use warnings;
  3. use Test::More;
  4. use Test::NoWarnings;
  5. use Test::Fatal qw{exception};
  6. use File::Temp qw{tempdir};
  7. use IO::Socket::UNIX;
  8. use Socket qw{SOCK_DGRAM};
  9. use Log::Dispatch;
  10. use FindBin;
  11. use lib "$FindBin::Bin/../lib";
  12. require_ok('Trog::Log::Syslog') or BAIL_OUT("Can't find SUT");
  13. # A datagram socket in a temporary directory stands in for /dev/log, which is
  14. # a datagram socket too.
  15. my $dir = tempdir( CLEANUP => 1 );
  16. my $path = "$dir/log";
  17. my $sink = IO::Socket::UNIX->new( Type => SOCK_DGRAM, Local => $path ) or die "Cannot listen on $path: $!";
  18. sub received {
  19. my $got = '';
  20. $sink->recv( $got, 65536 );
  21. return $got;
  22. }
  23. # What tPSGI passes to every logger that its configuration names.
  24. my $output = Trog::Log::Syslog->new( min_level => 'info', log_dir => $dir, socket => $path );
  25. my $log = Log::Dispatch->new();
  26. $log->add($output);
  27. subtest 'a line arrives as the syslog of this machine reads it' => sub {
  28. $log->info("2026-09-29T12:00:00Z [INFO]: RequestId x From 192.0.2.1 |nobody| Failed login for user\n");
  29. my $got = received();
  30. # daemon is facility 3 and info is severity 6, so the priority is 3*8+6.
  31. like( $got, qr/^<30>/, 'as daemon.info' );
  32. like( $got, qr/\btcms\[$$\]: /, 'tagged with the program name and the pid' );
  33. like( $got, qr/: 2026-09-29T12:00:00Z \[INFO\]: RequestId x From 192\.0\.2\.1 \|nobody\| Failed login for user$/, 'with the line that the file gets, and no newline' );
  34. };
  35. subtest 'each level keeps its severity' => sub {
  36. my %severity = ( info => 6, notice => 5, warning => 4, error => 3, critical => 2, alert => 1, emergency => 0 );
  37. foreach my $level ( sort keys %severity ) {
  38. $log->log( level => $level, message => "at $level" );
  39. my $priority = 3 * 8 + $severity{$level};
  40. like( received(), qr/^<$priority>.*: at $level$/, "$level is severity $severity{$level}" );
  41. }
  42. };
  43. subtest 'nothing below the level goes out' => sub {
  44. $log->debug('too quiet');
  45. $log->info('loud enough');
  46. like( received(), qr/loud enough$/, 'the debug line was not sent' );
  47. };
  48. subtest 'a syslog that went away does not take the application with it' => sub {
  49. close($sink);
  50. unlink($path);
  51. # The file output still has the line, and a request that fails because
  52. # its log line did not arrive is worse than a line that did not arrive.
  53. is( exception { $log->warning('into the void') }, undef, 'logging to a missing socket does not die' );
  54. };
  55. subtest 'log_init adds the loggers that tpsgi.ini names' => sub {
  56. require Trog::Log;
  57. # A stand-in, so that this test does not write to the syslog of the
  58. # machine that runs it.
  59. {
  60. package Test::Capture;
  61. use parent -norequire, qw{Log::Dispatch::Output};
  62. our ( @lines, %given );
  63. sub new {
  64. my ( $class, %p ) = @_;
  65. %given = %p;
  66. my $self = bless( {}, $class );
  67. $self->_basic_init( min_level => $p{min_level} );
  68. return $self;
  69. }
  70. sub log_message { my ( $self, %p ) = @_; push( @lines, $p{message} ); return }
  71. }
  72. local $INC{'Test/Capture.pm'} = __FILE__;
  73. Trog::Log::log_init( "$dir/tcms.log", 'info', ['Test::Capture'] );
  74. Trog::Log::INFO('through every output');
  75. no warnings 'once';
  76. is( $Test::Capture::given{log_dir}, $dir, 'handed the log directory, as tPSGI hands it' );
  77. like( $Test::Capture::lines[-1], qr/through every output/, 'and given the lines' );
  78. like( exception { Trog::Log::log_init( "$dir/tcms.log", 'info', ['../../etc/passwd'] ) }, qr/not a module name/, 'a logger that is not a module name is refused' );
  79. };
  80. Test::NoWarnings::had_no_warnings();
  81. done_testing();