2 # statslog - Rearrange and output selected parts of slapd's statslog output.
4 # This work is part of OpenLDAP Software <http://www.openldap.org/>.
6 # Copyright 1998-2011 The OpenLDAP Foundation.
7 # Portions Copyright 2004 Hallvard B. Furuseth.
10 # Redistribution and use in source and binary forms, with or without
11 # modification, are permitted only as authorized by the OpenLDAP
14 # A copy of this license is available in the file LICENSE in the
15 # top-level directory of the distribution or, alternatively, at
16 # <http://www.OpenLDAP.org/license.html>.
19 die join("", @_, <<'EOM');
20 Usage: statslog [options] [logfiles; may be .gz or .bz2 files]
22 Output selected parts of slapd's statslog output (LDAP request/response
23 log to syslog or stderr; loglevel 256), grouping log lines by LDAP
24 connection. Lines with no connection are excluded by default.
27 --brief -b Brief output (omit time, host/process name/ID).
28 --exclude=RE -e RE Exclude connections whose output matches REgexp.
29 --include=RE -i RE Only include connections matching REgexp.
30 --EXCLUDE=RE -E RE Case-sensitive '--exclude'.
31 --INCLUDE=RE -I RE Case-sensitive '--include'.
32 --loose -l Include "loose" lines (lines with no connection).
33 --no-loose -L RE Only exclude the "loose" lines that match RE.
34 --join -j Join the inputs as if they were one big log file.
35 Each file must start where the previous left off.
36 --no-join -J Do not --join. (Can be useful with --sort.)
37 --sort -s Sort input files by age. Implies --join.
38 --trace -t Print file names when read. Implies --no-join.
39 All --exclude/include options are applied. Note: --exclude/include are
40 unreliable without --join/sort for connections spanning several log files.
44 ########################################################################
51 my %conns; # Hash (connection number -> output)
52 my @loose; # Collected output with no connection number
54 # Command line options
55 my($brief, @filters, @conditions, $no_loose);
56 my($join_files, $sort_files, $trace, $getopt_ok);
58 # Handle --include/INCLUDE/exclude/EXCLUDE options
60 my($opt, $regexp) = @_;
61 push(@conditions, sprintf('$lines %s /$filters[%d]/om%s',
62 (lc($opt) eq 'include' ? "=~" : "!~"),
64 ($opt eq lc($opt) ? "i" : "")));
65 push(@filters, $regexp);
68 # Parse options at compile time so some can become constants to optimize away
70 &Getopt::Long::Configure(qw(bundling no_ignore_case));
71 $getopt_ok = GetOptions("brief|b" => \$brief,
72 "include|i=s" => \&filter_opt,
73 "exclude|e=s" => \&filter_opt,
74 "INCLUDE|I=s" => \&filter_opt,
75 "EXCLUDE|E=s" => \&filter_opt,
76 "join|j" => \$join_files,
77 "no-join|J" => sub { $join_files = 0; },
78 "sort|s" => \$sort_files,
79 "loose|l" => sub { $no_loose = ".^"; },
80 "no-loose|L=s" => \$no_loose,
81 "trace|t" => \$trace);
83 usage() unless $getopt_ok;
84 usage("--trace is incompatible with --join.\n") if $trace && $join_files;
86 $join_files = 1 if !defined($join_files) && $sort_files && !$trace;
87 use constant BRIEF => !!$brief;
88 use constant LOOSE => defined($no_loose) && ($no_loose eq ".^" ? 2 : 1);
90 # Build sub out(header, connection number) to output one connection's data
92 ? ' if (@loose) { print "\n", @loose; @loose = (); } '
94 $out_body .= ' print "\n", $_[0], $lines; ';
95 $out_body = " if (" . join("\n && ", @conditions) . ") {\n$out_body\n}"
99 my \$lines = delete(\$conns{\$_[1]});
106 # Read and output log lines from one file
109 my($conn, $line, $act);
112 ? (($conn, $line, $act) = /\bconn=(\d+) (\S+ (\S+).*\n)/)
113 : (($conn, $act) = /\bconn=(\d+) \S+ (\S+)/ )) {
114 $conns{$conn} .= (BRIEF ? $line : $_);
115 out("", $conn) if $act eq 'closed';
116 } elsif (LOOSE && (LOOSE > 1 || !/$no_loose/omi)) {
117 s/^\w{3} [ \d]+:\d\d:\d\d [^:]*: // if BRIEF;
121 final() unless $join_files;
124 # Output log lines for unfinished connections
127 for my $conn (sort keys %conns) {
128 out("UNFINISHED:\n", $conn);
132 if (LOOSE && @loose) { print "\n", @loose; @loose = (); }
140 if ($sort_files && @ARGV > 1) {
141 # Sort files by last modified time; oldest first
143 for my $file (@ARGV) {
146 push(@fileinfo, [$age, $file]);
148 print STDERR "File not found: $file\n";
151 exit(1) unless @fileinfo;
152 @ARGV = map { $_->[1] } sort { $b->[0] <=> $a->[0] } @fileinfo;
155 # Prepare to pipe .gz, .bz2 and .bz files through gunzip or bunzip2
156 my %type2prog = ("gz" => "gunzip", "bz2" => "bunzip2", "bz" => "bunzip2");
158 if (/\.(gz|bz2?)$/) {
160 die "Bad filename: $_\n" if /^[+-]|[^\w\/.,:%=+-]|^$/;
161 $_ = "$type2prog{$type} -c $_ |";
166 for my $file (@ARGV) {
167 print "\n$file:\n" if $trace;