~launchpad-pqm/launchpad/devel

10637.3.15 by Guilherme Salgado
update the shebang lines of a few scripts that still had python2.5 hard-coded
1
#!/usr/bin/python -S
9590.1.135 by Michael Hudson
add files from launchpad-loggerhead tree to launchpad tree
2
#
3
# Copyright 2009, 2010 Canonical Ltd.  This software is licensed under the
4
# GNU Affero General Public License version 3 (see the file LICENSE).
5
6
import _pythonpath
7
8
import logging
9
import os
10
import sys
7675.876.1 by Andrew Bennetts
Log request finish and total request time for loggerhead.
11
import time
12
import traceback
9590.1.135 by Michael Hudson
add files from launchpad-loggerhead tree to launchpad tree
13
14
from paste import httpserver
15
from paste.deploy.config import PrefixMiddleware
16
from paste.httpexceptions import HTTPExceptionHandler
17
from paste.request import construct_url
18
from paste.translogger import TransLogger
7675.876.1 by Andrew Bennetts
Log request finish and total request time for loggerhead.
19
from paste.wsgilib import catch_errors
9590.1.135 by Michael Hudson
add files from launchpad-loggerhead tree to launchpad tree
20
21
from canonical.config import config
22
import lp.codehosting
23
11734.1.1 by Jonathan Lange
Allow codebrowse port and interface to be configured with config file.
24
25
LISTEN_HOST = config.codebrowse.listen_host
26
LISTEN_PORT = config.codebrowse.port
9590.1.135 by Michael Hudson
add files from launchpad-loggerhead tree to launchpad tree
27
THREADPOOL_WORKERS = 10
28
29
30
class NoLockingFileHandler(logging.FileHandler):
31
    """A version of logging.FileHandler that doesn't do it's own locking.
32
33
    We experienced occasional hangs in production where gdb-ery on the server
34
    revealed that we sometimes end up with many threads blocking on the RLock
35
    held by the logging file handler, and log reading finds that an exception
36
    managed to kill a thread in an unsafe window for RLock's.
37
38
    Luckily, there's no real reason for us to take a lock during logging as
39
    each log message translates to one call to .write on a file object, which
40
    translates to one fwrite call, and it seems that this does enough locking
41
    itself for our purposes.
42
43
    So this handler just doesn't lock in log message handling.
44
    """
45
46
    def acquire(self):
47
        pass
48
49
    def release(self):
50
        pass
51
52
53
def setup_logging(home, foreground):
54
    # i hate that stupid logging config format, so just set up logging here.
55
56
    log_folder = config.codebrowse.log_folder
57
    if not log_folder:
58
        log_folder = os.path.join(home, 'logs')
59
    if not os.path.exists(log_folder):
11435.9.1 by Ian Booth
Fix log folder creation
60
        os.makedirs(log_folder)
9590.1.135 by Michael Hudson
add files from launchpad-loggerhead tree to launchpad tree
61
62
    f = logging.Formatter(
63
        '%(levelname)-.3s [%(asctime)s.%(msecs)03d] [%(thread)d] %(name)s: %(message)s',
64
        '%Y%m%d-%H:%M:%S')
65
    debug_log = NoLockingFileHandler(os.path.join(log_folder, 'debug.log'))
66
    debug_log.setLevel(logging.DEBUG)
67
    debug_log.setFormatter(f)
68
    if foreground:
69
        stdout_log = logging.StreamHandler(sys.stdout)
70
        stdout_log.setLevel(logging.DEBUG)
71
        stdout_log.setFormatter(f)
72
    f = logging.Formatter('[%(asctime)s.%(msecs)03d] %(message)s',
73
                          '%Y%m%d-%H:%M:%S')
74
    access_log = NoLockingFileHandler(os.path.join(log_folder, 'access.log'))
75
    access_log.setLevel(logging.INFO)
76
    access_log.setFormatter(f)
77
78
    logging.getLogger('').setLevel(logging.DEBUG)
79
    logging.getLogger('').addHandler(debug_log)
80
    logging.getLogger('wsgi').addHandler(access_log)
81
82
    if foreground:
83
        logging.getLogger('').addHandler(stdout_log)
84
    else:
85
        class S(object):
86
            def write(self, str):
87
                logging.getLogger().error(str.rstrip('\n'))
88
            def flush(self):
89
                pass
90
        sys.stderr = S()
91
92
93
94
foreground = False
95
if len(sys.argv) > 1:
96
    if sys.argv[1] == '-f':
97
        foreground = True
98
99
home = os.path.realpath(os.path.dirname(__file__))
100
pidfile = os.path.join(home, 'loggerhead.pid')
101
102
if not foreground:
103
    sys.stderr.write('\n')
104
    sys.stderr.write('Launching loggerhead into the background.\n')
