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

1import json 

2import logging 

3import os 

4 

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 

12 

13BAD_REQUEST_HANDLER_NAME_NOT_PROVIDED = HttpResponseBadRequest("400 Bad Request: no handler name provided") 

14 

15file_handlers = { 

16 "logging.FileHandler", 

17 "logging.handlers.RotatingFileHandler", 

18 "logging.handlers.TimedRotatingFileHandler", 

19 "logging.handlers.WatchedFileHandler", 

20} 

21 

22client_logger = logging.getLogger("django_log_lens.client") 

23 

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} 

32 

33 

34@require_http_methods(["POST"]) 

35def logout_view(request): 

36 if request.user.is_authenticated: 

37 logout(request) 

38 return redirect('log-lens:login') 

39 

40 

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') 

56 

57 

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.") 

75 

76 

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") 

96 

97 

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"}) 

120 

121 

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"}) 

139 

140 

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") 

160 

161 

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({}) 

180 

181 

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')