logger.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2014-2016 OpenMarket Ltd
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. import logging
  16. import logging.config
  17. import os
  18. import sys
  19. from string import Template
  20. import yaml
  21. from twisted.logger import STDLibLogObserver, globalLogBeginner
  22. import synapse
  23. from synapse.app import _base as appbase
  24. from synapse.logging.context import LoggingContextFilter
  25. from synapse.util.versionstring import get_version_string
  26. from ._base import Config
  27. DEFAULT_LOG_CONFIG = Template(
  28. """
  29. version: 1
  30. formatters:
  31. precise:
  32. format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - \
  33. %(request)s - %(message)s'
  34. filters:
  35. context:
  36. (): synapse.logging.context.LoggingContextFilter
  37. request: ""
  38. handlers:
  39. file:
  40. class: logging.handlers.RotatingFileHandler
  41. formatter: precise
  42. filename: ${log_file}
  43. maxBytes: 104857600
  44. backupCount: 10
  45. filters: [context]
  46. encoding: utf8
  47. console:
  48. class: logging.StreamHandler
  49. formatter: precise
  50. filters: [context]
  51. loggers:
  52. synapse:
  53. level: INFO
  54. synapse.storage.SQL:
  55. # beware: increasing this to DEBUG will make synapse log sensitive
  56. # information such as access tokens.
  57. level: INFO
  58. root:
  59. level: INFO
  60. handlers: [file, console]
  61. """
  62. )
  63. class LoggingConfig(Config):
  64. def read_config(self, config, **kwargs):
  65. self.verbosity = config.get("verbose", 0)
  66. self.no_redirect_stdio = config.get("no_redirect_stdio", False)
  67. self.log_config = self.abspath(config.get("log_config"))
  68. self.log_file = self.abspath(config.get("log_file"))
  69. def generate_config_section(self, config_dir_path, server_name, **kwargs):
  70. log_config = os.path.join(config_dir_path, server_name + ".log.config")
  71. return (
  72. """\
  73. ## Logging ##
  74. # A yaml python logging config file
  75. #
  76. log_config: "%(log_config)s"
  77. """
  78. % locals()
  79. )
  80. def read_arguments(self, args):
  81. if args.verbose is not None:
  82. self.verbosity = args.verbose
  83. if args.no_redirect_stdio is not None:
  84. self.no_redirect_stdio = args.no_redirect_stdio
  85. if args.log_config is not None:
  86. self.log_config = args.log_config
  87. if args.log_file is not None:
  88. self.log_file = args.log_file
  89. def add_arguments(cls, parser):
  90. logging_group = parser.add_argument_group("logging")
  91. logging_group.add_argument(
  92. "-v",
  93. "--verbose",
  94. dest="verbose",
  95. action="count",
  96. help="The verbosity level. Specify multiple times to increase "
  97. "verbosity. (Ignored if --log-config is specified.)",
  98. )
  99. logging_group.add_argument(
  100. "-f",
  101. "--log-file",
  102. dest="log_file",
  103. help="File to log to. (Ignored if --log-config is specified.)",
  104. )
  105. logging_group.add_argument(
  106. "--log-config",
  107. dest="log_config",
  108. default=None,
  109. help="Python logging config file",
  110. )
  111. logging_group.add_argument(
  112. "-n",
  113. "--no-redirect-stdio",
  114. action="store_true",
  115. default=None,
  116. help="Do not redirect stdout/stderr to the log",
  117. )
  118. def generate_files(self, config, config_dir_path):
  119. log_config = config.get("log_config")
  120. if log_config and not os.path.exists(log_config):
  121. log_file = self.abspath("homeserver.log")
  122. print(
  123. "Generating log config file %s which will log to %s"
  124. % (log_config, log_file)
  125. )
  126. with open(log_config, "w") as log_config_file:
  127. log_config_file.write(DEFAULT_LOG_CONFIG.substitute(log_file=log_file))
  128. def setup_logging(config, use_worker_options=False):
  129. """ Set up python logging
  130. Args:
  131. config (LoggingConfig | synapse.config.workers.WorkerConfig):
  132. configuration data
  133. use_worker_options (bool): True to use 'worker_log_config' and
  134. 'worker_log_file' options instead of 'log_config' and 'log_file'.
  135. register_sighup (func | None): Function to call to register a
  136. sighup handler.
  137. """
  138. log_config = config.worker_log_config if use_worker_options else config.log_config
  139. log_file = config.worker_log_file if use_worker_options else config.log_file
  140. log_format = (
  141. "%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s"
  142. " - %(message)s"
  143. )
  144. if log_config is None:
  145. # We don't have a logfile, so fall back to the 'verbosity' param from
  146. # the config or cmdline. (Note that we generate a log config for new
  147. # installs, so this will be an unusual case)
  148. level = logging.INFO
  149. level_for_storage = logging.INFO
  150. if config.verbosity:
  151. level = logging.DEBUG
  152. if config.verbosity > 1:
  153. level_for_storage = logging.DEBUG
  154. logger = logging.getLogger("")
  155. logger.setLevel(level)
  156. logging.getLogger("synapse.storage.SQL").setLevel(level_for_storage)
  157. formatter = logging.Formatter(log_format)
  158. if log_file:
  159. # TODO: Customisable file size / backup count
  160. handler = logging.handlers.RotatingFileHandler(
  161. log_file, maxBytes=(1000 * 1000 * 100), backupCount=3, encoding="utf8"
  162. )
  163. def sighup(signum, stack):
  164. logger.info("Closing log file due to SIGHUP")
  165. handler.doRollover()
  166. logger.info("Opened new log file due to SIGHUP")
  167. else:
  168. handler = logging.StreamHandler()
  169. def sighup(*args):
  170. pass
  171. handler.setFormatter(formatter)
  172. handler.addFilter(LoggingContextFilter(request=""))
  173. logger.addHandler(handler)
  174. else:
  175. def load_log_config():
  176. with open(log_config, "r") as f:
  177. logging.config.dictConfig(yaml.safe_load(f))
  178. def sighup(*args):
  179. # it might be better to use a file watcher or something for this.
  180. load_log_config()
  181. logging.info("Reloaded log config from %s due to SIGHUP", log_config)
  182. load_log_config()
  183. appbase.register_sighup(sighup)
  184. # make sure that the first thing we log is a thing we can grep backwards
  185. # for
  186. logging.warn("***** STARTING SERVER *****")
  187. logging.warn("Server %s version %s", sys.argv[0], get_version_string(synapse))
  188. logging.info("Server hostname: %s", config.server_name)
  189. # It's critical to point twisted's internal logging somewhere, otherwise it
  190. # stacks up and leaks kup to 64K object;
  191. # see: https://twistedmatrix.com/trac/ticket/8164
  192. #
  193. # Routing to the python logging framework could be a performance problem if
  194. # the handlers blocked for a long time as python.logging is a blocking API
  195. # see https://twistedmatrix.com/documents/current/core/howto/logger.html
  196. # filed as https://github.com/matrix-org/synapse/issues/1727
  197. #
  198. # However this may not be too much of a problem if we are just writing to a file.
  199. observer = STDLibLogObserver()
  200. def _log(event):
  201. if "log_text" in event:
  202. if event["log_text"].startswith("DNSDatagramProtocol starting on "):
  203. return
  204. if event["log_text"].startswith("(UDP Port "):
  205. return
  206. if event["log_text"].startswith("Timing out client"):
  207. return
  208. return observer(event)
  209. globalLogBeginner.beginLoggingTo(
  210. [_log], redirectStandardIO=not config.no_redirect_stdio
  211. )
  212. if not config.no_redirect_stdio:
  213. print("Redirected stdout/stderr to logs")