logger.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  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. from ._base import Config
  16. from synapse.util.logcontext import LoggingContextFilter
  17. from twisted.python.log import PythonLoggingObserver
  18. import logging
  19. import logging.config
  20. import yaml
  21. from string import Template
  22. import os
  23. import signal
  24. from synapse.util.debug import debug_deferreds
  25. DEFAULT_LOG_CONFIG = Template("""
  26. version: 1
  27. formatters:
  28. precise:
  29. format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s\
  30. - %(message)s'
  31. filters:
  32. context:
  33. (): synapse.util.logcontext.LoggingContextFilter
  34. request: ""
  35. handlers:
  36. file:
  37. class: logging.handlers.RotatingFileHandler
  38. formatter: precise
  39. filename: ${log_file}
  40. maxBytes: 104857600
  41. backupCount: 10
  42. filters: [context]
  43. level: INFO
  44. console:
  45. class: logging.StreamHandler
  46. formatter: precise
  47. loggers:
  48. synapse:
  49. level: INFO
  50. synapse.storage.SQL:
  51. level: INFO
  52. root:
  53. level: INFO
  54. handlers: [file, console]
  55. """)
  56. class LoggingConfig(Config):
  57. def read_config(self, config):
  58. self.verbosity = config.get("verbose", 0)
  59. self.log_config = self.abspath(config.get("log_config"))
  60. self.log_file = self.abspath(config.get("log_file"))
  61. if config.get("full_twisted_stacktraces"):
  62. debug_deferreds()
  63. def default_config(self, config_dir_path, server_name, **kwargs):
  64. log_file = self.abspath("homeserver.log")
  65. log_config = self.abspath(
  66. os.path.join(config_dir_path, server_name + ".log.config")
  67. )
  68. return """
  69. # Logging verbosity level.
  70. verbose: 0
  71. # File to write logging to
  72. log_file: "%(log_file)s"
  73. # A yaml python logging config file
  74. log_config: "%(log_config)s"
  75. # Stop twisted from discarding the stack traces of exceptions in
  76. # deferreds by waiting a reactor tick before running a deferred's
  77. # callbacks.
  78. # full_twisted_stacktraces: true
  79. """ % locals()
  80. def read_arguments(self, args):
  81. if args.verbose is not None:
  82. self.verbosity = args.verbose
  83. if args.log_config is not None:
  84. self.log_config = args.log_config
  85. if args.log_file is not None:
  86. self.log_file = args.log_file
  87. def add_arguments(cls, parser):
  88. logging_group = parser.add_argument_group("logging")
  89. logging_group.add_argument(
  90. '-v', '--verbose', dest="verbose", action='count',
  91. help="The verbosity level."
  92. )
  93. logging_group.add_argument(
  94. '-f', '--log-file', dest="log_file",
  95. help="File to log to."
  96. )
  97. logging_group.add_argument(
  98. '--log-config', dest="log_config", default=None,
  99. help="Python logging config file"
  100. )
  101. def generate_files(self, config):
  102. log_config = config.get("log_config")
  103. if log_config and not os.path.exists(log_config):
  104. with open(log_config, "wb") as log_config_file:
  105. log_config_file.write(
  106. DEFAULT_LOG_CONFIG.substitute(log_file=config["log_file"])
  107. )
  108. def setup_logging(self):
  109. log_format = (
  110. "%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s"
  111. " - %(message)s"
  112. )
  113. if self.log_config is None:
  114. level = logging.INFO
  115. level_for_storage = logging.INFO
  116. if self.verbosity:
  117. level = logging.DEBUG
  118. if self.verbosity > 1:
  119. level_for_storage = logging.DEBUG
  120. # FIXME: we need a logging.WARN for a -q quiet option
  121. logger = logging.getLogger('')
  122. logger.setLevel(level)
  123. logging.getLogger('synapse.storage').setLevel(level_for_storage)
  124. formatter = logging.Formatter(log_format)
  125. if self.log_file:
  126. # TODO: Customisable file size / backup count
  127. handler = logging.handlers.RotatingFileHandler(
  128. self.log_file, maxBytes=(1000 * 1000 * 100), backupCount=3
  129. )
  130. def sighup(signum, stack):
  131. logger.info("Closing log file due to SIGHUP")
  132. handler.doRollover()
  133. logger.info("Opened new log file due to SIGHUP")
  134. # TODO(paul): obviously this is a terrible mechanism for
  135. # stealing SIGHUP, because it means no other part of synapse
  136. # can use it instead. If we want to catch SIGHUP anywhere
  137. # else as well, I'd suggest we find a nicer way to broadcast
  138. # it around.
  139. if getattr(signal, "SIGHUP"):
  140. signal.signal(signal.SIGHUP, sighup)
  141. else:
  142. handler = logging.StreamHandler()
  143. handler.setFormatter(formatter)
  144. handler.addFilter(LoggingContextFilter(request=""))
  145. logger.addHandler(handler)
  146. else:
  147. with open(self.log_config, 'r') as f:
  148. logging.config.dictConfig(yaml.load(f))
  149. observer = PythonLoggingObserver()
  150. observer.start()