1
#!/usr/bin/python2.5 -S
3
# Copyright 2009, 2010 Canonical Ltd. This software is licensed under the
4
# GNU Affero General Public License version 3 (see the file LICENSE).
12
from paste import httpserver
13
from paste.deploy.config import PrefixMiddleware
14
from paste.httpexceptions import HTTPExceptionHandler
15
from paste.request import construct_url
16
from paste.translogger import TransLogger
18
from canonical.config import config
21
LISTEN_HOST = '0.0.0.0'
23
THREADPOOL_WORKERS = 10
26
class NoLockingFileHandler(logging.FileHandler):
27
"""A version of logging.FileHandler that doesn't do it's own locking.
29
We experienced occasional hangs in production where gdb-ery on the server
30
revealed that we sometimes end up with many threads blocking on the RLock
31
held by the logging file handler, and log reading finds that an exception
32
managed to kill a thread in an unsafe window for RLock's.
34
Luckily, there's no real reason for us to take a lock during logging as
35
each log message translates to one call to .write on a file object, which
36
translates to one fwrite call, and it seems that this does enough locking
37
itself for our purposes.
39
So this handler just doesn't lock in log message handling.
49
def setup_logging(home, foreground):
50
# i hate that stupid logging config format, so just set up logging here.
52
log_folder = config.codebrowse.log_folder
54
log_folder = os.path.join(home, 'logs')
55
if not os.path.exists(log_folder):
58
f = logging.Formatter(
59
'%(levelname)-.3s [%(asctime)s.%(msecs)03d] [%(thread)d] %(name)s: %(message)s',
61
debug_log = NoLockingFileHandler(os.path.join(log_folder, 'debug.log'))
62
debug_log.setLevel(logging.DEBUG)
63
debug_log.setFormatter(f)
65
stdout_log = logging.StreamHandler(sys.stdout)
66
stdout_log.setLevel(logging.DEBUG)
67
stdout_log.setFormatter(f)
68
f = logging.Formatter('[%(asctime)s.%(msecs)03d] %(message)s',
70
access_log = NoLockingFileHandler(os.path.join(log_folder, 'access.log'))
71
access_log.setLevel(logging.INFO)
72
access_log.setFormatter(f)
74
logging.getLogger('').setLevel(logging.DEBUG)
75
logging.getLogger('').addHandler(debug_log)
76
logging.getLogger('wsgi').addHandler(access_log)
79
logging.getLogger('').addHandler(stdout_log)
83
logging.getLogger().error(str.rstrip('\n'))
92
if sys.argv[1] == '-f':
95
home = os.path.realpath(os.path.dirname(__file__))
96
pidfile = os.path.join(home, 'loggerhead.pid')
99
sys.stderr.write('\n')
100
sys.stderr.write('Launching loggerhead into the background.\n')
101
sys.stderr.write('PID file: %s\n' % (pidfile,))
102
sys.stderr.write('\n')
104
from loggerhead.daemon import daemonize
105
daemonize(pidfile, home)
107
setup_logging(home, foreground=foreground)
109
log = logging.getLogger('loggerhead')
110
log.info('Starting up...')
112
log.info('Loading the bzr plugins...')
113
from bzrlib.plugin import load_plugins
116
import bzrlib.plugins
117
if getattr(bzrlib.plugins, 'loom', None) is None:
118
log.error('Loom plugin loading failed.')
120
from launchpad_loggerhead.debug import (
121
change_kill_thread_criteria, threadpool_debug)
122
from launchpad_loggerhead.app import RootApp
123
from launchpad_loggerhead.session import SessionHandler
125
SESSION_VAR = 'lh.session'
127
secret = open(os.path.join(config.root, config.codebrowse.secret_path)).read()
129
app = RootApp(SESSION_VAR)
130
app = HTTPExceptionHandler(app)
131
app = SessionHandler(app, SESSION_VAR, secret)
132
def log_on_request_start(app):
133
def wrapped(environ, start_response):
134
log = logging.getLogger('loggerhead')
135
log.info("Starting to process %s", construct_url(environ))
136
return app(environ, start_response)
138
app = log_on_request_start(app)
139
app = PrefixMiddleware(app)
140
app = TransLogger(app)
141
app = threadpool_debug(app)
144
"""Set wsgi.url_scheme in the environment correctly.
146
We serve requests that originated from both http and https, and
147
distinguish between them by adding a header in the https Apache config.
149
def wrapped(environ, start_response):
150
environ['wsgi.url_scheme'] = environ.pop(
151
'HTTP_X_FORWARDED_SCHEME', 'http')
152
return app(environ, start_response)
154
app = set_scheme(app)
155
app = change_kill_thread_criteria(app)
159
app, host=LISTEN_HOST, port=LISTEN_PORT,
160
threadpool_workers=THREADPOOL_WORKERS,
162
# Kill threads after 300 seconds. This is insanely high, but
163
# lower enough than the default (1800 seconds!) that evidence
164
# suggests it will be hit occasionally, and there's very little
165
# chance of it having negative consequences.
166
'kill_thread_limit': 300,
167
# Check for threads that should be killed every 10 requests. The
168
# default is every 100, which is easily long enough for things to
169
# gum up completely in between checks.
170
'hung_check_period': 10,
173
log.info('Shutdown.')