update-stats: Output logging.conf on lighttpd failure.
[matthijs/servers/drsnuggles.git] / usr / local / bin / update-stats
1 #!/usr/bin/python
2 # This script takes care of two things:
3 #  * Generate lighttpd configuration that puts access logs for each subdomain
4 #    into a separate file.
5 #  * Generate awstats configuration files to parse each of these.
6 #  * Run awstats to process all current logfiles or
7 #  * When --after-logrotate is given, run awstats to process the just rotated
8 #    logfiles.
9 # For the last part, it is assumed that logrotate is configured with dateext,
10 # without olddir and, until http://bugs.gentoo.org/106651 is fixed, with
11 # delaycompress.
12
13 import os, sys, datetime, subprocess
14
15 root_dir = '/data/www'
16 htdocs_dir = 'htdocs'
17 logs_dir = 'logs'
18 lighttpd_conf_file = '/etc/lighttpd/logging.conf'
19 # The directory with awstats configuration files
20 awstats_dir = '/etc/awstats'
21 # The template for each awstats configuration file. %s is replaced with the
22 # full domain name the configuration is for
23 awstats_config_file = 'awstats.%s.conf'
24 # Let each awstats config file include this file
25 awstats_common_file = os.path.join(awstats_dir, 'common.conf')
26 # Filename for the log files
27 log_file = 'access.log'
28 # Directory for domains we didn't find
29 other_dir = 'other'
30 awstats = '/usr/lib/cgi-bin/awstats.pl'
31 # Use sudo to run awstats as this user
32 awstats_user = 'www-data'
33 # The dateformat option as used by logrotate. This is the default.
34 dateformat = '-%Y%m%d'
35 # Lighttpd restart command
36 reload_lighttpd = 'invoke-rc.d lighttpd reload'
37
38 header = """
39 # This config file was autogenerated by the %s script. Do not change it
40 # directly, since it will be periodically regenerated.
41
42 """ % sys.argv[0]
43
44 lighttpd_conf = header
45 domains = {}
46
47 for d in os.listdir(root_dir):
48   domain_htdocs_dir = os.path.join(root_dir, d, htdocs_dir)
49   # Require a dot in the domain name to filter out stuff like "template" or
50   # "php5-libs" and require the htdocs directory to exist.
51   if not '.' in d or not os.path.isdir(domain_htdocs_dir):
52     continue
53
54   print "%s" % d
55
56   # Make a dictionary of subdomains, containing a list of all aliases.
57   # Iterate all subdomains by looking into the htdocs directory.
58   subdomains = {}
59   def add_subdomain(sub, alias=None):
60       if (not sub in subdomains): subdomains[sub] = []
61       if alias: subdomains[sub].append(alias)
62
63   for dir in os.listdir(domain_htdocs_dir):
64     subdomain_htdocs_dir = os.path.join(domain_htdocs_dir, dir)
65     # Skip non-directories
66     if not os.path.isdir(subdomain_htdocs_dir):
67       continue
68
69     # If the htdocs dir is a link, resolve it (only once!)
70     if os.path.islink(subdomain_htdocs_dir):
71       # Resolve the link to a full path
72       target = os.readlink(subdomain_htdocs_dir)
73       target = os.path.join(domain_htdocs_dir, target)
74       # Only resolve links that point within the same domain
75       if os.path.dirname(target) == domain_htdocs_dir:
76         target = os.path.basename(target)
77         print "\t\%s -> %s" % (dir, target)
78
79         add_subdomain(target, dir)
80         continue
81     # If we get here, there was no resolvable link
82     add_subdomain(dir, dir)
83
84   domains[d] = subdomains
85
86   # Generate the lighttpd config file part for this domain
87   other_logfile = os.path.join(root_dir, d, logs_dir, other_dir, log_file)
88   lighttpd_conf += '$HTTP["host"] =~ ".%s$" {\n' % d
89   lighttpd_conf += '\t# Fallback logfile, in case none if the below conditionals match.\n'
90   lighttpd_conf += '\t# This can happen when a domain was added, but the %s script\n' % sys.argv[0]
91   lighttpd_conf += '\t# has not run yet\n'
92   lighttpd_conf += '\taccesslog.filename = "%s"\n' % other_logfile
93
94   # Make sure the directory exists
95   if not os.path.isdir(os.path.dirname(other_logfile)):
96     os.makedirs(os.path.dirname(other_logfile))
97
98   for (s, aliases) in subdomains.items():
99     print "\t%s" % s
100
101     full_domain = "%s.%s" % (s, d)
102     subdomain_logfile = os.path.join(root_dir, d, logs_dir, s, log_file)
103
104     # Generate the lighttpd config file part for this subdomain
105     print "\t\tGenerating lighttpd configuration"
106     if aliases != [s]:
107       # Don't use a regex if we don't need to. I think this should slightly
108       # speed up lighttpd.
109       aliases_regex = '|'.join(aliases)
110       lighttpd_conf += '\t$HTTP["host"] =~ "^(%s).%s$" {\n' % (aliases_regex, d)
111     else:
112       lighttpd_conf += '\t$HTTP["host"] == "%s.%s" {\n' % (s, d)
113     lighttpd_conf += '\t\taccesslog.filename = "%s"\n' % subdomain_logfile
114     lighttpd_conf += '\t}\n'
115
116     # Only generate awstats configuration for real paths, not symlinks
117     awstats_conf = header
118     awstats_conf += 'LogFile="%s"\n' % subdomain_logfile
119     awstats_conf += 'SiteDomain="%s.%s"\n' % (s, d)
120     awstats_conf += 'HostAliases="%s"\n' % ' '.join(["%s.%s" % (s, d) for s in aliases])
121     awstats_conf += 'Include "%s"\n' % awstats_common_file
122
123     # Write out the awstats config file
124     subdomain_awstats_file = os.path.join(awstats_dir, awstats_config_file % full_domain)
125     print "\t\tWriting %s" % subdomain_awstats_file
126     f = open(subdomain_awstats_file , 'w')
127     f.write(awstats_conf)
128
129     # Make sure the directory exists
130     if not os.path.isdir(os.path.dirname(subdomain_logfile)):
131       os.makedirs(os.path.dirname(subdomain_logfile))
132
133   lighttpd_conf += '}\n'
134
135 # Write out the lighttpd configuration. Check if it has changed first, to
136 # prevent useless lighttpd reloads.
137 f = open(lighttpd_conf_file, 'r+')
138 if lighttpd_conf != f.read():
139   print "Writing %s" % lighttpd_conf_file
140   f.seek(0)
141   f.truncate()
142   f.write(lighttpd_conf)
143
144   # Reload lighttpd configuration
145   print "Reloading lighttpd: %s" % reload_lighttpd
146   ret = subprocess.call(reload_lighttpd, shell=True)
147
148   if ret != 0:
149     print >> sys.stderr, "Reloading lighttpd failed. Logging.conf was:"
150     print >> sys.stderr, lighttpd_conf
151
152 f.close()
153
154 # Now, run awstats to parse log files.
155
156 if len(sys.argv) > 1 and sys.argv[1] == '--after-logrotate':
157   # Logs have just been rotated, so update "todays" log. We make a guess at
158   # logrotate's date extension (which shouldn't be a guess, unless logrotate's
159   # dateformat was modified).
160   dateext = datetime.date.today().strftime(dateformat)
161 else:
162   dateext = ''
163
164 for (d, subdomains) in domains.items():
165   for (s, aliases) in subdomains.items():
166     subdomain_logfile = os.path.join(root_dir, d, logs_dir, s, log_file + dateext)
167
168     # Call awstats. We explicitly pass in a LogFile, in case --after-logrotate
169     # is given. The config parameter points to the middle part of the
170     # configuration file name, awstats adds the root dir and awstats.%s.conf
171     # part. We check if the file exists, since rotation might not have been
172     # happened (when the file was empty, for example)
173     if os.path.exists(subdomain_logfile):
174       subprocess.call([ 'sudo'
175                       , '-u', awstats_user
176                       , awstats
177                       , '-config=%s.%s' % (s, d)
178                       , '-update'
179                       , '-LogFile=%s' % subdomain_logfile
180                       ])
181     
182 # vim: set sw=2 sts=2 expandtab autoindent: