| #!/usr/bin/env python3 |
| # -*- coding: utf-8 -*- |
| |
| import os |
| import logging |
| import json |
| from pathlib import Path |
| from django.http import HttpRequest |
| |
| BUILDDIR = Path(os.environ.get('BUILDDIR', '/tmp')) |
| |
| def log_api_request(request, response, view, logger_name='api'): |
| """Helper function for LogAPIMixin""" |
| |
| repjson = { |
| 'view': view, |
| 'path': request.path, |
| 'method': request.method, |
| 'status': response.status_code |
| } |
| |
| logger = logging.getLogger(logger_name) |
| logger.info( |
| json.dumps(repjson, indent=4, separators=(", ", " : ")) |
| ) |
| |
| |
| def log_view_mixin(view): |
| def log_view_request(*args, **kwargs): |
| # get request from args else kwargs |
| request = None |
| if len(args) > 0: |
| for req in args: |
| if isinstance(req, HttpRequest): |
| request = req |
| break |
| elif request is None: |
| request = kwargs.get('request') |
| |
| response = view(*args, **kwargs) |
| view_name = 'unknown' |
| if hasattr(request, 'resolver_match'): |
| if hasattr(request.resolver_match, 'view_name'): |
| view_name = request.resolver_match.view_name |
| |
| log_api_request( |
| request, response, view_name, 'toaster') |
| return response |
| return log_view_request |
| |
| |
| |
| class LogAPIMixin: |
| """Logs API requests |
| |
| tested with: |
| - APIView |
| - ModelViewSet |
| - ReadOnlyModelViewSet |
| - GenericAPIView |
| |
| Note: you can set `view_name` attribute in View to override get_view_name() |
| """ |
| |
| def get_view_name(self): |
| if hasattr(self, 'view_name'): |
| return self.view_name |
| return super().get_view_name() |
| |
| def finalize_response(self, request, response, *args, **kwargs): |
| log_api_request(request, response, self.get_view_name()) |
| return super().finalize_response(request, response, *args, **kwargs) |
| |
| |
| LOGGING_SETTINGS = { |
| 'version': 1, |
| 'disable_existing_loggers': False, |
| 'filters': { |
| 'require_debug_false': { |
| '()': 'django.utils.log.RequireDebugFalse' |
| } |
| }, |
| 'formatters': { |
| 'datetime': { |
| 'format': '%(asctime)s %(levelname)s %(message)s' |
| }, |
| 'verbose': { |
| 'format': '{levelname} {asctime} {module} {name}.{funcName} {process:d} {thread:d} {message}', |
| 'datefmt': "%d/%b/%Y %H:%M:%S", |
| 'style': '{', |
| }, |
| 'api': { |
| 'format': '\n{levelname} {asctime} {name}.{funcName}:\n{message}', |
| 'style': '{' |
| } |
| }, |
| 'handlers': { |
| 'mail_admins': { |
| 'level': 'ERROR', |
| 'filters': ['require_debug_false'], |
| 'class': 'django.utils.log.AdminEmailHandler' |
| }, |
| 'console': { |
| 'level': 'DEBUG', |
| 'class': 'logging.StreamHandler', |
| 'formatter': 'datetime', |
| }, |
| 'file_django': { |
| 'level': 'INFO', |
| 'class': 'logging.handlers.TimedRotatingFileHandler', |
| 'filename': BUILDDIR / 'toaster_logs/django.log', |
| 'when': 'D', # interval type |
| 'interval': 1, # defaults to 1 |
| 'backupCount': 10, # how many files to keep |
| 'formatter': 'verbose', |
| }, |
| 'file_api': { |
| 'level': 'INFO', |
| 'class': 'logging.handlers.TimedRotatingFileHandler', |
| 'filename': BUILDDIR / 'toaster_logs/api.log', |
| 'when': 'D', |
| 'interval': 1, |
| 'backupCount': 10, |
| 'formatter': 'verbose', |
| }, |
| 'file_toaster': { |
| 'level': 'INFO', |
| 'class': 'logging.handlers.TimedRotatingFileHandler', |
| 'filename': BUILDDIR / 'toaster_logs/web.log', |
| 'when': 'D', |
| 'interval': 1, |
| 'backupCount': 10, |
| 'formatter': 'verbose', |
| }, |
| }, |
| 'loggers': { |
| 'django.request': { |
| 'handlers': ['file_django', 'console'], |
| 'level': 'WARN', |
| 'propagate': True, |
| }, |
| 'django': { |
| 'handlers': ['file_django', 'console'], |
| 'level': 'WARNING', |
| 'propogate': True, |
| }, |
| 'toaster': { |
| 'handlers': ['file_toaster'], |
| 'level': 'INFO', |
| 'propagate': False, |
| }, |
| 'api': { |
| 'handlers': ['file_api'], |
| 'level': 'INFO', |
| 'propagate': False, |
| } |
| } |
| } |