You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			110 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			110 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			Python
		
	
"""Log utilities."""
 | 
						|
 | 
						|
# -----------------------------------------------------------------------------
 | 
						|
#  Copyright (c) Jupyter Development Team
 | 
						|
#
 | 
						|
#  Distributed under the terms of the BSD License.  The full license is in
 | 
						|
#  the file LICENSE, distributed as part of this software.
 | 
						|
# -----------------------------------------------------------------------------
 | 
						|
import json
 | 
						|
from urllib.parse import urlparse, urlunparse
 | 
						|
 | 
						|
from tornado.log import access_log
 | 
						|
 | 
						|
from .auth import User
 | 
						|
from .prometheus.log_functions import prometheus_log_method
 | 
						|
 | 
						|
# url params to be scrubbed if seen
 | 
						|
# any url param that *contains* one of these
 | 
						|
# will be scrubbed from logs
 | 
						|
_DEFAULT_SCRUB_PARAM_KEYS = {"token", "auth", "key", "code", "state", "xsrf"}
 | 
						|
 | 
						|
 | 
						|
def _scrub_uri(uri: str, extra_param_keys=None) -> str:
 | 
						|
    """scrub auth info from uri"""
 | 
						|
 | 
						|
    scrub_param_keys = _DEFAULT_SCRUB_PARAM_KEYS.union(set(extra_param_keys or []))
 | 
						|
 | 
						|
    parsed = urlparse(uri)
 | 
						|
    if parsed.query:
 | 
						|
        # check for potentially sensitive url params
 | 
						|
        # use manual list + split rather than parsing
 | 
						|
        # to minimally perturb original
 | 
						|
        parts = parsed.query.split("&")
 | 
						|
        changed = False
 | 
						|
        for i, s in enumerate(parts):
 | 
						|
            key, sep, value = s.partition("=")
 | 
						|
            for substring in scrub_param_keys:
 | 
						|
                if substring in key:
 | 
						|
                    parts[i] = f"{key}{sep}[secret]"
 | 
						|
                    changed = True
 | 
						|
        if changed:
 | 
						|
            parsed = parsed._replace(query="&".join(parts))
 | 
						|
            return urlunparse(parsed)
 | 
						|
    return uri
 | 
						|
 | 
						|
 | 
						|
def log_request(handler, record_prometheus_metrics=True):
 | 
						|
    """log a bit more information about each request than tornado's default
 | 
						|
 | 
						|
    - move static file get success to debug-level (reduces noise)
 | 
						|
    - get proxied IP instead of proxy IP
 | 
						|
    - log referer for redirect and failed requests
 | 
						|
    - log user-agent for failed requests
 | 
						|
 | 
						|
    if record_prometheus_metrics is true, will record a histogram prometheus
 | 
						|
    metric (http_request_duration_seconds) for each request handler
 | 
						|
    """
 | 
						|
    status = handler.get_status()
 | 
						|
    request = handler.request
 | 
						|
    try:
 | 
						|
        logger = handler.log
 | 
						|
    except AttributeError:
 | 
						|
        logger = access_log
 | 
						|
 | 
						|
    extra_param_keys = handler.settings.get("extra_log_scrub_param_keys", [])
 | 
						|
 | 
						|
    if status < 300 or status == 304:
 | 
						|
        # Successes (or 304 FOUND) are debug-level
 | 
						|
        log_method = logger.debug
 | 
						|
    elif status < 400:
 | 
						|
        log_method = logger.info
 | 
						|
    elif status < 500:
 | 
						|
        log_method = logger.warning
 | 
						|
    else:
 | 
						|
        log_method = logger.error
 | 
						|
 | 
						|
    request_time = 1000.0 * handler.request.request_time()
 | 
						|
    ns = {
 | 
						|
        "status": status,
 | 
						|
        "method": request.method,
 | 
						|
        "ip": request.remote_ip,
 | 
						|
        "uri": _scrub_uri(request.uri, extra_param_keys),
 | 
						|
        "request_time": request_time,
 | 
						|
    }
 | 
						|
    # log username
 | 
						|
    # make sure we don't break anything
 | 
						|
    # in case mixins cause current_user to not be a User somehow
 | 
						|
    try:
 | 
						|
        user = handler.current_user
 | 
						|
    except Exception:
 | 
						|
        user = None
 | 
						|
    username = (user.username if isinstance(user, User) else "unknown") if user else ""
 | 
						|
    ns["username"] = username
 | 
						|
 | 
						|
    msg = "{status} {method} {uri} ({username}@{ip}) {request_time:.2f}ms"
 | 
						|
    if status >= 400:
 | 
						|
        # log bad referrers
 | 
						|
        ns["referer"] = _scrub_uri(request.headers.get("Referer", "None"), extra_param_keys)
 | 
						|
        msg = msg + " referer={referer}"
 | 
						|
    if status >= 500 and status != 502:
 | 
						|
        # Log a subset of the headers if it caused an error.
 | 
						|
        headers = {}
 | 
						|
        for header in ["Host", "Accept", "Referer", "User-Agent"]:
 | 
						|
            if header in request.headers:
 | 
						|
                headers[header] = request.headers[header]
 | 
						|
        log_method(json.dumps(headers, indent=2))
 | 
						|
    log_method(msg.format(**ns))
 | 
						|
    if record_prometheus_metrics:
 | 
						|
        prometheus_log_method(handler)
 |