Changeset 3227


Ignore:
Timestamp:
08/27/10 01:46:46 (21 months ago)
Author:
nuxwin
Message:
  • [ENGINE]
    • Fixed (again) #2187: apache logger should be able to use the improvements of logio in http accounting
    • Rewrited ispcp-apache-logger
File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/engine/ispcp-apache-logger

    r3201 r3227  
    22 
    33# ispCP ω (OMEGA) a Virtual Hosting Control Panel 
    4 # Copyright (C) 2001-2006 by moleSoftware GmbH - http://www.molesoftware.com 
    54# Copyright (C) 2006-2010 by isp Control Panel - http://ispcp.net 
    65# 
    76# Version: $Id$ 
     7# Author: Laurent Declercq <laurent.declercq@ispcp.net> 
    88# 
    99# The contents of this file are subject to the Mozilla Public License 
     
    1717# under the License. 
    1818# 
    19 # The Original Code is "VHCS - Virtual Hosting Control System". 
    20 # 
    21 # The Initial Developer of the Original Code is moleSoftware GmbH. 
    22 # Portions created by Initial Developer are Copyright (C) 2001-2006 
    23 # by moleSoftware GmbH. All Rights Reserved. 
    24 # Portions created by the ispCP Team are Copyright (C) 2006-2010 by 
     19# The Original Code is "ispCP ω (OMEGA) a Virtual Hosting Control Panel". 
     20# 
     21# The Initial Developer of the Original Code is ispCP Team. 
     22# Portions created by Initial Developer are Copyright (C) 2006-2010 by 
    2523# isp Control Panel. All Rights Reserved. 
    2624# 
     
    3028# 
    3129 
    32 use strict; 
    33 use warnings; 
    34 use sigtrap qw(handler exitall HUP USR1 TERM INT PIPE); 
    35 use Getopt::Std; 
    36  
    37 use FindBin; 
    38 use lib "$FindBin::Bin/"; 
    39 require 'ispcp_common_code.pl'; 
    40  
    41 our %OPTS; 
    42 getopts( 'e', \%OPTS ); 
    43  
    44 my $MAXFILES = "33"; 
    45 if ( $main::cfg{'APACHE_MAX_OPEN_LOG'} ) { 
    46         $MAXFILES = $main::cfg{'APACHE_MAX_OPEN_LOG'}; 
    47 } 
    48  
    49 my $MAXLOGFILESIZE = 10485760; # 10Mb 
    50 if ( $main::cfg{'APACHE_MAX_LOG_FILE_SIZE'} ) { 
    51         $MAXLOGFILESIZE = $main::cfg{'APACHE_MAX_LOG_FILE_SIZE'}; 
    52 } 
    53  
    54 my %timestamps = (); 
    55 my %combined_logs = (); 
    56 my %traff_logs = (); 
    57 my %access_logs = (); 
    58 my %error_logs = (); 
    59  
    60 sub checkFileExists; 
    61 sub DefaultLogs(); 
    62 sub ErrorLogs(); 
    63 sub checkFileSize; 
    64  
    65 ## Creating log files if possible ## 
    66  
    67 if ( !-d $main::cfg{'LOG_DIR'} ) { 
    68         print STDERR "[ispcp-apache-logger] target directory ".$main::cfg{'LOG_DIR'}." does not exist - logging on standard streams.\n"; 
     30################################################################################ 
     31## Script description:                                                        ## 
     32##  ispcp-apache-logger - ispCP apache logfiles handling                      ## 
     33##                                                                            ## 
     34## I. CustomLog handler:                                                      ## 
     35##                                                                            ## 
     36## When called without arguments, this script receives log lines through a    ## 
     37## pipe and outputs their information into three separate files, depending on ## 
     38## the origin virtual host:                                                   ## 
     39##                                                                            ## 
     40##  - /var/log/apache2/domain.tld-combined.log                                ## 
     41##  - /var/log/apache2/domain.tld-traf log                                    ## 
     42##  - /var/log/apache2/users/domain.tld-access.log                            ## 
     43##                                                                            ## 
     44## The program assumes that the first field of each log line is the virtual   ## 
     45## host identity (See the %v logging directive from Apache mod_log_config).   ## 
     46##                                                                            ## 
     47## The log format is currently recognized as follows:                         ## 
     48##                                                                            ## 
     49##  "%v %... %I %O"                                                           ## 
     50##                                                                            ## 
     51## Where %... represents any other logging directives of your choice.         ## 
     52## Additionally, %I and %O correspond to apache's mod_logio directives for    ## 
     53## Input and Output bytes, used to generate the content of the traffic log    ## 
     54## files.                                                                     ## 
     55##                                                                            ## 
     56## II. ErrorLog handler:                                                      ## 
     57##                                                                            ## 
     58## When called with the 'e' argument, this script takes error log lines       ## 
     59## pipe and outputs their information into a separate file depending on the   ## 
     60## originating virtual host :                                                 ## 
     61##                                                                            ## 
     62##  - /var/log/apache2/users/domain.tld-error.log                             ## 
     63##                                                                            ## 
     64## Due to the fixed log format of error log lines, the script uses a          ## 
     65## workaround to retrieve the virtual host identity, by looking for any       ## 
     66## virtual host information inside the line itself. If the script is unable   ## 
     67## to identify the virtual host with this method, errors are written to       ## 
     68## the fallback log file, named "default-error.log".                          ## 
     69##                                                                            ## 
     70## See http://httpd.apache.org/docs/current/logs.html#piped for more info.    ## 
     71################################################################################ 
     72 
     73# Only for dev 
     74#use strict; 
     75#no strict 'refs'; 
     76#use warnings; 
     77 
     78use IO::Handle; 
     79use POSIX qw/strftime/; 
     80use FileCache maxopen => 55; 
     81use Getopt::Long; 
     82use sigtrap 'handler' => \&signalHandler, 'normal-signals'; 
     83 
     84my %opt = ( 
     85        loggingType => 'access', # Default logging type 
     86        logDir => '/var/log/ispcp'  # Script event log directory 
     87); 
     88 
     89Getopt::Long::Configure(qw(no_ignore_case)); 
     90GetOptions(\%opt, 'loggingType|t=s', 'logDir|l=s'); 
     91 
     92%main::cfg = (); 
     93 
     94################################################################################ 
     95#                                 Subroutines                                  # 
     96################################################################################ 
     97 
     98 
     99################################################################################ 
     100# 
     101# Load configuration from ispCP configuration file 
     102# 
     103# @return int 0 on success, negative int otherwise 
     104# 
     105sub getConf { 
     106 
     107        my $file; 
     108 
     109        if(-e '/usr/local/etc/ispcp/ispcp.conf'){ 
     110                $file = '/usr/local/etc/ispcp/ispcp.conf'; 
     111        } elsif(-e '/etc/ispcp/ispcp.conf') { 
     112                $file = '/etc/ispcp/ispcp.conf'; 
     113        } else { 
     114                return -1; 
     115        } 
     116 
     117        return -1 if ! open F, '<', $file; 
     118 
     119        %main::cfg = join('', <F>) =~ /^\s*(\w+)\s*=\s*(.*)$/gm; 
     120        close F; 
     121 
     122        %main::cfg ? 0 : 1; 
     123} 
     124 
     125################################################################################ 
     126# Create/update all custom log files 
     127# 
     128# This subroutine is the handler that is responsible to grab and parse all error 
     129# log lines from apache and create the ispCP errors log files from them. 
     130# 
     131# @return void 
     132# 
     133sub ErrorLogHandler { 
     134 
     135        eventLog( 
     136                'notice', 'ispCP Apache Logger started ErrorLog Handler -- ' . 
     137                        'resuming normal operations' 
     138        ); 
     139 
     140        while (<STDIN>) { 
     141                my $vhost = 'default'; 
     142 
     143                # Trying to retrieve the virtual host identity 
     144                $vhost = $1 if / 
     145                        (?:$main::cfg{'APACHE_WWW_DIR'}|$main::cfg{'PHP_STARTER_DIR'}) 
     146                        \/([\w\d.-]+) 
     147                /x; 
     148 
     149                # Error from ispCP Frontend ? 
     150                $vhost = $main::cfg{'BASE_SERVER_VHOST'} if ($vhost eq 'master'); 
     151 
     152                # Write the error log line in the default|domain.tld-error.log 
     153                my $fh; 
     154 
     155                if($vhost eq 'default') { 
     156                        $fh = cacheout '>>', "$main::cfg{'APACHE_LOG_DIR'}/$vhost-error.log"; 
     157                } else { 
     158                        $fh = cacheout '>>', 
     159                                "$main::cfg{'APACHE_USERS_LOG_DIR'}/$vhost-error.log"; 
     160                } 
     161 
     162                $fh->autoflush(1); 
     163                print $fh $_; 
     164        } 
     165 
     166        # Occurs when apache breaks the communication for the reason exposed here: 
     167        # https://issues.apache.org/bugzilla/show_bug.cgi?id=49800 
     168        signalHandler('eol'); 
     169} 
     170 
     171################################################################################ 
     172# Create/update all custom log files 
     173# 
     174# This subroutine is the handler that is responsible to grab and parse all 
     175# custom log lines from apache and create the ispCP custom logfiles from them. 
     176# 
     177# @return void 
     178# 
     179sub CustomLoghandler { 
     180 
     181        eventLog( 
     182                'notice', 'ispCP Apache Logger started CustomLog Handler -- ' . 
     183                        'resuming normal operations' 
     184        ); 
     185 
     186        while (<STDIN>) { 
     187                my ($vhost, $inBytes, $outBytes) = /^(\S+|\s) .* (\d+) (\d+)$/; 
     188 
     189                # Normalize the virtual host name to all lowercase. If it's blank, the 
     190                # request was handled by the default server, so supply a default name. 
     191                # This shouldn't happen, but caution rocks. 
     192                $vhost = lc ($vhost) || 'default'; 
     193 
     194                # If the vhost contains a '/' or '\', it is illegal so just use the 
     195                # default log to avoid any security issues due if it is interpreted as a 
     196                # directory separator. 
     197                $vhost = 'default' if $vhost =~ m@[/\\]@; 
     198 
     199                # Back the log line to the NCSA extended/combined log format by stripped 
     200                # the %v %I and %O variables. 
     201                # Note: That allows to not define a custom log format in Awstats 
     202                # configuration template. 
     203                s/^\S*\s+(.*)\s\d+\s\d+$/$1/; 
     204 
     205                # Write the log line in the domain.tld-combined.log file 
     206                my $fh = cacheout '>>', 
     207                        "$main::cfg{'APACHE_LOG_DIR'}/$vhost-combined.log"; 
     208                $fh->autoflush(1); 
     209                print $fh $_; 
     210 
     211                # Write the traffic value in the default|domain.tld-traf.log file 
     212                $fh = cacheout '>>', "$main::cfg{'APACHE_LOG_DIR'}/$vhost-traf.log"; 
     213                $fh->autoflush(1); 
     214                print $fh $inBytes + $outBytes, "\n"; 
     215 
     216                # Write the log line in the default|domain.tld-access.log file 
     217                $fh = cacheout '>>', 
     218                        "$main::cfg{'APACHE_USERS_LOG_DIR'}/$vhost-access.log"; 
     219                $fh->autoflush(1); 
     220                print $fh $_; 
     221        } 
     222} 
     223 
     224################################################################################ 
     225# 
     226# Convenience subroutines to log events from this script 
     227# 
     228sub eventLog { 
     229 
     230        ($priority, $message) = @_; 
     231 
     232        my $date = strftime "%a %b %e %H:%M:%S %Y", localtime; 
     233 
     234        if(-d $opt{'logDir'}) { 
     235                if($priority ne 'debug' && $priority ne 'info' && $priority ne 'notice') { 
     236                        *STDERR = cacheout '>>', 
     237                                "$opt{'logDir'}/ispcp-apache-logger.stderr"; 
     238                        STDERR->autoflush(1); 
     239 
     240                        print STDERR "[$date] [$priority] $message\n"; 
     241                } elsif($priority eq 'debug' && !$main::cfg{'DEBUG'}) { 
     242                        return 0; 
     243                } else { 
     244                        *STDOUT = cacheout '>>', 
     245                                "$opt{'logDir'}/ispcp-apache-logger.stdout"; 
     246                        STDOUT->autoflush(1); 
     247 
     248                        print STDOUT "[$date] [$priority] $message\n"; 
     249                } 
     250        } else { # Logging on STDERR 
     251                STDERR->autoflush(1); 
     252 
     253                print STDERR "[$date] [$priority] $message\n"; 
     254        } 
     255 
     256        0; 
     257} 
     258 
     259################################################################################ 
     260# 
     261# This subroutine is the handler that is responsible to handle some signals that 
     262# can be received from Apache. This handler ensure that all filehandles are 
     263# correctly closed before shutting down. 
     264# 
     265# This method is also called by the errorLog handler in condition exposed here: 
     266# https://issues.apache.org/bugzilla/show_bug.cgi?id=49800 
     267# 
     268sub signalHandler() { 
     269 
     270        my $signal =  shift; 
     271        my $loggingType = ($opt{'loggingType'} eq 'access') ? 'Access' : 'Error'; 
     272 
     273        eventLog('debug', "Ending $loggingType log processing..."); 
     274 
     275        if($signal eq 'eol') { 
     276                eventLog('notice', "No more lines received, shutting down (pid $$)"); 
     277        } else { 
     278                eventLog('notice', "Caught SIG$signal, shutting down (pid $$)"); 
     279        } 
     280 
     281        # Close all filehandles 
     282        cacheout_close(); 
     283 
     284        exit; 
     285} 
     286 
     287################################################################################ 
     288#                              Main program                                    # 
     289################################################################################ 
     290 
     291eventLog('notice', "Starting ispCP Apache logger (pid $$)"); 
     292 
     293# Get ispCP configuration file 
     294if(getConf() != 0) { 
     295        eventLog( 
     296                'err', 
     297                "Unable to load ispCP configuration from the ispcp.conf file!" 
     298        ); 
     299 
     300} elsif (!-d $main::cfg{'APACHE_LOG_DIR'}) { 
     301        eventLog( 
     302                'err', 
     303                "Apache $main::cfg{'APACHE_LOG_DIR'} directory does not exist!" 
     304        ); 
     305 
     306} elsif (!-d $main::cfg{'APACHE_USERS_LOG_DIR'}) { 
     307        eventLog( 
     308                'err', 
     309                "Apache $main::cfg{'APACHE_USERS_LOG_DIR'} directory does not exist!" 
     310        ); 
     311 
     312} elsif ($opt{'loggingType'} eq 'access') { 
     313        # Stating Apache access log processing 
     314        CustomLoghandler(); 
    69315} else { 
    70         checkFileSize("$main::cfg{'LOG_DIR'}/ispcp-apache-logger.stderr"); 
    71         checkFileSize("$main::cfg{'LOG_DIR'}/ispcp-apache-logger.stdout"); 
    72  
    73         my $res = open (STDERR, ">>", "$main::cfg{'LOG_DIR'}/ispcp-apache-logger.stderr"); 
    74         if (!defined($res)) { 
    75                 print STDERR "[ispcp-apache-logger] Can't redirect STDERR\n"; 
    76         } else { 
    77                 STDERR->autoflush(1); 
    78         } 
    79         $res = open (STDOUT, ">>", "$main::cfg{'LOG_DIR'}/ispcp-apache-logger.stdout"); 
    80         if (!defined($res)) { 
    81                 print STDERR "[ispcp-apache-logger] Can't redirect STDOUT\n"; 
    82         } else { 
    83                 STDOUT->autoflush(1); 
    84         } 
    85 } 
    86  
    87 if ( !-d $main::cfg{'APACHE_LOG_DIR'}) { 
    88         print STDERR "[ispcp-apache-logger] target directory ".$main::cfg{'APACHE_LOG_DIR'}." does not exist!!!\n"; 
    89 } elsif ( !-d $main::cfg{'APACHE_USERS_LOG_DIR'}) { 
    90         print STDERR "[ispcp-apache-logger] target directory ".$main::cfg{'APACHE_USERS_LOG_DIR'}." does not exist!!!.\n"; 
    91 } elsif ( $OPTS{'e'} ) { 
    92         ErrorLogs(); 
    93 } else { 
    94         DefaultLogs(); 
    95 } 
    96  
    97 while ( my $log_line = <STDIN> ){} 
    98  
    99 sub ErrorLogs(){ 
    100         if (defined($main::engine_debug)){ 
    101                 print STDOUT "[ispcp-apache-logger] Starting error log proccessing...\n"; 
    102         } 
    103         while ( my $log_line = <STDIN> ) { 
    104                 my $vhost = 'default'; 
    105  
    106                 if($log_line =~ m/($main::cfg{'APACHE_WWW_DIR'})/){ 
    107                         ($vhost) = $log_line =~ m/$main::cfg{'APACHE_WWW_DIR'}\/([a-zA-Z0-9_\-\.]*)/; 
    108                 } else { 
    109                         if($log_line =~ m/($main::cfg{'PHP_STARTER_DIR'})/){ 
    110                                 ($vhost) = $log_line =~ m/$main::cfg{'PHP_STARTER_DIR'}\/([a-zA-Z0-9_\-\.]*)/; 
    111                         } 
    112                 } 
    113  
    114                 if ( $vhost eq 'master' ){ 
    115                         $vhost='default'; 
    116                 } 
    117  
    118                 my $force_open=0; 
    119                 if ( !$error_logs{$vhost} ){ 
    120                         $force_open=1; 
    121                         if ( (keys(%timestamps)+1) > $MAXFILES ) { 
    122                                 my ( $key, $value ) = sort { $timestamps{$a} <=> $timestamps{$b} } ( keys(%timestamps) ); 
    123                                 close $error_logs{$key}; 
    124                                 delete $error_logs{$key}; 
    125                                 delete $timestamps{$key}; 
    126                         } 
    127                 } 
    128  
    129                 checkFileExists($vhost, \%error_logs, $main::cfg{'APACHE_USERS_LOG_DIR'},"-error.log",$force_open); 
    130                 print { $error_logs{$vhost} } $log_line; 
    131         } 
    132 } 
    133  
    134 sub DefaultLogs(){ 
    135         if (defined($main::engine_debug)){ 
    136                 print STDOUT "[ispcp-apache-logger] Starting default log proccessing...\n"; 
    137         } 
    138  
    139         while (my $log_line = <STDIN>) { 
    140                 my ($vhost, $size, $in, $out) =  
    141                         $log_line =~ m/^(\S+) (\d+|-) (\d+|-) (\d+|-)$/s; 
    142  
    143                 if(!defined($vhost) || !defined($size)){ 
    144                         print STDERR "[ispcp-apache-logger] Trouble line:\n\t$log_line\n"; 
    145                 } 
    146  
    147                 $vhost = lc($vhost) || "default"; 
    148                 if ( $vhost =~ m#[/\\]# ) { $vhost = "default" } 
    149                 $vhost =~ /(.*)/o; 
    150                 $vhost = $1; 
    151                 $vhost = 'default' unless $vhost; 
    152  
    153                 my $force_open = 0; 
    154                 if (!$combined_logs{$vhost} || !$traff_logs{$vhost} || !$access_logs{$vhost}){ 
    155                         $force_open = 1; 
    156                         if ((keys(%timestamps)+1) > $MAXFILES) { 
    157                                 my ($key, $value) = sort { $timestamps{$a} <=> $timestamps{$b} } (keys(%timestamps)); 
    158                                 if(-e "$main::cfg{'APACHE_LOG_DIR'}/$key-combined.log" && defined($combined_logs{$key})){ 
    159                                         close $combined_logs{$key}; 
    160                                 } 
    161                                 if(-e "$main::cfg{'APACHE_LOG_DIR'}/$key-traf.log" && defined($traff_logs{$key})){ 
    162                                         close $traff_logs{$key}; 
    163                                 } 
    164                                 if(-e "$main::cfg{'APACHE_USERS_LOG_DIR'}/$key-access.log" && defined($access_logs{$key})){ 
    165                                         close $access_logs{$key}; 
    166                                 } 
    167                                 delete $combined_logs{$key}; 
    168                                 delete $traff_logs{$key}; 
    169                                 delete $access_logs{$key}; 
    170                                 delete $timestamps{$key}; 
    171                         } 
    172                 } 
    173  
    174                 checkFileExists($vhost, \%combined_logs, $main::cfg{'APACHE_LOG_DIR'},"-combined.log",$force_open); 
    175                 checkFileExists($vhost, \%traff_logs, $main::cfg{'APACHE_LOG_DIR'},"-traf.log",$force_open); 
    176                 checkFileExists($vhost, \%access_logs, $main::cfg{'APACHE_USERS_LOG_DIR'},"-access.log",$force_open); 
    177  
    178                 print { $combined_logs{$vhost} } $log_line; 
    179                 print { $access_logs{$vhost} } $log_line; 
    180                 if ($size ne '-' && $size != 0){ 
    181                         print { $traff_logs{$vhost} } "$size\n"; 
    182                 } 
    183         } 
    184 } 
    185  
    186 exit; 
    187  
    188 sub checkFileSize(){ 
    189         my ($file)=@_; 
    190         if( -e $file && ((my $filesize = -s $file) > $MAXLOGFILESIZE)) { 
    191                 unlink($file); 
    192         } 
    193 } 
    194  
    195 sub checkFileExists(){ 
    196         my ($local_vhost, $hash, $path, $postpend,$force)=@_; 
    197         if (!(-e "$path/$local_vhost$postpend") || $force eq '1'){ 
    198                 my $res = open ($hash->{$local_vhost}, ">>", "$path/$local_vhost$postpend"); 
    199                 if (!defined($res)) { 
    200                         print STDERR "[ispcp-apache-logger] Can't open $path/$local_vhost$postpend\n"; 
    201                 } else { 
    202                         $hash->{$local_vhost}->autoflush(1); 
    203                         $timestamps{$local_vhost}=time(); 
    204                 } 
    205         } 
    206 } 
    207  
    208 sub exitall { 
    209         if ( $OPTS{'e'} ) { 
    210                 foreach my $key ( keys %error_logs ) { 
    211                         close $key; 
    212                 } 
    213                 if (defined($main::engine_debug)){ 
    214                         print STDOUT "[ispcp-apache-logger] Ending error log proccessing...\n"; 
    215                 } 
    216         } else { 
    217                 foreach my $key ( keys %combined_logs ) { 
    218                         close $key; 
    219                 } 
    220                 %combined_logs = (); 
    221                 foreach my $key ( keys %traff_logs ) { 
    222                         close $key; 
    223                 } 
    224                 %traff_logs = (); 
    225                 foreach my $key ( keys %access_logs ) { 
    226                         close $key; 
    227                 } 
    228                 %access_logs = (); 
    229                 if (defined($main::engine_debug)){ 
    230                         print STDOUT "[ispcp-apache-logger] Ending default log proccessing...\n"; 
    231                 } 
    232         } 
    233 } 
     316        # Stating Apache error log processing 
     317        ErrorLogHandler(); 
     318} 
     319 
     320# This is the rescue part of this script that is run when an error occurs. 
     321# This allows to not kill the current process and prevents Apache to trying to 
     322# re-spawn the process again and again. Instead, the script will acts as 
     323# /dev/null.  It's really better to act like this to avoid too much processor 
     324# resources consumption. But the side effect of this is that the administrator 
     325# will not be able to see the problem unless if he reads the log file. 
     326 
     327eventLog( 
     328        'notice', "Acts as /dev/null to avoid too many re-spawn attempts by " . 
     329                "Apache (pid $$)" 
     330); 
     331 
     332while(<STDIN>) {} 
     333 
     334__END__ 
Note: See TracChangeset for help on using the changeset viewer.