Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

glogging.py 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. # -*- coding: utf-8 -
  2. #
  3. # This file is part of gunicorn released under the MIT license.
  4. # See the NOTICE for more information.
  5. import base64
  6. import binascii
  7. import time
  8. import logging
  9. logging.Logger.manager.emittedNoHandlerWarning = 1
  10. from logging.config import fileConfig
  11. import os
  12. import socket
  13. import sys
  14. import threading
  15. import traceback
  16. from gunicorn import util
  17. from gunicorn.six import PY3, string_types
  18. # syslog facility codes
  19. SYSLOG_FACILITIES = {
  20. "auth": 4,
  21. "authpriv": 10,
  22. "cron": 9,
  23. "daemon": 3,
  24. "ftp": 11,
  25. "kern": 0,
  26. "lpr": 6,
  27. "mail": 2,
  28. "news": 7,
  29. "security": 4, # DEPRECATED
  30. "syslog": 5,
  31. "user": 1,
  32. "uucp": 8,
  33. "local0": 16,
  34. "local1": 17,
  35. "local2": 18,
  36. "local3": 19,
  37. "local4": 20,
  38. "local5": 21,
  39. "local6": 22,
  40. "local7": 23
  41. }
  42. CONFIG_DEFAULTS = dict(
  43. version=1,
  44. disable_existing_loggers=False,
  45. loggers={
  46. "root": {"level": "INFO", "handlers": ["console"]},
  47. "gunicorn.error": {
  48. "level": "INFO",
  49. "handlers": ["error_console"],
  50. "propagate": True,
  51. "qualname": "gunicorn.error"
  52. },
  53. "gunicorn.access": {
  54. "level": "INFO",
  55. "handlers": ["console"],
  56. "propagate": True,
  57. "qualname": "gunicorn.access"
  58. }
  59. },
  60. handlers={
  61. "console": {
  62. "class": "logging.StreamHandler",
  63. "formatter": "generic",
  64. "stream": "sys.stdout"
  65. },
  66. "error_console": {
  67. "class": "logging.StreamHandler",
  68. "formatter": "generic",
  69. "stream": "sys.stderr"
  70. },
  71. },
  72. formatters={
  73. "generic": {
  74. "format": "%(asctime)s [%(process)d] [%(levelname)s] %(message)s",
  75. "datefmt": "[%Y-%m-%d %H:%M:%S %z]",
  76. "class": "logging.Formatter"
  77. }
  78. }
  79. )
  80. def loggers():
  81. """ get list of all loggers """
  82. root = logging.root
  83. existing = root.manager.loggerDict.keys()
  84. return [logging.getLogger(name) for name in existing]
  85. class SafeAtoms(dict):
  86. def __init__(self, atoms):
  87. dict.__init__(self)
  88. for key, value in atoms.items():
  89. if isinstance(value, string_types):
  90. self[key] = value.replace('"', '\\"')
  91. else:
  92. self[key] = value
  93. def __getitem__(self, k):
  94. if k.startswith("{"):
  95. kl = k.lower()
  96. if kl in self:
  97. return super(SafeAtoms, self).__getitem__(kl)
  98. else:
  99. return "-"
  100. if k in self:
  101. return super(SafeAtoms, self).__getitem__(k)
  102. else:
  103. return '-'
  104. def parse_syslog_address(addr):
  105. if addr.startswith("unix://"):
  106. sock_type = socket.SOCK_STREAM
  107. # are we using a different socket type?
  108. parts = addr.split("#", 1)
  109. if len(parts) == 2:
  110. addr = parts[0]
  111. if parts[1] == "dgram":
  112. sock_type = socket.SOCK_DGRAM
  113. return (sock_type, addr.split("unix://")[1])
  114. if addr.startswith("udp://"):
  115. addr = addr.split("udp://")[1]
  116. socktype = socket.SOCK_DGRAM
  117. elif addr.startswith("tcp://"):
  118. addr = addr.split("tcp://")[1]
  119. socktype = socket.SOCK_STREAM
  120. else:
  121. raise RuntimeError("invalid syslog address")
  122. if '[' in addr and ']' in addr:
  123. host = addr.split(']')[0][1:].lower()
  124. elif ':' in addr:
  125. host = addr.split(':')[0].lower()
  126. elif addr == "":
  127. host = "localhost"
  128. else:
  129. host = addr.lower()
  130. addr = addr.split(']')[-1]
  131. if ":" in addr:
  132. port = addr.split(':', 1)[1]
  133. if not port.isdigit():
  134. raise RuntimeError("%r is not a valid port number." % port)
  135. port = int(port)
  136. else:
  137. port = 514
  138. return (socktype, (host, port))
  139. class Logger(object):
  140. LOG_LEVELS = {
  141. "critical": logging.CRITICAL,
  142. "error": logging.ERROR,
  143. "warning": logging.WARNING,
  144. "info": logging.INFO,
  145. "debug": logging.DEBUG
  146. }
  147. loglevel = logging.INFO
  148. error_fmt = r"%(asctime)s [%(process)d] [%(levelname)s] %(message)s"
  149. datefmt = r"[%Y-%m-%d %H:%M:%S %z]"
  150. access_fmt = "%(message)s"
  151. syslog_fmt = "[%(process)d] %(message)s"
  152. atoms_wrapper_class = SafeAtoms
  153. def __init__(self, cfg):
  154. self.error_log = logging.getLogger("gunicorn.error")
  155. self.error_log.propagate = False
  156. self.access_log = logging.getLogger("gunicorn.access")
  157. self.access_log.propagate = False
  158. self.error_handlers = []
  159. self.access_handlers = []
  160. self.logfile = None
  161. self.lock = threading.Lock()
  162. self.cfg = cfg
  163. self.setup(cfg)
  164. def setup(self, cfg):
  165. self.loglevel = self.LOG_LEVELS.get(cfg.loglevel.lower(), logging.INFO)
  166. self.error_log.setLevel(self.loglevel)
  167. self.access_log.setLevel(logging.INFO)
  168. # set gunicorn.error handler
  169. if self.cfg.capture_output and cfg.errorlog != "-":
  170. for stream in sys.stdout, sys.stderr:
  171. stream.flush()
  172. self.logfile = open(cfg.errorlog, 'a+')
  173. os.dup2(self.logfile.fileno(), sys.stdout.fileno())
  174. os.dup2(self.logfile.fileno(), sys.stderr.fileno())
  175. self._set_handler(self.error_log, cfg.errorlog,
  176. logging.Formatter(self.error_fmt, self.datefmt))
  177. # set gunicorn.access handler
  178. if cfg.accesslog is not None:
  179. self._set_handler(self.access_log, cfg.accesslog,
  180. fmt=logging.Formatter(self.access_fmt))
  181. # set syslog handler
  182. if cfg.syslog:
  183. self._set_syslog_handler(
  184. self.error_log, cfg, self.syslog_fmt, "error"
  185. )
  186. self._set_syslog_handler(
  187. self.access_log, cfg, self.syslog_fmt, "access"
  188. )
  189. if cfg.logconfig:
  190. if os.path.exists(cfg.logconfig):
  191. defaults = CONFIG_DEFAULTS.copy()
  192. defaults['__file__'] = cfg.logconfig
  193. defaults['here'] = os.path.dirname(cfg.logconfig)
  194. fileConfig(cfg.logconfig, defaults=defaults,
  195. disable_existing_loggers=False)
  196. else:
  197. msg = "Error: log config '%s' not found"
  198. raise RuntimeError(msg % cfg.logconfig)
  199. def critical(self, msg, *args, **kwargs):
  200. self.error_log.critical(msg, *args, **kwargs)
  201. def error(self, msg, *args, **kwargs):
  202. self.error_log.error(msg, *args, **kwargs)
  203. def warning(self, msg, *args, **kwargs):
  204. self.error_log.warning(msg, *args, **kwargs)
  205. def info(self, msg, *args, **kwargs):
  206. self.error_log.info(msg, *args, **kwargs)
  207. def debug(self, msg, *args, **kwargs):
  208. self.error_log.debug(msg, *args, **kwargs)
  209. def exception(self, msg, *args, **kwargs):
  210. self.error_log.exception(msg, *args, **kwargs)
  211. def log(self, lvl, msg, *args, **kwargs):
  212. if isinstance(lvl, string_types):
  213. lvl = self.LOG_LEVELS.get(lvl.lower(), logging.INFO)
  214. self.error_log.log(lvl, msg, *args, **kwargs)
  215. def atoms(self, resp, req, environ, request_time):
  216. """ Gets atoms for log formating.
  217. """
  218. status = resp.status
  219. if isinstance(status, str):
  220. status = status.split(None, 1)[0]
  221. atoms = {
  222. 'h': environ.get('REMOTE_ADDR', '-'),
  223. 'l': '-',
  224. 'u': self._get_user(environ) or '-',
  225. 't': self.now(),
  226. 'r': "%s %s %s" % (environ['REQUEST_METHOD'],
  227. environ['RAW_URI'], environ["SERVER_PROTOCOL"]),
  228. 's': status,
  229. 'm': environ.get('REQUEST_METHOD'),
  230. 'U': environ.get('PATH_INFO'),
  231. 'q': environ.get('QUERY_STRING'),
  232. 'H': environ.get('SERVER_PROTOCOL'),
  233. 'b': getattr(resp, 'sent', None) and str(resp.sent) or '-',
  234. 'B': getattr(resp, 'sent', None),
  235. 'f': environ.get('HTTP_REFERER', '-'),
  236. 'a': environ.get('HTTP_USER_AGENT', '-'),
  237. 'T': request_time.seconds,
  238. 'D': (request_time.seconds*1000000) + request_time.microseconds,
  239. 'L': "%d.%06d" % (request_time.seconds, request_time.microseconds),
  240. 'p': "<%s>" % os.getpid()
  241. }
  242. # add request headers
  243. if hasattr(req, 'headers'):
  244. req_headers = req.headers
  245. else:
  246. req_headers = req
  247. if hasattr(req_headers, "items"):
  248. req_headers = req_headers.items()
  249. atoms.update(dict([("{%s}i" % k.lower(), v) for k, v in req_headers]))
  250. resp_headers = resp.headers
  251. if hasattr(resp_headers, "items"):
  252. resp_headers = resp_headers.items()
  253. # add response headers
  254. atoms.update(dict([("{%s}o" % k.lower(), v) for k, v in resp_headers]))
  255. return atoms
  256. def access(self, resp, req, environ, request_time):
  257. """ See http://httpd.apache.org/docs/2.0/logs.html#combined
  258. for format details
  259. """
  260. if not (self.cfg.accesslog or self.cfg.logconfig or self.cfg.syslog):
  261. return
  262. # wrap atoms:
  263. # - make sure atoms will be test case insensitively
  264. # - if atom doesn't exist replace it by '-'
  265. safe_atoms = self.atoms_wrapper_class(self.atoms(resp, req, environ,
  266. request_time))
  267. try:
  268. self.access_log.info(self.cfg.access_log_format % safe_atoms)
  269. except:
  270. self.error(traceback.format_exc())
  271. def now(self):
  272. """ return date in Apache Common Log Format """
  273. return time.strftime('[%d/%b/%Y:%H:%M:%S %z]')
  274. def reopen_files(self):
  275. if self.cfg.capture_output and self.cfg.errorlog != "-":
  276. for stream in sys.stdout, sys.stderr:
  277. stream.flush()
  278. with self.lock:
  279. if self.logfile is not None:
  280. self.logfile.close()
  281. self.logfile = open(self.cfg.errorlog, 'a+')
  282. os.dup2(self.logfile.fileno(), sys.stdout.fileno())
  283. os.dup2(self.logfile.fileno(), sys.stderr.fileno())
  284. for log in loggers():
  285. for handler in log.handlers:
  286. if isinstance(handler, logging.FileHandler):
  287. handler.acquire()
  288. try:
  289. if handler.stream:
  290. handler.stream.close()
  291. handler.stream = open(handler.baseFilename,
  292. handler.mode)
  293. finally:
  294. handler.release()
  295. def close_on_exec(self):
  296. for log in loggers():
  297. for handler in log.handlers:
  298. if isinstance(handler, logging.FileHandler):
  299. handler.acquire()
  300. try:
  301. if handler.stream:
  302. util.close_on_exec(handler.stream.fileno())
  303. finally:
  304. handler.release()
  305. def _get_gunicorn_handler(self, log):
  306. for h in log.handlers:
  307. if getattr(h, "_gunicorn", False):
  308. return h
  309. def _set_handler(self, log, output, fmt):
  310. # remove previous gunicorn log handler
  311. h = self._get_gunicorn_handler(log)
  312. if h:
  313. log.handlers.remove(h)
  314. if output is not None:
  315. if output == "-":
  316. h = logging.StreamHandler()
  317. else:
  318. util.check_is_writeable(output)
  319. h = logging.FileHandler(output)
  320. # make sure the user can reopen the file
  321. try:
  322. os.chown(h.baseFilename, self.cfg.user, self.cfg.group)
  323. except OSError:
  324. # it's probably OK there, we assume the user has given
  325. # /dev/null as a parameter.
  326. pass
  327. h.setFormatter(fmt)
  328. h._gunicorn = True
  329. log.addHandler(h)
  330. def _set_syslog_handler(self, log, cfg, fmt, name):
  331. # setup format
  332. if not cfg.syslog_prefix:
  333. prefix = cfg.proc_name.replace(":", ".")
  334. else:
  335. prefix = cfg.syslog_prefix
  336. prefix = "gunicorn.%s.%s" % (prefix, name)
  337. # set format
  338. fmt = logging.Formatter(r"%s: %s" % (prefix, fmt))
  339. # syslog facility
  340. try:
  341. facility = SYSLOG_FACILITIES[cfg.syslog_facility.lower()]
  342. except KeyError:
  343. raise RuntimeError("unknown facility name")
  344. # parse syslog address
  345. socktype, addr = parse_syslog_address(cfg.syslog_addr)
  346. # finally setup the syslog handler
  347. if sys.version_info >= (2, 7):
  348. h = logging.handlers.SysLogHandler(address=addr,
  349. facility=facility, socktype=socktype)
  350. else:
  351. # socktype is only supported in 2.7 and sup
  352. # fix issue #541
  353. h = logging.handlers.SysLogHandler(address=addr,
  354. facility=facility)
  355. h.setFormatter(fmt)
  356. h._gunicorn = True
  357. log.addHandler(h)
  358. def _get_user(self, environ):
  359. user = None
  360. http_auth = environ.get("HTTP_AUTHORIZATION")
  361. if http_auth and http_auth.startswith('Basic'):
  362. auth = http_auth.split(" ", 1)
  363. if len(auth) == 2:
  364. try:
  365. # b64decode doesn't accept unicode in Python < 3.3
  366. # so we need to convert it to a byte string
  367. auth = base64.b64decode(auth[1].strip().encode('utf-8'))
  368. if PY3: # b64decode returns a byte string in Python 3
  369. auth = auth.decode('utf-8')
  370. auth = auth.split(":", 1)
  371. except TypeError as exc:
  372. self.debug("Couldn't get username: %s", exc)
  373. return user
  374. except binascii.Error as exc:
  375. self.debug("Couldn't get username: %s", exc)
  376. return user
  377. if len(auth) == 2:
  378. user = auth[0]
  379. return user