Coverage for django_log_lens / views.py: 85%
117 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-15 19:39 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-15 19:39 +0000
1import json
2import logging
3import os
5from django.conf import settings
6from django.contrib.auth import authenticate, login, logout
7from django.contrib.auth.decorators import user_passes_test
8from django.http import (HttpResponse, HttpResponseBadRequest,
9 HttpResponseForbidden, JsonResponse)
10from django.shortcuts import redirect, render
11from django.views.decorators.http import require_http_methods
13BAD_REQUEST_HANDLER_NAME_NOT_PROVIDED = HttpResponseBadRequest("400 Bad Request: no handler name provided")
15file_handlers = {
16 "logging.FileHandler",
17 "logging.handlers.RotatingFileHandler",
18 "logging.handlers.TimedRotatingFileHandler",
19 "logging.handlers.WatchedFileHandler",
20}
22client_logger = logging.getLogger("django_log_lens.client")
24log_level_functional_map = {
25 "DEBUG": client_logger.debug,
26 "INFO": client_logger.info,
27 "WARNING": client_logger.warning,
28 "ERROR": client_logger.error,
29 "CRITICAL": client_logger.critical,
30 "ASSERTION FAILED (CRITICAL)": client_logger.critical
31}
34@require_http_methods(["POST"])
35def logout_view(request):
36 if request.user.is_authenticated:
37 logout(request)
38 return redirect('log-lens:login')
41@require_http_methods(["GET", "POST"])
42def login_view(request):
43 if request.method == 'POST':
44 username = request.POST.get('username', '')
45 password = request.POST.get('password', '')
46 user = authenticate(username=username, password=password)
47 if user is not None and user.is_superuser: # type: ignore
48 login(request, user)
49 return redirect('log-lens:view')
50 elif user is not None:
51 return render(request, 'log-lens-login.html', {'error_message': f'{username} is not a superuser'})
52 else:
53 return render(request, 'log-lens-login.html', {'error_message': 'Invalid credentials'})
54 else:
55 return render(request, 'log-lens-login.html')
58@require_http_methods(["POST"])
59def log_js_error(request):
60 """
61 Logs the error message and stack trace provided by the POST request.
62 Only for development purposes, don't use in production.
63 """
64 try:
65 allow_js_logging = settings.ALLOW_JS_LOGGING
66 except AttributeError:
67 allow_js_logging = False
68 if not allow_js_logging:
69 return HttpResponseForbidden("Client logger is disabled.")
70 log = json.loads(request.body.decode('utf-8'))
71 log_message = log['log_message']
72 log_level = log['severity']
73 log_level_functional_map.get(log_level, client_logger.error)(log_message)
74 return HttpResponse("Log message processed.")
77@require_http_methods(["GET"])
78@user_passes_test(lambda user: user.is_superuser, login_url='log-lens:login')
79def download_logfile(request) -> HttpResponse:
80 """
81 Allows downloading the log file associated with the handler_name
82 defined in the query string.
83 A logged in superuser is required.
84 """
85 handler_name = request.GET.get('handler_name', None)
86 if handler_name is None:
87 return BAD_REQUEST_HANDLER_NAME_NOT_PROVIDED
88 try:
89 filename = settings.LOGGING['handlers'][handler_name]['filename']
90 with open(filename, 'r') as f:
91 response = HttpResponse(f.read(), content_type='text/plain')
92 response['Content-Disposition'] = f'attachment; filename="{os.path.basename(filename)}"'
93 return response
94 except (FileNotFoundError, KeyError):
95 return HttpResponse("No logs available")
98@require_http_methods(["GET"])
99@user_passes_test(lambda user: user.is_superuser, login_url='log-lens:login')
100def request_logfile(request) -> HttpResponse:
101 """
102 Returns the contents of the log file associated with the handler_name
103 defined in the query string.
104 A logged in superuser is required.
105 """
106 handler_name = request.GET.get('handler_name', None)
107 if handler_name is None:
108 return BAD_REQUEST_HANDLER_NAME_NOT_PROVIDED
109 try:
110 filename = settings.LOGGING['handlers'][handler_name]['filename']
111 with open(filename, 'r') as f:
112 ti_m = os.path.getmtime(filename)
113 return JsonResponse({"text": f.read(), "timestamp": f"{ti_m}"})
114 except FileNotFoundError:
115 return JsonResponse({"text": f"Log file {filename} not found", "timestamp": "0"})
116 except KeyError:
117 return JsonResponse(
118 {"text": "Improperly configured.\nPlease check the LOGGING configuration"
119 " in your settings.py", "timestamp": "0"})
122@require_http_methods(["GET"])
123@user_passes_test(lambda user: user.is_superuser, login_url='log-lens:login')
124def request_logfile_timestamp(request) -> HttpResponse:
125 """
126 Returns the timestamp of the logfile associated with the handler_name
127 defined in the query string.
128 A logged in superuser is required.
129 """
130 handler_name = request.GET.get('handler_name', None)
131 if handler_name is None:
132 return BAD_REQUEST_HANDLER_NAME_NOT_PROVIDED
133 try:
134 filename = settings.LOGGING['handlers'][handler_name]['filename']
135 ti_m = os.path.getmtime(filename)
136 return JsonResponse({"timestamp": f"{ti_m}"})
137 except (FileNotFoundError, KeyError):
138 return JsonResponse({"timestamp": "0"})
141@require_http_methods(["DELETE"])
142@user_passes_test(lambda user: user.is_superuser, login_url='log-lens:login')
143def clear_logfile(request) -> HttpResponse:
144 """
145 Clears the contents of the log file specified by the handler_name GET parameter.
146 A logged in superuser is required.
147 """
148 handler_name = request.GET.get('handler_name', None)
149 if handler_name is None:
150 return BAD_REQUEST_HANDLER_NAME_NOT_PROVIDED
151 filename = settings.LOGGING.get('handlers', {}).get(handler_name, {}).get('filename')
152 if filename is None:
153 return HttpResponseBadRequest("400 Bad Request: invalid handler name provided")
154 try:
155 with open(filename, 'w') as f:
156 f.write("")
157 return HttpResponse(f"Log file {filename} cleared")
158 except FileNotFoundError:
159 return HttpResponse("No logs available")
162@require_http_methods(["GET"])
163@user_passes_test(lambda user: user.is_superuser, login_url='log-lens:login')
164def request_logfile_paths(request) -> JsonResponse | HttpResponseBadRequest:
165 """
166 Returns a JSON object containing the paths of all log files
167 associated with any file handlers defined in the LOGGING configuration.
168 A logged in superuser is required.
169 """
170 try:
171 paths = {}
172 for handler_name in settings.LOGGING['handlers']:
173 has_filename = 'filename' in settings.LOGGING['handlers'][handler_name]
174 is_file_handler = settings.LOGGING['handlers'][handler_name]['class'] in file_handlers
175 if has_filename and is_file_handler:
176 paths[handler_name] = settings.LOGGING['handlers'][handler_name]['filename']
177 return JsonResponse(paths)
178 except KeyError:
179 return JsonResponse({})
182@require_http_methods(["GET"])
183@user_passes_test(lambda user: user.is_superuser, login_url='log-lens:login')
184def log_lens_view(request) -> HttpResponse:
185 """
186 Returns the log viewer page.
187 A logged in superuser is required.
188 """
189 return render(request, 'log-lens.html')