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