logger.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  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 signal
  19. import sys
  20. from string import Template
  21. import yaml
  22. from twisted.logger import STDLibLogObserver, globalLogBeginner
  23. import synapse
  24. from synapse.util.logcontext import LoggingContextFilter
  25. from synapse.util.versionstring import get_version_string
  26. from ._base import Config
  27. DEFAULT_LOG_CONFIG = Template("""
  28. version: 1
  29. formatters:
  30. precise:
  31. format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - \
  32. %(request)s - %(message)s'
  33. filters:
  34. context:
  35. (): synapse.util.logcontext.LoggingContextFilter
  36. request: ""
  37. handlers:
  38. file:
  39. class: logging.handlers.RotatingFileHandler
  40. formatter: precise
  41. filename: ${log_file}
  42. maxBytes: 104857600
  43. backupCount: 10
  44. filters: [context]
  45. console:
  46. class: logging.StreamHandler
  47. formatter: precise
  48. filters: [context]
  49. loggers:
  50. synapse:
  51. level: INFO
  52. synapse.storage.SQL:
  53. # beware: increasing this to DEBUG will make synapse log sensitive
  54. # information such as access tokens.
  55. level: INFO
  56. root:
  57. level: INFO
  58. handlers: [file, console]
  59. """)
  60. class LoggingConfig(Config):
  61. def read_config(self, config):
  62. self.verbosity = config.get("verbose", 0)
  63. self.no_redirect_stdio = config.get("no_redirect_stdio", False)
  64. self.log_config = self.abspath(config.get("log_config"))
  65. self.log_file = self.abspath(config.get("log_file"))
  66. def default_config(self, config_dir_path, server_name, **kwargs):
  67. log_config = self.abspath(
  68. os.path.join(config_dir_path, server_name + ".log.config")
  69. )
  70. return """
  71. # A yaml python logging config file
  72. log_config: "%(log_config)s"
  73. """ % locals()
  74. def read_arguments(self, args):
  75. if args.verbose is not None:
  76. self.verbosity = args.verbose
  77. if args.no_redirect_stdio is not None:
  78. self.no_redirect_stdio = args.no_redirect_stdio
  79. if args.log_config is not None:
  80. self.log_config = args.log_config
  81. if args.log_file is not None:
  82. self.log_file = args.log_file
  83. def add_arguments(cls, parser):
  84. logging_group = parser.add_argument_group("logging")
  85. logging_group.add_argument(
  86. '-v', '--verbose', dest="verbose", action='count',
  87. help="The verbosity level. Specify multiple times to increase "
  88. "verbosity. (Ignored if --log-config is specified.)"
  89. )
  90. logging_group.add_argument(
  91. '-f', '--log-file', dest="log_file",
  92. help="File to log to. (Ignored if --log-config is specified.)"
  93. )
  94. logging_group.add_argument(
  95. '--log-config', dest="log_config", default=None,
  96. help="Python logging config file"
  97. )
  98. logging_group.add_argument(
  99. '-n', '--no-redirect-stdio',
  100. action='store_true', default=None,
  101. help="Do not redirect stdout/stderr to the log"
  102. )
  103. def generate_files(self, config):
  104. log_config = config.get("log_config")
  105. if log_config and not os.path.exists(log_config):
  106. log_file = self.abspath("homeserver.log")
  107. with open(log_config, "w") as log_config_file:
  108. log_config_file.write(
  109. DEFAULT_LOG_CONFIG.substitute(log_file=log_file)
  110. )
  111. def setup_logging(config, use_worker_options=False):
  112. """ Set up python logging
  113. Args:
  114. config (LoggingConfig | synapse.config.workers.WorkerConfig):
  115. configuration data
  116. use_worker_options (bool): True to use 'worker_log_config' and
  117. 'worker_log_file' options instead of 'log_config' and 'log_file'.
  118. """
  119. log_config = (config.worker_log_config if use_worker_options
  120. else config.log_config)
  121. log_file = (config.worker_log_file if use_worker_options
  122. else config.log_file)
  123. log_format = (
  124. "%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s"
  125. " - %(message)s"
  126. )
  127. if log_config is None:
  128. # We don't have a logfile, so fall back to the 'verbosity' param from
  129. # the config or cmdline. (Note that we generate a log config for new
  130. # installs, so this will be an unusual case)
  131. level = logging.INFO
  132. level_for_storage = logging.INFO
  133. if config.verbosity:
  134. level = logging.DEBUG
  135. if config.verbosity > 1:
  136. level_for_storage = logging.DEBUG
  137. logger = logging.getLogger('')
  138. logger.setLevel(level)
  139. logging.getLogger('synapse.storage.SQL').setLevel(level_for_storage)
  140. formatter = logging.Formatter(log_format)
  141. if log_file:
  142. # TODO: Customisable file size / backup count
  143. handler = logging.handlers.RotatingFileHandler(
  144. log_file, maxBytes=(1000 * 1000 * 100), backupCount=3
  145. )
  146. def sighup(signum, stack):
  147. logger.info("Closing log file due to SIGHUP")
  148. handler.doRollover()
  149. logger.info("Opened new log file due to SIGHUP")
  150. else:
  151. handler = logging.StreamHandler()
  152. def sighup(signum, stack):
  153. pass
  154. handler.setFormatter(formatter)
  155. handler.addFilter(LoggingContextFilter(request=""))
  156. logger.addHandler(handler)
  157. else:
  158. def load_log_config():
  159. with open(log_config, 'r') as f:
  160. logging.config.dictConfig(yaml.load(f))
  161. def sighup(signum, stack):
  162. # it might be better to use a file watcher or something for this.
  163. logging.info("Reloading log config from %s due to SIGHUP",
  164. log_config)
  165. load_log_config()
  166. load_log_config()
  167. # TODO(paul): obviously this is a terrible mechanism for
  168. # stealing SIGHUP, because it means no other part of synapse
  169. # can use it instead. If we want to catch SIGHUP anywhere
  170. # else as well, I'd suggest we find a nicer way to broadcast
  171. # it around.
  172. if getattr(signal, "SIGHUP"):
  173. signal.signal(signal.SIGHUP, sighup)
  174. # make sure that the first thing we log is a thing we can grep backwards
  175. # for
  176. logging.warn("***** STARTING SERVER *****")
  177. logging.warn(
  178. "Server %s version %s",
  179. sys.argv[0], get_version_string(synapse),
  180. )
  181. logging.info("Server hostname: %s", config.server_name)
  182. # It's critical to point twisted's internal logging somewhere, otherwise it
  183. # stacks up and leaks kup to 64K object;
  184. # see: https://twistedmatrix.com/trac/ticket/8164
  185. #
  186. # Routing to the python logging framework could be a performance problem if
  187. # the handlers blocked for a long time as python.logging is a blocking API
  188. # see https://twistedmatrix.com/documents/current/core/howto/logger.html
  189. # filed as https://github.com/matrix-org/synapse/issues/1727
  190. #
  191. # However this may not be too much of a problem if we are just writing to a file.
  192. observer = STDLibLogObserver()
  193. globalLogBeginner.beginLoggingTo(
  194. [observer],
  195. redirectStandardIO=not config.no_redirect_stdio,
  196. )