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 |
|
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
|