1414import contextlib
1515import logging
1616import time
17- from typing import Optional , Type , Union
17+ from typing import Optional , Tuple , Type , Union
1818
1919import attr
2020from zope .interface import implementer
2626from synapse .config .server import ListenerConfig
2727from synapse .http import get_request_user_agent , redact_uri
2828from synapse .http .request_metrics import RequestMetrics , requests_counter
29- from synapse .logging .context import LoggingContext , PreserveLoggingContext
29+ from synapse .logging .context import (
30+ ContextRequest ,
31+ LoggingContext ,
32+ PreserveLoggingContext ,
33+ )
3034from synapse .types import Requester
3135
3236logger = logging .getLogger (__name__ )
@@ -63,7 +67,7 @@ def __init__(self, channel, *args, **kw):
6367
6468 # The requester, if authenticated. For federation requests this is the
6569 # server name, for client requests this is the Requester object.
66- self .requester = None # type: Optional[Union[Requester, str]]
70+ self ._requester = None # type: Optional[Union[Requester, str]]
6771
6872 # we can't yet create the logcontext, as we don't know the method.
6973 self .logcontext = None # type: Optional[LoggingContext]
@@ -93,6 +97,31 @@ def __repr__(self):
9397 self .site .site_tag ,
9498 )
9599
100+ @property
101+ def requester (self ) -> Optional [Union [Requester , str ]]:
102+ return self ._requester
103+
104+ @requester .setter
105+ def requester (self , value : Union [Requester , str ]) -> None :
106+ # Store the requester, and update some properties based on it.
107+
108+ # This should only be called once.
109+ assert self ._requester is None
110+
111+ self ._requester = value
112+
113+ # A logging context should exist by now (and have a ContextRequest).
114+ assert self .logcontext is not None
115+ assert self .logcontext .request is not None
116+
117+ (
118+ requester ,
119+ authenticated_entity ,
120+ ) = self .get_authenticated_entity ()
121+ self .logcontext .request .requester = requester
122+ # If there's no authenticated entity, it was the requester.
123+ self .logcontext .request .authenticated_entity = authenticated_entity or requester
124+
96125 def get_request_id (self ):
97126 return "%s-%i" % (self .get_method (), self .request_seq )
98127
@@ -126,13 +155,60 @@ def get_method(self) -> str:
126155 return self .method .decode ("ascii" )
127156 return method
128157
158+ def get_authenticated_entity (self ) -> Tuple [Optional [str ], Optional [str ]]:
159+ """
160+ Get the "authenticated" entity of the request, which might be the user
161+ performing the action, or a user being puppeted by a server admin.
162+
163+ Returns:
164+ A tuple:
165+ The first item is a string representing the user making the request.
166+
167+ The second item is a string or None representing the user who
168+ authenticated when making this request. See
169+ Requester.authenticated_entity.
170+ """
171+ # Convert the requester into a string that we can log
172+ if isinstance (self ._requester , str ):
173+ return self ._requester , None
174+ elif isinstance (self ._requester , Requester ):
175+ requester = self ._requester .user .to_string ()
176+ authenticated_entity = self ._requester .authenticated_entity
177+
178+ # If this is a request where the target user doesn't match the user who
179+ # authenticated (e.g. and admin is puppetting a user) then we return both.
180+ if self ._requester .user .to_string () != authenticated_entity :
181+ return requester , authenticated_entity
182+
183+ return requester , None
184+ elif self ._requester is not None :
185+ # This shouldn't happen, but we log it so we don't lose information
186+ # and can see that we're doing something wrong.
187+ return repr (self ._requester ), None # type: ignore[unreachable]
188+
189+ return None , None
190+
129191 def render (self , resrc ):
130192 # this is called once a Resource has been found to serve the request; in our
131193 # case the Resource in question will normally be a JsonResource.
132194
133195 # create a LogContext for this request
134196 request_id = self .get_request_id ()
135- self .logcontext = LoggingContext (request_id , request = request_id )
197+ self .logcontext = LoggingContext (
198+ request_id ,
199+ request = ContextRequest (
200+ request_id = request_id ,
201+ ip_address = self .getClientIP (),
202+ site_tag = self .site .site_tag ,
203+ # The requester is going to be unknown at this point.
204+ requester = None ,
205+ authenticated_entity = None ,
206+ method = self .get_method (),
207+ url = self .get_redacted_uri (),
208+ protocol = self .clientproto .decode ("ascii" , errors = "replace" ),
209+ user_agent = get_request_user_agent (self ),
210+ ),
211+ )
136212
137213 # override the Server header which is set by twisted
138214 self .setHeader ("Server" , self .site .server_version_string )
@@ -277,25 +353,6 @@ def _finished_processing(self):
277353 # to the client (nb may be negative)
278354 response_send_time = self .finish_time - self ._processing_finished_time
279355
280- # Convert the requester into a string that we can log
281- authenticated_entity = None
282- if isinstance (self .requester , str ):
283- authenticated_entity = self .requester
284- elif isinstance (self .requester , Requester ):
285- authenticated_entity = self .requester .authenticated_entity
286-
287- # If this is a request where the target user doesn't match the user who
288- # authenticated (e.g. and admin is puppetting a user) then we log both.
289- if self .requester .user .to_string () != authenticated_entity :
290- authenticated_entity = "{},{}" .format (
291- authenticated_entity ,
292- self .requester .user .to_string (),
293- )
294- elif self .requester is not None :
295- # This shouldn't happen, but we log it so we don't lose information
296- # and can see that we're doing something wrong.
297- authenticated_entity = repr (self .requester ) # type: ignore[unreachable]
298-
299356 user_agent = get_request_user_agent (self , "-" )
300357
301358 code = str (self .code )
@@ -305,14 +362,21 @@ def _finished_processing(self):
305362 code += "!"
306363
307364 log_level = logging .INFO if self ._should_log_request () else logging .DEBUG
365+
366+ # If this is a request where the target user doesn't match the user who
367+ # authenticated (e.g. and admin is puppetting a user) then we log both.
368+ requester , authenticated_entity = self .get_authenticated_entity ()
369+ if authenticated_entity :
370+ requester = "{}.{}" .format (authenticated_entity , requester )
371+
308372 self .site .access_logger .log (
309373 log_level ,
310374 "%s - %s - {%s}"
311375 " Processed request: %.3fsec/%.3fsec (%.3fsec, %.3fsec) (%.3fsec/%.3fsec/%d)"
312376 ' %sB %s "%s %s %s" "%s" [%d dbevts]' ,
313377 self .getClientIP (),
314378 self .site .site_tag ,
315- authenticated_entity ,
379+ requester ,
316380 processing_time ,
317381 response_send_time ,
318382 usage .ru_utime ,
0 commit comments