~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to scripts/start-loggerhead.py

add files from launchpad-loggerhead tree to launchpad tree

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python2.5 -S
 
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
 
11
 
 
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
 
17
 
 
18
from canonical.config import config
 
19
import lp.codehosting
 
20
 
 
21
LISTEN_HOST = '0.0.0.0'
 
22
LISTEN_PORT = 8080
 
23
THREADPOOL_WORKERS = 10
 
24
 
 
25
 
 
26
class NoLockingFileHandler(logging.FileHandler):
 
27
    """A version of logging.FileHandler that doesn't do it's own locking.
 
28
 
 
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.
 
33
 
 
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.
 
38
 
 
39
    So this handler just doesn't lock in log message handling.
 
40
    """
 
41
 
 
42
    def acquire(self):
 
43
        pass
 
44
 
 
45
    def release(self):
 
46
        pass
 
47
 
 
48
 
 
49
def setup_logging(home, foreground):
 
50
    # i hate that stupid logging config format, so just set up logging here.
 
51
 
 
52
    log_folder = config.codebrowse.log_folder
 
53
    if not log_folder:
 
54
        log_folder = os.path.join(home, 'logs')
 
55
    if not os.path.exists(log_folder):
 
56
        os.mkdir(log_folder)
 
57
 
 
58
    f = logging.Formatter(
 
59
        '%(levelname)-.3s [%(asctime)s.%(msecs)03d] [%(thread)d] %(name)s: %(message)s',
 
60
        '%Y%m%d-%H:%M:%S')
 
61
    debug_log = NoLockingFileHandler(os.path.join(log_folder, 'debug.log'))
 
62
    debug_log.setLevel(logging.DEBUG)
 
63
    debug_log.setFormatter(f)
 
64
    if foreground:
 
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',
 
69
                          '%Y%m%d-%H:%M:%S')
 
70
    access_log = NoLockingFileHandler(os.path.join(log_folder, 'access.log'))
 
71
    access_log.setLevel(logging.INFO)
 
72
    access_log.setFormatter(f)
 
73
 
 
74
    logging.getLogger('').setLevel(logging.DEBUG)
 
75
    logging.getLogger('').addHandler(debug_log)
 
76
    logging.getLogger('wsgi').addHandler(access_log)
 
77
 
 
78
    if foreground:
 
79
        logging.getLogger('').addHandler(stdout_log)
 
80
    else:
 
81
        class S(object):
 
82
            def write(self, str):
 
83
                logging.getLogger().error(str.rstrip('\n'))
 
84
            def flush(self):
 
85
                pass
 
86
        sys.stderr = S()
 
87
 
 
88
 
 
89
 
 
90
foreground = False
 
91
if len(sys.argv) > 1:
 
92
    if sys.argv[1] == '-f':
 
93
        foreground = True
 
94
 
 
95
home = os.path.realpath(os.path.dirname(__file__))
 
96
pidfile = os.path.join(home, 'loggerhead.pid')
 
97
 
 
98
if not foreground:
 
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')
 
103
 
 
104
    from loggerhead.daemon import daemonize
 
105
    daemonize(pidfile, home)
 
106
 
 
107
setup_logging(home, foreground=foreground)
 
108
 
 
109
log = logging.getLogger('loggerhead')
 
110
log.info('Starting up...')
 
111
 
 
112
log.info('Loading the bzr plugins...')
 
113
from bzrlib.plugin import load_plugins
 
114
load_plugins()
 
115
 
 
116
import bzrlib.plugins
 
117
if getattr(bzrlib.plugins, 'loom', None) is None:
 
118
    log.error('Loom plugin loading failed.')
 
119
 
 
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
 
124
 
 
125
SESSION_VAR = 'lh.session'
 
126
 
 
127
secret = open(os.path.join(config.root, config.codebrowse.secret_path)).read()
 
128
 
 
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)
 
137
    return wrapped
 
138
app = log_on_request_start(app)
 
139
app = PrefixMiddleware(app)
 
140
app = TransLogger(app)
 
141
app = threadpool_debug(app)
 
142
 
 
143
def set_scheme(app):
 
144
    """Set wsgi.url_scheme in the environment correctly.
 
145
 
 
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.
 
148
    """
 
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)
 
153
    return wrapped
 
154
app = set_scheme(app)
 
155
app = change_kill_thread_criteria(app)
 
156
 
 
157
try:
 
158
    httpserver.serve(
 
159
        app, host=LISTEN_HOST, port=LISTEN_PORT,
 
160
        threadpool_workers=THREADPOOL_WORKERS,
 
161
        threadpool_options={
 
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,
 
171
            })
 
172
finally:
 
173
    log.info('Shutdown.')
 
174
    try:
 
175
        os.remove(pidfile)
 
176
    except OSError:
 
177
        pass