105
    sys.stderr.write('PID file: %s\n' % (pidfile,))
106
    sys.stderr.write('\n')
107
108
    from loggerhead.daemon import daemonize
109
    daemonize(pidfile, home)
110
111
setup_logging(home, foreground=foreground)
112
113
log = logging.getLogger('loggerhead')
114
log.info('Starting up...')
115
116
log.info('Loading the bzr plugins...')
117
from bzrlib.plugin import load_plugins
118
load_plugins()
119
120
import bzrlib.plugins
121
if getattr(bzrlib.plugins, 'loom', None) is None:
122
    log.error('Loom plugin loading failed.')
123
124
from launchpad_loggerhead.debug import (
125
    change_kill_thread_criteria, threadpool_debug)
11225.1.1 by Andrew Bennetts
Add OOPS logging to codebrowse.
126
from launchpad_loggerhead.app import RootApp, oops_middleware
9590.1.135 by Michael Hudson
add files from launchpad-loggerhead tree to launchpad tree
127
from launchpad_loggerhead.session import SessionHandler
128
129
SESSION_VAR = 'lh.session'
130
131
secret = open(os.path.join(config.root, config.codebrowse.secret_path)).read()
132
133
app = RootApp(SESSION_VAR)
134
app = HTTPExceptionHandler(app)
135
app = SessionHandler(app, SESSION_VAR, secret)
7675.876.1 by Andrew Bennetts
Log request finish and total request time for loggerhead.
136
def log_request_start_and_stop(app):
9590.1.135 by Michael Hudson
add files from launchpad-loggerhead tree to launchpad tree
137
    def wrapped(environ, start_response):
138
        log = logging.getLogger('loggerhead')
7675.876.1 by Andrew Bennetts
Log request finish and total request time for loggerhead.
139
        url = construct_url(environ)
140
        log.info("Starting to process %s", url)
141
        start_time = time.time()
142
        def request_done_ok():
7675.876.2 by Andrew Bennetts
Remove cruft, tweak log messages.
143
            log.info("Processed ok %s [%0.3f seconds]", url, time.time() -
7675.876.1 by Andrew Bennetts
Log request finish and total request time for loggerhead.
144
                    start_time)
145
        def request_done_err(exc_info):
7675.876.2 by Andrew Bennetts
Remove cruft, tweak log messages.
146
            log.info("Processed err %s [%0.3f seconds]: %s", url, time.time() -
7675.876.1 by Andrew Bennetts
Log request finish and total request time for loggerhead.
147
                    start_time, traceback.format_exception_only(*exc_info[:2]))
148
        return catch_errors(app, environ, start_response, request_done_err,
149
                request_done_ok)
9590.1.135 by Michael Hudson
add files from launchpad-loggerhead tree to launchpad tree
150
    return wrapped
7675.876.1 by Andrew Bennetts
Log request finish and total request time for loggerhead.
151
app = log_request_start_and_stop(app)
9590.1.135 by Michael Hudson
add files from launchpad-loggerhead tree to launchpad tree
152
app = PrefixMiddleware(app)
153
app = TransLogger(app)
154
app = threadpool_debug(app)
155
156
def set_scheme(app):
157
    """Set wsgi.url_scheme in the environment correctly.
158
159
    We serve requests that originated from both http and https, and
160
    distinguish between them by adding a header in the https Apache config.
161
    """
162
    def wrapped(environ, start_response):
163
        environ['wsgi.url_scheme'] = environ.pop(
164
            'HTTP_X_FORWARDED_SCHEME', 'http')
165
        return app(environ, start_response)
166
    return wrapped
167
app = set_scheme(app)
168
app = change_kill_thread_criteria(app)
11225.1.1 by Andrew Bennetts
Add OOPS logging to codebrowse.
169
app = oops_middleware(app)
9590.1.135 by Michael Hudson
add files from launchpad-loggerhead tree to launchpad tree
170
171
try:
172
    httpserver.serve(
173
        app, host=LISTEN_HOST, port=LISTEN_PORT,
174
        threadpool_workers=THREADPOOL_WORKERS,
175
        threadpool_options={
176
            # Kill threads after 300 seconds.  This is insanely high, but
177
            # lower enough than the default (1800 seconds!) that evidence
178
            # suggests it will be hit occasionally, and there's very little
179
            # chance of it having negative consequences.
180
            'kill_thread_limit': 300,
181
            # Check for threads that should be killed every 10 requests.  The
182
            # default is every 100, which is easily long enough for things to
183
            # gum up completely in between checks.
184
            'hung_check_period': 10,
185
            })
186
finally:
187
    log.info('Shutdown.')
188
    try:
189
        os.remove(pidfile)
190
    except OSError:
191
        pass