site.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. # Copyright 2016 OpenMarket Ltd
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. from synapse.util.logcontext import LoggingContext
  15. from twisted.web.server import Site, Request
  16. import contextlib
  17. import logging
  18. import re
  19. import time
  20. ACCESS_TOKEN_RE = re.compile(r'(\?.*access(_|%5[Ff])token=)[^&]*(.*)$')
  21. class SynapseRequest(Request):
  22. def __init__(self, site, *args, **kw):
  23. Request.__init__(self, *args, **kw)
  24. self.site = site
  25. self.authenticated_entity = None
  26. self.start_time = 0
  27. def __repr__(self):
  28. # We overwrite this so that we don't log ``access_token``
  29. return '<%s at 0x%x method=%s uri=%s clientproto=%s site=%s>' % (
  30. self.__class__.__name__,
  31. id(self),
  32. self.method,
  33. self.get_redacted_uri(),
  34. self.clientproto,
  35. self.site.site_tag,
  36. )
  37. def get_redacted_uri(self):
  38. return ACCESS_TOKEN_RE.sub(
  39. r'\1<redacted>\3',
  40. self.uri
  41. )
  42. def get_user_agent(self):
  43. return self.requestHeaders.getRawHeaders("User-Agent", [None])[-1]
  44. def started_processing(self):
  45. self.site.access_logger.info(
  46. "%s - %s - Received request: %s %s",
  47. self.getClientIP(),
  48. self.site.site_tag,
  49. self.method,
  50. self.get_redacted_uri()
  51. )
  52. self.start_time = int(time.time() * 1000)
  53. def finished_processing(self):
  54. try:
  55. context = LoggingContext.current_context()
  56. ru_utime, ru_stime = context.get_resource_usage()
  57. db_txn_count = context.db_txn_count
  58. db_txn_duration = context.db_txn_duration
  59. except:
  60. ru_utime, ru_stime = (0, 0)
  61. db_txn_count, db_txn_duration = (0, 0)
  62. self.site.access_logger.info(
  63. "%s - %s - {%s}"
  64. " Processed request: %dms (%dms, %dms) (%dms/%d)"
  65. " %sB %s \"%s %s %s\" \"%s\"",
  66. self.getClientIP(),
  67. self.site.site_tag,
  68. self.authenticated_entity,
  69. int(time.time() * 1000) - self.start_time,
  70. int(ru_utime * 1000),
  71. int(ru_stime * 1000),
  72. int(db_txn_duration * 1000),
  73. int(db_txn_count),
  74. self.sentLength,
  75. self.code,
  76. self.method,
  77. self.get_redacted_uri(),
  78. self.clientproto,
  79. self.get_user_agent(),
  80. )
  81. @contextlib.contextmanager
  82. def processing(self):
  83. self.started_processing()
  84. yield
  85. self.finished_processing()
  86. class XForwardedForRequest(SynapseRequest):
  87. def __init__(self, *args, **kw):
  88. SynapseRequest.__init__(self, *args, **kw)
  89. """
  90. Add a layer on top of another request that only uses the value of an
  91. X-Forwarded-For header as the result of C{getClientIP}.
  92. """
  93. def getClientIP(self):
  94. """
  95. @return: The client address (the first address) in the value of the
  96. I{X-Forwarded-For header}. If the header is not present, return
  97. C{b"-"}.
  98. """
  99. return self.requestHeaders.getRawHeaders(
  100. b"x-forwarded-for", [b"-"])[0].split(b",")[0].strip()
  101. class SynapseRequestFactory(object):
  102. def __init__(self, site, x_forwarded_for):
  103. self.site = site
  104. self.x_forwarded_for = x_forwarded_for
  105. def __call__(self, *args, **kwargs):
  106. if self.x_forwarded_for:
  107. return XForwardedForRequest(self.site, *args, **kwargs)
  108. else:
  109. return SynapseRequest(self.site, *args, **kwargs)
  110. class SynapseSite(Site):
  111. """
  112. Subclass of a twisted http Site that does access logging with python's
  113. standard logging
  114. """
  115. def __init__(self, logger_name, site_tag, config, resource, *args, **kwargs):
  116. Site.__init__(self, resource, *args, **kwargs)
  117. self.site_tag = site_tag
  118. proxied = config.get("x_forwarded", False)
  119. self.requestFactory = SynapseRequestFactory(self, proxied)
  120. self.access_logger = logging.getLogger(logger_name)
  121. def log(self, request):
  122. pass