##// END OF EJS Templates
feat(configs): allow json log format for gunicorn
super-admin -
r5502:bff5e272 default
parent child Browse files
Show More
@@ -1,520 +1,545 b''
1 """
1 """
2 Gunicorn config extension and hooks. This config file adds some extra settings and memory management.
2 Gunicorn config extension and hooks. This config file adds some extra settings and memory management.
3 Gunicorn configuration should be managed by .ini files entries of RhodeCode or VCSServer
3 Gunicorn configuration should be managed by .ini files entries of RhodeCode or VCSServer
4 """
4 """
5
5
6 import gc
6 import gc
7 import os
7 import os
8 import sys
8 import sys
9 import math
9 import math
10 import time
10 import time
11 import threading
11 import threading
12 import traceback
12 import traceback
13 import random
13 import random
14 import socket
14 import socket
15 import dataclasses
15 import dataclasses
16 import json
16 from gunicorn.glogging import Logger
17 from gunicorn.glogging import Logger
17
18
18
19
19 def get_workers():
20 def get_workers():
20 import multiprocessing
21 import multiprocessing
21 return multiprocessing.cpu_count() * 2 + 1
22 return multiprocessing.cpu_count() * 2 + 1
22
23
23
24
24 bind = "127.0.0.1:10020"
25 bind = "127.0.0.1:10020"
25
26
26
27
27 # Error logging output for gunicorn (-) is stdout
28 # Error logging output for gunicorn (-) is stdout
28 errorlog = '-'
29 errorlog = '-'
29
30
30 # Access logging output for gunicorn (-) is stdout
31 # Access logging output for gunicorn (-) is stdout
31 accesslog = '-'
32 accesslog = '-'
32
33
33
34
34 # SERVER MECHANICS
35 # SERVER MECHANICS
35 # None == system temp dir
36 # None == system temp dir
36 # worker_tmp_dir is recommended to be set to some tmpfs
37 # worker_tmp_dir is recommended to be set to some tmpfs
37 worker_tmp_dir = None
38 worker_tmp_dir = None
38 tmp_upload_dir = None
39 tmp_upload_dir = None
39
40
40 # use re-use port logic
41 # use re-use port logic to let linux internals load-balance the requests better.
41 #reuse_port = True
42 reuse_port = True
42
43
43 # Custom log format
44 # Custom log format
44 #access_log_format = (
45 #access_log_format = (
45 # '%(t)s %(p)s INFO [GNCRN] %(h)-15s rqt:%(L)s %(s)s %(b)-6s "%(m)s:%(U)s %(q)s" usr:%(u)s "%(f)s" "%(a)s"')
46 # '%(t)s %(p)s INFO [GNCRN] %(h)-15s rqt:%(L)s %(s)s %(b)-6s "%(m)s:%(U)s %(q)s" usr:%(u)s "%(f)s" "%(a)s"')
46
47
47 # loki format for easier parsing in grafana
48 # loki format for easier parsing in grafana
48 access_log_format = (
49 loki_access_log_format = (
49 'time="%(t)s" pid=%(p)s level="INFO" type="[GNCRN]" ip="%(h)-15s" rqt="%(L)s" response_code="%(s)s" response_bytes="%(b)-6s" uri="%(m)s:%(U)s %(q)s" user=":%(u)s" user_agent="%(a)s"')
50 'time="%(t)s" pid=%(p)s level="INFO" type="[GNCRN]" ip="%(h)-15s" rqt="%(L)s" response_code="%(s)s" response_bytes="%(b)-6s" uri="%(m)s:%(U)s %(q)s" user=":%(u)s" user_agent="%(a)s"')
50
51
52 # JSON format
53 json_access_log_format = json.dumps({
54 'time': r'%(t)s',
55 'pid': r'%(p)s',
56 'level': 'INFO',
57 'ip': r'%(h)s',
58 'request_time': r'%(L)s',
59 'remote_address': r'%(h)s',
60 'user_name': r'%(u)s',
61 'status': r'%(s)s',
62 'method': r'%(m)s',
63 'url_path': r'%(U)s',
64 'query_string': r'%(q)s',
65 'protocol': r'%(H)s',
66 'response_length': r'%(B)s',
67 'referer': r'%(f)s',
68 'user_agent': r'%(a)s',
69
70 })
71
72 access_log_format = loki_access_log_format
73 if os.environ.get('RC_LOGGING_FORMATTER') == 'json':
74 access_log_format = json_access_log_format
75
51 # self adjust workers based on CPU count, to use maximum of CPU and not overquota the resources
76 # self adjust workers based on CPU count, to use maximum of CPU and not overquota the resources
52 # workers = get_workers()
77 # workers = get_workers()
53
78
54 # Gunicorn access log level
79 # Gunicorn access log level
55 loglevel = 'info'
80 loglevel = 'info'
56
81
57 # Process name visible in a process list
82 # Process name visible in a process list
58 proc_name = 'rhodecode_enterprise'
83 proc_name = 'rhodecode_enterprise'
59
84
60 # Type of worker class, one of `sync`, `gevent` or `gthread`
85 # Type of worker class, one of `sync`, `gevent` or `gthread`
61 # currently `sync` is the only option allowed for vcsserver and for rhodecode all of 3 are allowed
86 # currently `sync` is the only option allowed for vcsserver and for rhodecode all of 3 are allowed
62 # gevent:
87 # gevent:
63 # In this case, the maximum number of concurrent requests is (N workers * X worker_connections)
88 # In this case, the maximum number of concurrent requests is (N workers * X worker_connections)
64 # e.g. workers =3 worker_connections=10 = 3*10, 30 concurrent requests can be handled
89 # e.g. workers =3 worker_connections=10 = 3*10, 30 concurrent requests can be handled
65 # gthread:
90 # gthread:
66 # In this case, the maximum number of concurrent requests is (N workers * X threads)
91 # In this case, the maximum number of concurrent requests is (N workers * X threads)
67 # e.g. workers = 3 threads=3 = 3*3, 9 concurrent requests can be handled
92 # e.g. workers = 3 threads=3 = 3*3, 9 concurrent requests can be handled
68 worker_class = 'gthread'
93 worker_class = 'gthread'
69
94
70 # Sets the number of process workers. More workers means more concurrent connections
95 # Sets the number of process workers. More workers means more concurrent connections
71 # RhodeCode can handle at the same time. Each additional worker also it increases
96 # RhodeCode can handle at the same time. Each additional worker also it increases
72 # memory usage as each has its own set of caches.
97 # memory usage as each has its own set of caches.
73 # The Recommended value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers, but no more
98 # The Recommended value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers, but no more
74 # than 8-10 unless for huge deployments .e.g 700-1000 users.
99 # than 8-10 unless for huge deployments .e.g 700-1000 users.
75 # `instance_id = *` must be set in the [app:main] section below (which is the default)
100 # `instance_id = *` must be set in the [app:main] section below (which is the default)
76 # when using more than 1 worker.
101 # when using more than 1 worker.
77 workers = 2
102 workers = 2
78
103
79 # Threads numbers for worker class gthread
104 # Threads numbers for worker class gthread
80 threads = 1
105 threads = 1
81
106
82 # The maximum number of simultaneous clients. Valid only for gevent
107 # The maximum number of simultaneous clients. Valid only for gevent
83 # In this case, the maximum number of concurrent requests is (N workers * X worker_connections)
108 # In this case, the maximum number of concurrent requests is (N workers * X worker_connections)
84 # e.g workers =3 worker_connections=10 = 3*10, 30 concurrent requests can be handled
109 # e.g workers =3 worker_connections=10 = 3*10, 30 concurrent requests can be handled
85 worker_connections = 10
110 worker_connections = 10
86
111
87 # Max number of requests that worker will handle before being gracefully restarted.
112 # Max number of requests that worker will handle before being gracefully restarted.
88 # Prevents memory leaks, jitter adds variability so not all workers are restarted at once.
113 # Prevents memory leaks, jitter adds variability so not all workers are restarted at once.
89 max_requests = 2000
114 max_requests = 2000
90 max_requests_jitter = int(max_requests * 0.2) # 20% of max_requests
115 max_requests_jitter = int(max_requests * 0.2) # 20% of max_requests
91
116
92 # The maximum number of pending connections.
117 # The maximum number of pending connections.
93 # Exceeding this number results in the client getting an error when attempting to connect.
118 # Exceeding this number results in the client getting an error when attempting to connect.
94 backlog = 64
119 backlog = 64
95
120
96 # The Amount of time a worker can spend with handling a request before it
121 # The Amount of time a worker can spend with handling a request before it
97 # gets killed and restarted. By default, set to 21600 (6hrs)
122 # gets killed and restarted. By default, set to 21600 (6hrs)
98 # Examples: 1800 (30min), 3600 (1hr), 7200 (2hr), 43200 (12h)
123 # Examples: 1800 (30min), 3600 (1hr), 7200 (2hr), 43200 (12h)
99 timeout = 21600
124 timeout = 21600
100
125
101 # The maximum size of HTTP request line in bytes.
126 # The maximum size of HTTP request line in bytes.
102 # 0 for unlimited
127 # 0 for unlimited
103 limit_request_line = 0
128 limit_request_line = 0
104
129
105 # Limit the number of HTTP headers fields in a request.
130 # Limit the number of HTTP headers fields in a request.
106 # By default this value is 100 and can't be larger than 32768.
131 # By default this value is 100 and can't be larger than 32768.
107 limit_request_fields = 32768
132 limit_request_fields = 32768
108
133
109 # Limit the allowed size of an HTTP request header field.
134 # Limit the allowed size of an HTTP request header field.
110 # Value is a positive number or 0.
135 # Value is a positive number or 0.
111 # Setting it to 0 will allow unlimited header field sizes.
136 # Setting it to 0 will allow unlimited header field sizes.
112 limit_request_field_size = 0
137 limit_request_field_size = 0
113
138
114 # Timeout for graceful workers restart.
139 # Timeout for graceful workers restart.
115 # After receiving a restart signal, workers have this much time to finish
140 # After receiving a restart signal, workers have this much time to finish
116 # serving requests. Workers still alive after the timeout (starting from the
141 # serving requests. Workers still alive after the timeout (starting from the
117 # receipt of the restart signal) are force killed.
142 # receipt of the restart signal) are force killed.
118 # Examples: 1800 (30min), 3600 (1hr), 7200 (2hr), 43200 (12h)
143 # Examples: 1800 (30min), 3600 (1hr), 7200 (2hr), 43200 (12h)
119 graceful_timeout = 21600
144 graceful_timeout = 21600
120
145
121 # The number of seconds to wait for requests on a Keep-Alive connection.
146 # The number of seconds to wait for requests on a Keep-Alive connection.
122 # Generally set in the 1-5 seconds range.
147 # Generally set in the 1-5 seconds range.
123 keepalive = 2
148 keepalive = 2
124
149
125 # Maximum memory usage that each worker can use before it will receive a
150 # Maximum memory usage that each worker can use before it will receive a
126 # graceful restart signal 0 = memory monitoring is disabled
151 # graceful restart signal 0 = memory monitoring is disabled
127 # Examples: 268435456 (256MB), 536870912 (512MB)
152 # Examples: 268435456 (256MB), 536870912 (512MB)
128 # 1073741824 (1GB), 2147483648 (2GB), 4294967296 (4GB)
153 # 1073741824 (1GB), 2147483648 (2GB), 4294967296 (4GB)
129 # Dynamic formula 1024 * 1024 * 256 == 256MBs
154 # Dynamic formula 1024 * 1024 * 256 == 256MBs
130 memory_max_usage = 0
155 memory_max_usage = 0
131
156
132 # How often in seconds to check for memory usage for each gunicorn worker
157 # How often in seconds to check for memory usage for each gunicorn worker
133 memory_usage_check_interval = 60
158 memory_usage_check_interval = 60
134
159
135 # Threshold value for which we don't recycle worker if GarbageCollection
160 # Threshold value for which we don't recycle worker if GarbageCollection
136 # frees up enough resources. Before each restart, we try to run GC on worker
161 # frees up enough resources. Before each restart, we try to run GC on worker
137 # in case we get enough free memory after that; restart will not happen.
162 # in case we get enough free memory after that; restart will not happen.
138 memory_usage_recovery_threshold = 0.8
163 memory_usage_recovery_threshold = 0.8
139
164
140
165
141 @dataclasses.dataclass
166 @dataclasses.dataclass
142 class MemoryCheckConfig:
167 class MemoryCheckConfig:
143 max_usage: int
168 max_usage: int
144 check_interval: int
169 check_interval: int
145 recovery_threshold: float
170 recovery_threshold: float
146
171
147
172
148 def _get_process_rss(pid=None):
173 def _get_process_rss(pid=None):
149 try:
174 try:
150 import psutil
175 import psutil
151 if pid:
176 if pid:
152 proc = psutil.Process(pid)
177 proc = psutil.Process(pid)
153 else:
178 else:
154 proc = psutil.Process()
179 proc = psutil.Process()
155 return proc.memory_info().rss
180 return proc.memory_info().rss
156 except Exception:
181 except Exception:
157 return None
182 return None
158
183
159
184
160 def _get_config(ini_path):
185 def _get_config(ini_path):
161 import configparser
186 import configparser
162
187
163 try:
188 try:
164 config = configparser.RawConfigParser()
189 config = configparser.RawConfigParser()
165 config.read(ini_path)
190 config.read(ini_path)
166 return config
191 return config
167 except Exception:
192 except Exception:
168 return None
193 return None
169
194
170
195
171 def get_memory_usage_params(config=None):
196 def get_memory_usage_params(config=None):
172 # memory spec defaults
197 # memory spec defaults
173 _memory_max_usage = memory_max_usage
198 _memory_max_usage = memory_max_usage
174 _memory_usage_check_interval = memory_usage_check_interval
199 _memory_usage_check_interval = memory_usage_check_interval
175 _memory_usage_recovery_threshold = memory_usage_recovery_threshold
200 _memory_usage_recovery_threshold = memory_usage_recovery_threshold
176
201
177 if config:
202 if config:
178 ini_path = os.path.abspath(config)
203 ini_path = os.path.abspath(config)
179 conf = _get_config(ini_path)
204 conf = _get_config(ini_path)
180
205
181 section = 'server:main'
206 section = 'server:main'
182 if conf and conf.has_section(section):
207 if conf and conf.has_section(section):
183
208
184 if conf.has_option(section, 'memory_max_usage'):
209 if conf.has_option(section, 'memory_max_usage'):
185 _memory_max_usage = conf.getint(section, 'memory_max_usage')
210 _memory_max_usage = conf.getint(section, 'memory_max_usage')
186
211
187 if conf.has_option(section, 'memory_usage_check_interval'):
212 if conf.has_option(section, 'memory_usage_check_interval'):
188 _memory_usage_check_interval = conf.getint(section, 'memory_usage_check_interval')
213 _memory_usage_check_interval = conf.getint(section, 'memory_usage_check_interval')
189
214
190 if conf.has_option(section, 'memory_usage_recovery_threshold'):
215 if conf.has_option(section, 'memory_usage_recovery_threshold'):
191 _memory_usage_recovery_threshold = conf.getfloat(section, 'memory_usage_recovery_threshold')
216 _memory_usage_recovery_threshold = conf.getfloat(section, 'memory_usage_recovery_threshold')
192
217
193 _memory_max_usage = int(os.environ.get('RC_GUNICORN_MEMORY_MAX_USAGE', '')
218 _memory_max_usage = int(os.environ.get('RC_GUNICORN_MEMORY_MAX_USAGE', '')
194 or _memory_max_usage)
219 or _memory_max_usage)
195 _memory_usage_check_interval = int(os.environ.get('RC_GUNICORN_MEMORY_USAGE_CHECK_INTERVAL', '')
220 _memory_usage_check_interval = int(os.environ.get('RC_GUNICORN_MEMORY_USAGE_CHECK_INTERVAL', '')
196 or _memory_usage_check_interval)
221 or _memory_usage_check_interval)
197 _memory_usage_recovery_threshold = float(os.environ.get('RC_GUNICORN_MEMORY_USAGE_RECOVERY_THRESHOLD', '')
222 _memory_usage_recovery_threshold = float(os.environ.get('RC_GUNICORN_MEMORY_USAGE_RECOVERY_THRESHOLD', '')
198 or _memory_usage_recovery_threshold)
223 or _memory_usage_recovery_threshold)
199
224
200 return MemoryCheckConfig(_memory_max_usage, _memory_usage_check_interval, _memory_usage_recovery_threshold)
225 return MemoryCheckConfig(_memory_max_usage, _memory_usage_check_interval, _memory_usage_recovery_threshold)
201
226
202
227
203 def _time_with_offset(check_interval):
228 def _time_with_offset(check_interval):
204 return time.time() - random.randint(0, check_interval/2.0)
229 return time.time() - random.randint(0, check_interval/2.0)
205
230
206
231
207 def pre_fork(server, worker):
232 def pre_fork(server, worker):
208 pass
233 pass
209
234
210
235
211 def post_fork(server, worker):
236 def post_fork(server, worker):
212
237
213 memory_conf = get_memory_usage_params()
238 memory_conf = get_memory_usage_params()
214 _memory_max_usage = memory_conf.max_usage
239 _memory_max_usage = memory_conf.max_usage
215 _memory_usage_check_interval = memory_conf.check_interval
240 _memory_usage_check_interval = memory_conf.check_interval
216 _memory_usage_recovery_threshold = memory_conf.recovery_threshold
241 _memory_usage_recovery_threshold = memory_conf.recovery_threshold
217
242
218 worker._memory_max_usage = int(os.environ.get('RC_GUNICORN_MEMORY_MAX_USAGE', '')
243 worker._memory_max_usage = int(os.environ.get('RC_GUNICORN_MEMORY_MAX_USAGE', '')
219 or _memory_max_usage)
244 or _memory_max_usage)
220 worker._memory_usage_check_interval = int(os.environ.get('RC_GUNICORN_MEMORY_USAGE_CHECK_INTERVAL', '')
245 worker._memory_usage_check_interval = int(os.environ.get('RC_GUNICORN_MEMORY_USAGE_CHECK_INTERVAL', '')
221 or _memory_usage_check_interval)
246 or _memory_usage_check_interval)
222 worker._memory_usage_recovery_threshold = float(os.environ.get('RC_GUNICORN_MEMORY_USAGE_RECOVERY_THRESHOLD', '')
247 worker._memory_usage_recovery_threshold = float(os.environ.get('RC_GUNICORN_MEMORY_USAGE_RECOVERY_THRESHOLD', '')
223 or _memory_usage_recovery_threshold)
248 or _memory_usage_recovery_threshold)
224
249
225 # register memory last check time, with some random offset so we don't recycle all
250 # register memory last check time, with some random offset so we don't recycle all
226 # at once
251 # at once
227 worker._last_memory_check_time = _time_with_offset(_memory_usage_check_interval)
252 worker._last_memory_check_time = _time_with_offset(_memory_usage_check_interval)
228
253
229 if _memory_max_usage:
254 if _memory_max_usage:
230 server.log.info("pid=[%-10s] WORKER spawned with max memory set at %s", worker.pid,
255 server.log.info("pid=[%-10s] WORKER spawned with max memory set at %s", worker.pid,
231 _format_data_size(_memory_max_usage))
256 _format_data_size(_memory_max_usage))
232 else:
257 else:
233 server.log.info("pid=[%-10s] WORKER spawned", worker.pid)
258 server.log.info("pid=[%-10s] WORKER spawned", worker.pid)
234
259
235
260
236 def pre_exec(server):
261 def pre_exec(server):
237 server.log.info("Forked child, re-executing.")
262 server.log.info("Forked child, re-executing.")
238
263
239
264
240 def on_starting(server):
265 def on_starting(server):
241 server_lbl = '{} {}'.format(server.proc_name, server.address)
266 server_lbl = '{} {}'.format(server.proc_name, server.address)
242 server.log.info("Server %s is starting.", server_lbl)
267 server.log.info("Server %s is starting.", server_lbl)
243 server.log.info('Config:')
268 server.log.info('Config:')
244 server.log.info(f"\n{server.cfg}")
269 server.log.info(f"\n{server.cfg}")
245 server.log.info(get_memory_usage_params())
270 server.log.info(get_memory_usage_params())
246
271
247
272
248 def when_ready(server):
273 def when_ready(server):
249 server.log.info("Server %s is ready. Spawning workers", server)
274 server.log.info("Server %s is ready. Spawning workers", server)
250
275
251
276
252 def on_reload(server):
277 def on_reload(server):
253 pass
278 pass
254
279
255
280
256 def _format_data_size(size, unit="B", precision=1, binary=True):
281 def _format_data_size(size, unit="B", precision=1, binary=True):
257 """Format a number using SI units (kilo, mega, etc.).
282 """Format a number using SI units (kilo, mega, etc.).
258
283
259 ``size``: The number as a float or int.
284 ``size``: The number as a float or int.
260
285
261 ``unit``: The unit name in plural form. Examples: "bytes", "B".
286 ``unit``: The unit name in plural form. Examples: "bytes", "B".
262
287
263 ``precision``: How many digits to the right of the decimal point. Default
288 ``precision``: How many digits to the right of the decimal point. Default
264 is 1. 0 suppresses the decimal point.
289 is 1. 0 suppresses the decimal point.
265
290
266 ``binary``: If false, use base-10 decimal prefixes (kilo = K = 1000).
291 ``binary``: If false, use base-10 decimal prefixes (kilo = K = 1000).
267 If true, use base-2 binary prefixes (kibi = Ki = 1024).
292 If true, use base-2 binary prefixes (kibi = Ki = 1024).
268
293
269 ``full_name``: If false (default), use the prefix abbreviation ("k" or
294 ``full_name``: If false (default), use the prefix abbreviation ("k" or
270 "Ki"). If true, use the full prefix ("kilo" or "kibi"). If false,
295 "Ki"). If true, use the full prefix ("kilo" or "kibi"). If false,
271 use abbreviation ("k" or "Ki").
296 use abbreviation ("k" or "Ki").
272
297
273 """
298 """
274
299
275 if not binary:
300 if not binary:
276 base = 1000
301 base = 1000
277 multiples = ('', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
302 multiples = ('', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
278 else:
303 else:
279 base = 1024
304 base = 1024
280 multiples = ('', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi')
305 multiples = ('', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi')
281
306
282 sign = ""
307 sign = ""
283 if size > 0:
308 if size > 0:
284 m = int(math.log(size, base))
309 m = int(math.log(size, base))
285 elif size < 0:
310 elif size < 0:
286 sign = "-"
311 sign = "-"
287 size = -size
312 size = -size
288 m = int(math.log(size, base))
313 m = int(math.log(size, base))
289 else:
314 else:
290 m = 0
315 m = 0
291 if m > 8:
316 if m > 8:
292 m = 8
317 m = 8
293
318
294 if m == 0:
319 if m == 0:
295 precision = '%.0f'
320 precision = '%.0f'
296 else:
321 else:
297 precision = '%%.%df' % precision
322 precision = '%%.%df' % precision
298
323
299 size = precision % (size / math.pow(base, m))
324 size = precision % (size / math.pow(base, m))
300
325
301 return '%s%s %s%s' % (sign, size.strip(), multiples[m], unit)
326 return '%s%s %s%s' % (sign, size.strip(), multiples[m], unit)
302
327
303
328
304 def _check_memory_usage(worker):
329 def _check_memory_usage(worker):
305 _memory_max_usage = worker._memory_max_usage
330 _memory_max_usage = worker._memory_max_usage
306 if not _memory_max_usage:
331 if not _memory_max_usage:
307 return
332 return
308
333
309 _memory_usage_check_interval = worker._memory_usage_check_interval
334 _memory_usage_check_interval = worker._memory_usage_check_interval
310 _memory_usage_recovery_threshold = memory_max_usage * worker._memory_usage_recovery_threshold
335 _memory_usage_recovery_threshold = memory_max_usage * worker._memory_usage_recovery_threshold
311
336
312 elapsed = time.time() - worker._last_memory_check_time
337 elapsed = time.time() - worker._last_memory_check_time
313 if elapsed > _memory_usage_check_interval:
338 if elapsed > _memory_usage_check_interval:
314 mem_usage = _get_process_rss()
339 mem_usage = _get_process_rss()
315 if mem_usage and mem_usage > _memory_max_usage:
340 if mem_usage and mem_usage > _memory_max_usage:
316 worker.log.info(
341 worker.log.info(
317 "memory usage %s > %s, forcing gc",
342 "memory usage %s > %s, forcing gc",
318 _format_data_size(mem_usage), _format_data_size(_memory_max_usage))
343 _format_data_size(mem_usage), _format_data_size(_memory_max_usage))
319 # Try to clean it up by forcing a full collection.
344 # Try to clean it up by forcing a full collection.
320 gc.collect()
345 gc.collect()
321 mem_usage = _get_process_rss()
346 mem_usage = _get_process_rss()
322 if mem_usage > _memory_usage_recovery_threshold:
347 if mem_usage > _memory_usage_recovery_threshold:
323 # Didn't clean up enough, we'll have to terminate.
348 # Didn't clean up enough, we'll have to terminate.
324 worker.log.warning(
349 worker.log.warning(
325 "memory usage %s > %s after gc, quitting",
350 "memory usage %s > %s after gc, quitting",
326 _format_data_size(mem_usage), _format_data_size(_memory_max_usage))
351 _format_data_size(mem_usage), _format_data_size(_memory_max_usage))
327 # This will cause worker to auto-restart itself
352 # This will cause worker to auto-restart itself
328 worker.alive = False
353 worker.alive = False
329 worker._last_memory_check_time = time.time()
354 worker._last_memory_check_time = time.time()
330
355
331
356
332 def worker_int(worker):
357 def worker_int(worker):
333 worker.log.info("pid=[%-10s] worker received INT or QUIT signal", worker.pid)
358 worker.log.info("pid=[%-10s] worker received INT or QUIT signal", worker.pid)
334
359
335 # get traceback info, when a worker crashes
360 # get traceback info, when a worker crashes
336 def get_thread_id(t_id):
361 def get_thread_id(t_id):
337 id2name = dict([(th.ident, th.name) for th in threading.enumerate()])
362 id2name = dict([(th.ident, th.name) for th in threading.enumerate()])
338 return id2name.get(t_id, "unknown_thread_id")
363 return id2name.get(t_id, "unknown_thread_id")
339
364
340 code = []
365 code = []
341 for thread_id, stack in sys._current_frames().items(): # noqa
366 for thread_id, stack in sys._current_frames().items(): # noqa
342 code.append(
367 code.append(
343 "\n# Thread: %s(%d)" % (get_thread_id(thread_id), thread_id))
368 "\n# Thread: %s(%d)" % (get_thread_id(thread_id), thread_id))
344 for fname, lineno, name, line in traceback.extract_stack(stack):
369 for fname, lineno, name, line in traceback.extract_stack(stack):
345 code.append('File: "%s", line %d, in %s' % (fname, lineno, name))
370 code.append('File: "%s", line %d, in %s' % (fname, lineno, name))
346 if line:
371 if line:
347 code.append(" %s" % (line.strip()))
372 code.append(" %s" % (line.strip()))
348 worker.log.debug("\n".join(code))
373 worker.log.debug("\n".join(code))
349
374
350
375
351 def worker_abort(worker):
376 def worker_abort(worker):
352 worker.log.info("pid=[%-10s] worker received SIGABRT signal", worker.pid)
377 worker.log.info("pid=[%-10s] worker received SIGABRT signal", worker.pid)
353
378
354
379
355 def worker_exit(server, worker):
380 def worker_exit(server, worker):
356 worker.log.info("pid=[%-10s] worker exit", worker.pid)
381 worker.log.info("pid=[%-10s] worker exit", worker.pid)
357
382
358
383
359 def child_exit(server, worker):
384 def child_exit(server, worker):
360 worker.log.info("pid=[%-10s] worker child exit", worker.pid)
385 worker.log.info("pid=[%-10s] worker child exit", worker.pid)
361
386
362
387
363 def pre_request(worker, req):
388 def pre_request(worker, req):
364 worker.start_time = time.time()
389 worker.start_time = time.time()
365 worker.log.debug(
390 worker.log.debug(
366 "GNCRN PRE WORKER [cnt:%s]: %s %s", worker.nr, req.method, req.path)
391 "GNCRN PRE WORKER [cnt:%s]: %s %s", worker.nr, req.method, req.path)
367
392
368
393
369 def post_request(worker, req, environ, resp):
394 def post_request(worker, req, environ, resp):
370 total_time = time.time() - worker.start_time
395 total_time = time.time() - worker.start_time
371 # Gunicorn sometimes has problems with reading the status_code
396 # Gunicorn sometimes has problems with reading the status_code
372 status_code = getattr(resp, 'status_code', '')
397 status_code = getattr(resp, 'status_code', '')
373 worker.log.debug(
398 worker.log.debug(
374 "GNCRN POST WORKER [cnt:%s]: %s %s resp: %s, Load Time: %.4fs",
399 "GNCRN POST WORKER [cnt:%s]: %s %s resp: %s, Load Time: %.4fs",
375 worker.nr, req.method, req.path, status_code, total_time)
400 worker.nr, req.method, req.path, status_code, total_time)
376 _check_memory_usage(worker)
401 _check_memory_usage(worker)
377
402
378
403
379 def _filter_proxy(ip):
404 def _filter_proxy(ip):
380 """
405 """
381 Passed in IP addresses in HEADERS can be in a special format of multiple
406 Passed in IP addresses in HEADERS can be in a special format of multiple
382 ips. Those comma separated IPs are passed from various proxies in the
407 ips. Those comma separated IPs are passed from various proxies in the
383 chain of request processing. The left-most being the original client.
408 chain of request processing. The left-most being the original client.
384 We only care about the first IP which came from the org. client.
409 We only care about the first IP which came from the org. client.
385
410
386 :param ip: ip string from headers
411 :param ip: ip string from headers
387 """
412 """
388 if ',' in ip:
413 if ',' in ip:
389 _ips = ip.split(',')
414 _ips = ip.split(',')
390 _first_ip = _ips[0].strip()
415 _first_ip = _ips[0].strip()
391 return _first_ip
416 return _first_ip
392 return ip
417 return ip
393
418
394
419
395 def _filter_port(ip):
420 def _filter_port(ip):
396 """
421 """
397 Removes a port from ip, there are 4 main cases to handle here.
422 Removes a port from ip, there are 4 main cases to handle here.
398 - ipv4 eg. 127.0.0.1
423 - ipv4 eg. 127.0.0.1
399 - ipv6 eg. ::1
424 - ipv6 eg. ::1
400 - ipv4+port eg. 127.0.0.1:8080
425 - ipv4+port eg. 127.0.0.1:8080
401 - ipv6+port eg. [::1]:8080
426 - ipv6+port eg. [::1]:8080
402
427
403 :param ip:
428 :param ip:
404 """
429 """
405 def is_ipv6(ip_addr):
430 def is_ipv6(ip_addr):
406 if hasattr(socket, 'inet_pton'):
431 if hasattr(socket, 'inet_pton'):
407 try:
432 try:
408 socket.inet_pton(socket.AF_INET6, ip_addr)
433 socket.inet_pton(socket.AF_INET6, ip_addr)
409 except socket.error:
434 except socket.error:
410 return False
435 return False
411 else:
436 else:
412 return False
437 return False
413 return True
438 return True
414
439
415 if ':' not in ip: # must be ipv4 pure ip
440 if ':' not in ip: # must be ipv4 pure ip
416 return ip
441 return ip
417
442
418 if '[' in ip and ']' in ip: # ipv6 with port
443 if '[' in ip and ']' in ip: # ipv6 with port
419 return ip.split(']')[0][1:].lower()
444 return ip.split(']')[0][1:].lower()
420
445
421 # must be ipv6 or ipv4 with port
446 # must be ipv6 or ipv4 with port
422 if is_ipv6(ip):
447 if is_ipv6(ip):
423 return ip
448 return ip
424 else:
449 else:
425 ip, _port = ip.split(':')[:2] # means ipv4+port
450 ip, _port = ip.split(':')[:2] # means ipv4+port
426 return ip
451 return ip
427
452
428
453
429 def get_ip_addr(environ):
454 def get_ip_addr(environ):
430 proxy_key = 'HTTP_X_REAL_IP'
455 proxy_key = 'HTTP_X_REAL_IP'
431 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
456 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
432 def_key = 'REMOTE_ADDR'
457 def_key = 'REMOTE_ADDR'
433
458
434 def _filters(x):
459 def _filters(x):
435 return _filter_port(_filter_proxy(x))
460 return _filter_port(_filter_proxy(x))
436
461
437 ip = environ.get(proxy_key)
462 ip = environ.get(proxy_key)
438 if ip:
463 if ip:
439 return _filters(ip)
464 return _filters(ip)
440
465
441 ip = environ.get(proxy_key2)
466 ip = environ.get(proxy_key2)
442 if ip:
467 if ip:
443 return _filters(ip)
468 return _filters(ip)
444
469
445 ip = environ.get(def_key, '0.0.0.0')
470 ip = environ.get(def_key, '0.0.0.0')
446 return _filters(ip)
471 return _filters(ip)
447
472
448
473
449 class RhodeCodeLogger(Logger):
474 class RhodeCodeLogger(Logger):
450 """
475 """
451 Custom Logger that allows some customization that gunicorn doesn't allow
476 Custom Logger that allows some customization that gunicorn doesn't allow
452 """
477 """
453
478
454 datefmt = r"%Y-%m-%d %H:%M:%S"
479 datefmt = r"%Y-%m-%d %H:%M:%S"
455
480
456 def __init__(self, cfg):
481 def __init__(self, cfg):
457 Logger.__init__(self, cfg)
482 Logger.__init__(self, cfg)
458
483
459 def now(self):
484 def now(self):
460 """ return date in RhodeCode Log format """
485 """ return date in RhodeCode Log format """
461 now = time.time()
486 now = time.time()
462 msecs = int((now - int(now)) * 1000)
487 msecs = int((now - int(now)) * 1000)
463 return time.strftime(self.datefmt, time.localtime(now)) + '.{0:03d}'.format(msecs)
488 return time.strftime(self.datefmt, time.localtime(now)) + '.{0:03d}'.format(msecs)
464
489
465 def atoms(self, resp, req, environ, request_time):
490 def atoms(self, resp, req, environ, request_time):
466 """ Gets atoms for log formatting.
491 """ Gets atoms for log formatting.
467 """
492 """
468 status = resp.status
493 status = resp.status
469 if isinstance(status, str):
494 if isinstance(status, str):
470 status = status.split(None, 1)[0]
495 status = status.split(None, 1)[0]
471 atoms = {
496 atoms = {
472 'h': get_ip_addr(environ),
497 'h': get_ip_addr(environ),
473 'l': '-',
498 'l': '-',
474 'u': self._get_user(environ) or '-',
499 'u': self._get_user(environ) or '-',
475 't': self.now(),
500 't': self.now(),
476 'r': "%s %s %s" % (environ['REQUEST_METHOD'],
501 'r': "%s %s %s" % (environ['REQUEST_METHOD'],
477 environ['RAW_URI'],
502 environ['RAW_URI'],
478 environ["SERVER_PROTOCOL"]),
503 environ["SERVER_PROTOCOL"]),
479 's': status,
504 's': status,
480 'm': environ.get('REQUEST_METHOD'),
505 'm': environ.get('REQUEST_METHOD'),
481 'U': environ.get('PATH_INFO'),
506 'U': environ.get('PATH_INFO'),
482 'q': environ.get('QUERY_STRING'),
507 'q': environ.get('QUERY_STRING'),
483 'H': environ.get('SERVER_PROTOCOL'),
508 'H': environ.get('SERVER_PROTOCOL'),
484 'b': getattr(resp, 'sent', None) is not None and str(resp.sent) or '-',
509 'b': getattr(resp, 'sent', None) is not None and str(resp.sent) or '-',
485 'B': getattr(resp, 'sent', None),
510 'B': getattr(resp, 'sent', None),
486 'f': environ.get('HTTP_REFERER', '-'),
511 'f': environ.get('HTTP_REFERER', '-'),
487 'a': environ.get('HTTP_USER_AGENT', '-'),
512 'a': environ.get('HTTP_USER_AGENT', '-'),
488 'T': request_time.seconds,
513 'T': request_time.seconds,
489 'D': (request_time.seconds * 1000000) + request_time.microseconds,
514 'D': (request_time.seconds * 1000000) + request_time.microseconds,
490 'M': (request_time.seconds * 1000) + int(request_time.microseconds/1000),
515 'M': (request_time.seconds * 1000) + int(request_time.microseconds/1000),
491 'L': "%d.%06d" % (request_time.seconds, request_time.microseconds),
516 'L': "%d.%06d" % (request_time.seconds, request_time.microseconds),
492 'p': "<%s>" % os.getpid()
517 'p': "<%s>" % os.getpid()
493 }
518 }
494
519
495 # add request headers
520 # add request headers
496 if hasattr(req, 'headers'):
521 if hasattr(req, 'headers'):
497 req_headers = req.headers
522 req_headers = req.headers
498 else:
523 else:
499 req_headers = req
524 req_headers = req
500
525
501 if hasattr(req_headers, "items"):
526 if hasattr(req_headers, "items"):
502 req_headers = req_headers.items()
527 req_headers = req_headers.items()
503
528
504 atoms.update({"{%s}i" % k.lower(): v for k, v in req_headers})
529 atoms.update({"{%s}i" % k.lower(): v for k, v in req_headers})
505
530
506 resp_headers = resp.headers
531 resp_headers = resp.headers
507 if hasattr(resp_headers, "items"):
532 if hasattr(resp_headers, "items"):
508 resp_headers = resp_headers.items()
533 resp_headers = resp_headers.items()
509
534
510 # add response headers
535 # add response headers
511 atoms.update({"{%s}o" % k.lower(): v for k, v in resp_headers})
536 atoms.update({"{%s}o" % k.lower(): v for k, v in resp_headers})
512
537
513 # add environ variables
538 # add environ variables
514 environ_variables = environ.items()
539 environ_variables = environ.items()
515 atoms.update({"{%s}e" % k.lower(): v for k, v in environ_variables})
540 atoms.update({"{%s}e" % k.lower(): v for k, v in environ_variables})
516
541
517 return atoms
542 return atoms
518
543
519
544
520 logger_class = RhodeCodeLogger
545 logger_class = RhodeCodeLogger
General Comments 0
You need to be logged in to leave comments. Login now