1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3 4import os 5import logging 6import json 7from pathlib import Path 8from django.http import HttpRequest 9 10BUILDDIR = Path(os.environ.get('BUILDDIR', '/tmp')) 11 12def log_api_request(request, response, view, logger_name='api'): 13 """Helper function for LogAPIMixin""" 14 15 repjson = { 16 'view': view, 17 'path': request.path, 18 'method': request.method, 19 'status': response.status_code 20 } 21 22 logger = logging.getLogger(logger_name) 23 logger.info( 24 json.dumps(repjson, indent=4, separators=(", ", " : ")) 25 ) 26 27 28def log_view_mixin(view): 29 def log_view_request(*args, **kwargs): 30 # get request from args else kwargs 31 request = None 32 if len(args) > 0: 33 for req in args: 34 if isinstance(req, HttpRequest): 35 request = req 36 break 37 elif request is None: 38 request = kwargs.get('request') 39 40 response = view(*args, **kwargs) 41 view_name = 'unknown' 42 if hasattr(request, 'resolver_match'): 43 if hasattr(request.resolver_match, 'view_name'): 44 view_name = request.resolver_match.view_name 45 46 log_api_request( 47 request, response, view_name, 'toaster') 48 return response 49 return log_view_request 50 51 52 53class LogAPIMixin: 54 """Logs API requests 55 56 tested with: 57 - APIView 58 - ModelViewSet 59 - ReadOnlyModelViewSet 60 - GenericAPIView 61 62 Note: you can set `view_name` attribute in View to override get_view_name() 63 """ 64 65 def get_view_name(self): 66 if hasattr(self, 'view_name'): 67 return self.view_name 68 return super().get_view_name() 69 70 def finalize_response(self, request, response, *args, **kwargs): 71 log_api_request(request, response, self.get_view_name()) 72 return super().finalize_response(request, response, *args, **kwargs) 73 74 75LOGGING_SETTINGS = { 76 'version': 1, 77 'disable_existing_loggers': False, 78 'filters': { 79 'require_debug_false': { 80 '()': 'django.utils.log.RequireDebugFalse' 81 } 82 }, 83 'formatters': { 84 'datetime': { 85 'format': '%(asctime)s %(levelname)s %(message)s' 86 }, 87 'verbose': { 88 'format': '{levelname} {asctime} {module} {name}.{funcName} {process:d} {thread:d} {message}', 89 'datefmt': "%d/%b/%Y %H:%M:%S", 90 'style': '{', 91 }, 92 'api': { 93 'format': '\n{levelname} {asctime} {name}.{funcName}:\n{message}', 94 'style': '{' 95 } 96 }, 97 'handlers': { 98 'mail_admins': { 99 'level': 'ERROR', 100 'filters': ['require_debug_false'], 101 'class': 'django.utils.log.AdminEmailHandler' 102 }, 103 'console': { 104 'level': 'DEBUG', 105 'class': 'logging.StreamHandler', 106 'formatter': 'datetime', 107 }, 108 'file_django': { 109 'level': 'INFO', 110 'class': 'logging.handlers.TimedRotatingFileHandler', 111 'filename': BUILDDIR / 'toaster_logs/django.log', 112 'when': 'D', # interval type 113 'interval': 1, # defaults to 1 114 'backupCount': 10, # how many files to keep 115 'formatter': 'verbose', 116 }, 117 'file_api': { 118 'level': 'INFO', 119 'class': 'logging.handlers.TimedRotatingFileHandler', 120 'filename': BUILDDIR / 'toaster_logs/api.log', 121 'when': 'D', 122 'interval': 1, 123 'backupCount': 10, 124 'formatter': 'verbose', 125 }, 126 'file_toaster': { 127 'level': 'INFO', 128 'class': 'logging.handlers.TimedRotatingFileHandler', 129 'filename': BUILDDIR / 'toaster_logs/web.log', 130 'when': 'D', 131 'interval': 1, 132 'backupCount': 10, 133 'formatter': 'verbose', 134 }, 135 }, 136 'loggers': { 137 'django.request': { 138 'handlers': ['file_django', 'console'], 139 'level': 'WARN', 140 'propagate': True, 141 }, 142 'django': { 143 'handlers': ['file_django', 'console'], 144 'level': 'WARNING', 145 'propogate': True, 146 }, 147 'toaster': { 148 'handlers': ['file_toaster'], 149 'level': 'INFO', 150 'propagate': False, 151 }, 152 'api': { 153 'handlers': ['file_api'], 154 'level': 'INFO', 155 'propagate': False, 156 } 157 } 158} 159