diff --git a/daphne/access.py b/daphne/access.py new file mode 100644 index 0000000..a3508ea --- /dev/null +++ b/daphne/access.py @@ -0,0 +1,54 @@ +import datetime + + +class AccessLogGenerator(object): + """ + Object that implements the Daphne "action logger" internal interface in + order to provide an access log in something resembling NCSA format. + """ + + def __init__(self, stream): + self.stream = stream + + def __call__(self, protocol, action, details): + """ + Called when an action happens; use it to generate log entries. + """ + # HTTP requests + if protocol == "http" and action == "complete": + self.write_entry( + host=details['client'], + date=datetime.datetime.now(), + request="%(method)s %(path)s" % details, + status=details['status'], + ) + # Websocket requests + elif protocol == "websocket" and action == "connected": + self.write_entry( + host=details['client'], + date=datetime.datetime.now(), + request="WSCONNECT %(path)s" % details, + ) + elif protocol == "websocket" and action == "disconnected": + self.write_entry( + host=details['client'], + date=datetime.datetime.now(), + request="WSDISCONNECT %(path)s" % details, + ) + + def write_entry(self, host, date, request, status=None, length=None, ident=None, user=None): + """ + Writes an NCSA-style entry to the log file (some liberty is taken with + what the entries are for non-HTTP) + """ + self.stream.write( + "%s %s %s [%s] \"%s\" %s %s\n" % ( + host, + ident or "-", + user or "-", + date.strftime("%d/%b/%Y:%H:%M:%S"), + request, + status or "-", + length or "-", + ) + ) diff --git a/daphne/cli.py b/daphne/cli.py index 543c274..06762e1 100755 --- a/daphne/cli.py +++ b/daphne/cli.py @@ -3,6 +3,7 @@ import argparse import logging import importlib from .server import Server +from .access import AccessLogGenerator logger = logging.getLogger(__name__) @@ -54,6 +55,11 @@ class CommandLineInterface(object): help='How long to wait for worker server before timing out HTTP connections', default=120, ) + self.parser.add_argument( + '--access-log', + help='Where to write the access log (- for stdout, the default for verbosity=1)', + default=None, + ) self.parser.add_argument( '--ping-interval', type=int, @@ -88,6 +94,12 @@ class CommandLineInterface(object): }[args.verbosity], format = "%(asctime)-15s %(levelname)-8s %(message)s" , ) + # If verbosity is 1 or greater, or they told us explicitly, set up access log + access_log_stream = None + if args.access_log: + access_log_stream = open(args.access_log, "a") + elif args.verbosity >= 1: + access_log_stream = sys.stdout # Import channel layer sys.path.insert(0, ".") module_path, object_path = args.channel_layer.split(":", 1) @@ -107,4 +119,5 @@ class CommandLineInterface(object): unix_socket=args.unix_socket, http_timeout=args.http_timeout, ping_interval=args.ping_interval, + action_logger=AccessLogGenerator(access_log_stream) if access_log_stream else None, ).run()