diff options
Diffstat (limited to 'bitbake/lib/toaster/toastermain')
6 files changed, 206 insertions, 53 deletions
diff --git a/bitbake/lib/toaster/toastermain/logs.py b/bitbake/lib/toaster/toastermain/logs.py new file mode 100644 index 0000000000..62d871963a --- /dev/null +++ b/bitbake/lib/toaster/toastermain/logs.py | |||
@@ -0,0 +1,158 @@ | |||
1 | #!/usr/bin/env python3 | ||
2 | # -*- coding: utf-8 -*- | ||
3 | |||
4 | import os | ||
5 | import logging | ||
6 | import json | ||
7 | from pathlib import Path | ||
8 | from django.http import HttpRequest | ||
9 | |||
10 | BUILDDIR = Path(os.environ.get('BUILDDIR', '/tmp')) | ||
11 | |||
12 | def 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 | |||
28 | def 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 | |||
53 | class 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 | |||
75 | LOGGING_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 | } | ||
diff --git a/bitbake/lib/toaster/toastermain/management/commands/buildimport.py b/bitbake/lib/toaster/toastermain/management/commands/buildimport.py index 59da6ff7ac..f7139aa041 100644 --- a/bitbake/lib/toaster/toastermain/management/commands/buildimport.py +++ b/bitbake/lib/toaster/toastermain/management/commands/buildimport.py | |||
@@ -451,7 +451,7 @@ class Command(BaseCommand): | |||
451 | # Catch vars relevant to Toaster (in case no Toaster section) | 451 | # Catch vars relevant to Toaster (in case no Toaster section) |
452 | self.update_project_vars(project,'DISTRO') | 452 | self.update_project_vars(project,'DISTRO') |
453 | self.update_project_vars(project,'MACHINE') | 453 | self.update_project_vars(project,'MACHINE') |
454 | self.update_project_vars(project,'IMAGE_INSTALL_append') | 454 | self.update_project_vars(project,'IMAGE_INSTALL:append') |
455 | self.update_project_vars(project,'IMAGE_FSTYPES') | 455 | self.update_project_vars(project,'IMAGE_FSTYPES') |
456 | self.update_project_vars(project,'PACKAGE_CLASSES') | 456 | self.update_project_vars(project,'PACKAGE_CLASSES') |
457 | # These vars are typically only assigned by Toaster | 457 | # These vars are typically only assigned by Toaster |
@@ -545,7 +545,7 @@ class Command(BaseCommand): | |||
545 | # Find the directory's release, and promote to default_release if local paths | 545 | # Find the directory's release, and promote to default_release if local paths |
546 | release = self.find_import_release(layers_list,lv_dict,default_release) | 546 | release = self.find_import_release(layers_list,lv_dict,default_release) |
547 | # create project, SANITY: reuse any project of same name | 547 | # create project, SANITY: reuse any project of same name |
548 | project = Project.objects.create_project(project_name,release,project) | 548 | project = Project.objects.create_project(project_name,release,project, imported=True) |
549 | # Apply any new layers or variables | 549 | # Apply any new layers or variables |
550 | self.apply_conf_variables(project,layers_list,lv_dict,release) | 550 | self.apply_conf_variables(project,layers_list,lv_dict,release) |
551 | # WORKAROUND: since we now derive the release, redirect 'newproject_specific' to 'project_specific' | 551 | # WORKAROUND: since we now derive the release, redirect 'newproject_specific' to 'project_specific' |
diff --git a/bitbake/lib/toaster/toastermain/management/commands/checksocket.py b/bitbake/lib/toaster/toastermain/management/commands/checksocket.py index 811fd5d516..b2c002da7a 100644 --- a/bitbake/lib/toaster/toastermain/management/commands/checksocket.py +++ b/bitbake/lib/toaster/toastermain/management/commands/checksocket.py | |||
@@ -13,7 +13,7 @@ import errno | |||
13 | import socket | 13 | import socket |
14 | 14 | ||
15 | from django.core.management.base import BaseCommand, CommandError | 15 | from django.core.management.base import BaseCommand, CommandError |
16 | from django.utils.encoding import force_text | 16 | from django.utils.encoding import force_str |
17 | 17 | ||
18 | DEFAULT_ADDRPORT = "0.0.0.0:8000" | 18 | DEFAULT_ADDRPORT = "0.0.0.0:8000" |
19 | 19 | ||
@@ -51,7 +51,7 @@ class Command(BaseCommand): | |||
51 | if hasattr(err, 'errno') and err.errno in errors: | 51 | if hasattr(err, 'errno') and err.errno in errors: |
52 | errtext = errors[err.errno] | 52 | errtext = errors[err.errno] |
53 | else: | 53 | else: |
54 | errtext = force_text(err) | 54 | errtext = force_str(err) |
55 | raise CommandError(errtext) | 55 | raise CommandError(errtext) |
56 | 56 | ||
57 | self.stdout.write("OK") | 57 | self.stdout.write("OK") |
diff --git a/bitbake/lib/toaster/toastermain/settings.py b/bitbake/lib/toaster/toastermain/settings.py index a4b370c8d4..e06adc5a93 100644 --- a/bitbake/lib/toaster/toastermain/settings.py +++ b/bitbake/lib/toaster/toastermain/settings.py | |||
@@ -9,6 +9,8 @@ | |||
9 | # Django settings for Toaster project. | 9 | # Django settings for Toaster project. |
10 | 10 | ||
11 | import os | 11 | import os |
12 | from pathlib import Path | ||
13 | from toastermain.logs import LOGGING_SETTINGS | ||
12 | 14 | ||
13 | DEBUG = True | 15 | DEBUG = True |
14 | 16 | ||
@@ -39,6 +41,9 @@ DATABASES = { | |||
39 | } | 41 | } |
40 | } | 42 | } |
41 | 43 | ||
44 | # New in Django 3.2 | ||
45 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' | ||
46 | |||
42 | # Needed when Using sqlite especially to add a longer timeout for waiting | 47 | # Needed when Using sqlite especially to add a longer timeout for waiting |
43 | # for the database lock to be released | 48 | # for the database lock to be released |
44 | # https://docs.djangoproject.com/en/1.6/ref/databases/#database-is-locked-errors | 49 | # https://docs.djangoproject.com/en/1.6/ref/databases/#database-is-locked-errors |
@@ -84,14 +89,17 @@ else: | |||
84 | from pytz.exceptions import UnknownTimeZoneError | 89 | from pytz.exceptions import UnknownTimeZoneError |
85 | try: | 90 | try: |
86 | if pytz.timezone(zonename) is not None: | 91 | if pytz.timezone(zonename) is not None: |
87 | zonefilelist[hashlib.md5(open(filepath, 'rb').read()).hexdigest()] = zonename | 92 | with open(filepath, 'rb') as f: |
93 | zonefilelist[hashlib.md5(f.read()).hexdigest()] = zonename | ||
88 | except UnknownTimeZoneError as ValueError: | 94 | except UnknownTimeZoneError as ValueError: |
89 | # we expect timezone failures here, just move over | 95 | # we expect timezone failures here, just move over |
90 | pass | 96 | pass |
91 | except ImportError: | 97 | except ImportError: |
92 | zonefilelist[hashlib.md5(open(filepath, 'rb').read()).hexdigest()] = zonename | 98 | with open(filepath, 'rb') as f: |
99 | zonefilelist[hashlib.md5(f.read()).hexdigest()] = zonename | ||
93 | 100 | ||
94 | TIME_ZONE = zonefilelist[hashlib.md5(open('/etc/localtime', 'rb').read()).hexdigest()] | 101 | with open('/etc/localtime', 'rb') as f: |
102 | TIME_ZONE = zonefilelist[hashlib.md5(f.read()).hexdigest()] | ||
95 | 103 | ||
96 | # Language code for this installation. All choices can be found here: | 104 | # Language code for this installation. All choices can be found here: |
97 | # http://www.i18nguy.com/unicode/language-identifiers.html | 105 | # http://www.i18nguy.com/unicode/language-identifiers.html |
@@ -103,10 +111,6 @@ SITE_ID = 1 | |||
103 | # to load the internationalization machinery. | 111 | # to load the internationalization machinery. |
104 | USE_I18N = True | 112 | USE_I18N = True |
105 | 113 | ||
106 | # If you set this to False, Django will not format dates, numbers and | ||
107 | # calendars according to the current locale. | ||
108 | USE_L10N = True | ||
109 | |||
110 | # If you set this to False, Django will not use timezone-aware datetimes. | 114 | # If you set this to False, Django will not use timezone-aware datetimes. |
111 | USE_TZ = True | 115 | USE_TZ = True |
112 | 116 | ||
@@ -147,6 +151,8 @@ STATICFILES_FINDERS = ( | |||
147 | # Make this unique, and don't share it with anybody. | 151 | # Make this unique, and don't share it with anybody. |
148 | SECRET_KEY = 'NOT_SUITABLE_FOR_HOSTED_DEPLOYMENT' | 152 | SECRET_KEY = 'NOT_SUITABLE_FOR_HOSTED_DEPLOYMENT' |
149 | 153 | ||
154 | TMPDIR = os.environ.get('TOASTER_DJANGO_TMPDIR', '/tmp') | ||
155 | |||
150 | class InvalidString(str): | 156 | class InvalidString(str): |
151 | def __mod__(self, other): | 157 | def __mod__(self, other): |
152 | from django.template.base import TemplateSyntaxError | 158 | from django.template.base import TemplateSyntaxError |
@@ -183,7 +189,13 @@ TEMPLATES = [ | |||
183 | 'django.template.loaders.app_directories.Loader', | 189 | 'django.template.loaders.app_directories.Loader', |
184 | #'django.template.loaders.eggs.Loader', | 190 | #'django.template.loaders.eggs.Loader', |
185 | ], | 191 | ], |
186 | 'string_if_invalid': InvalidString("%s"), | 192 | # https://docs.djangoproject.com/en/4.2/ref/templates/api/#how-invalid-variables-are-handled |
193 | # Generally, string_if_invalid should only be enabled in order to debug | ||
194 | # a specific template problem, then cleared once debugging is complete. | ||
195 | # If you assign a value other than '' to string_if_invalid, | ||
196 | # you will experience rendering problems with these templates and sites. | ||
197 | # 'string_if_invalid': InvalidString("%s"), | ||
198 | 'string_if_invalid': "", | ||
187 | 'debug': DEBUG, | 199 | 'debug': DEBUG, |
188 | }, | 200 | }, |
189 | }, | 201 | }, |
@@ -207,7 +219,7 @@ CACHES = { | |||
207 | # }, | 219 | # }, |
208 | 'default': { | 220 | 'default': { |
209 | 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', | 221 | 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', |
210 | 'LOCATION': '/tmp/toaster_cache_%d' % os.getuid(), | 222 | 'LOCATION': '%s/toaster_cache_%d' % (TMPDIR, os.getuid()), |
211 | 'TIMEOUT': 1, | 223 | 'TIMEOUT': 1, |
212 | } | 224 | } |
213 | } | 225 | } |
@@ -239,6 +251,9 @@ INSTALLED_APPS = ( | |||
239 | 'django.contrib.humanize', | 251 | 'django.contrib.humanize', |
240 | 'bldcollector', | 252 | 'bldcollector', |
241 | 'toastermain', | 253 | 'toastermain', |
254 | |||
255 | # 3rd-lib | ||
256 | "log_viewer", | ||
242 | ) | 257 | ) |
243 | 258 | ||
244 | 259 | ||
@@ -299,43 +314,21 @@ for t in os.walk(os.path.dirname(currentdir)): | |||
299 | # the site admins on every HTTP 500 error when DEBUG=False. | 314 | # the site admins on every HTTP 500 error when DEBUG=False. |
300 | # See http://docs.djangoproject.com/en/dev/topics/logging for | 315 | # See http://docs.djangoproject.com/en/dev/topics/logging for |
301 | # more details on how to customize your logging configuration. | 316 | # more details on how to customize your logging configuration. |
302 | LOGGING = { | 317 | LOGGING = LOGGING_SETTINGS |
303 | 'version': 1, | 318 | |
304 | 'disable_existing_loggers': False, | 319 | # Build paths inside the project like this: BASE_DIR / 'subdir'. |
305 | 'filters': { | 320 | BUILDDIR = os.environ.get("BUILDDIR", TMPDIR) |
306 | 'require_debug_false': { | 321 | |
307 | '()': 'django.utils.log.RequireDebugFalse' | 322 | # LOG VIEWER |
308 | } | 323 | # https://pypi.org/project/django-log-viewer/ |
309 | }, | 324 | LOG_VIEWER_FILES_PATTERN = '*.log*' |
310 | 'formatters': { | 325 | LOG_VIEWER_FILES_DIR = os.path.join(BUILDDIR, "toaster_logs/") |
311 | 'datetime': { | 326 | LOG_VIEWER_PAGE_LENGTH = 25 # total log lines per-page |
312 | 'format': '%(asctime)s %(levelname)s %(message)s' | 327 | LOG_VIEWER_MAX_READ_LINES = 100000 # total log lines will be read |
313 | } | 328 | LOG_VIEWER_PATTERNS = ['INFO', 'DEBUG', 'WARNING', 'ERROR', 'CRITICAL'] |
314 | }, | 329 | |
315 | 'handlers': { | 330 | # Optionally you can set the next variables in order to customize the admin: |
316 | 'mail_admins': { | 331 | LOG_VIEWER_FILE_LIST_TITLE = "Logs list" |
317 | 'level': 'ERROR', | ||
318 | 'filters': ['require_debug_false'], | ||
319 | 'class': 'django.utils.log.AdminEmailHandler' | ||
320 | }, | ||
321 | 'console': { | ||
322 | 'level': 'DEBUG', | ||
323 | 'class': 'logging.StreamHandler', | ||
324 | 'formatter': 'datetime', | ||
325 | } | ||
326 | }, | ||
327 | 'loggers': { | ||
328 | 'toaster' : { | ||
329 | 'handlers': ['console'], | ||
330 | 'level': 'DEBUG', | ||
331 | }, | ||
332 | 'django.request': { | ||
333 | 'handlers': ['console'], | ||
334 | 'level': 'WARN', | ||
335 | 'propagate': True, | ||
336 | }, | ||
337 | } | ||
338 | } | ||
339 | 332 | ||
340 | if DEBUG and SQL_DEBUG: | 333 | if DEBUG and SQL_DEBUG: |
341 | LOGGING['loggers']['django.db.backends'] = { | 334 | LOGGING['loggers']['django.db.backends'] = { |
diff --git a/bitbake/lib/toaster/toastermain/settings_test.py b/bitbake/lib/toaster/toastermain/settings_test.py index 6538d9e453..74def2d240 100644 --- a/bitbake/lib/toaster/toastermain/settings_test.py +++ b/bitbake/lib/toaster/toastermain/settings_test.py | |||
@@ -19,10 +19,10 @@ TEMPLATE_DEBUG = DEBUG | |||
19 | DATABASES = { | 19 | DATABASES = { |
20 | 'default': { | 20 | 'default': { |
21 | 'ENGINE': 'django.db.backends.sqlite3', | 21 | 'ENGINE': 'django.db.backends.sqlite3', |
22 | 'NAME': '/tmp/toaster-test-db.sqlite', | 22 | 'NAME': '%s/toaster-test-db.sqlite' % TMPDIR, |
23 | 'TEST': { | 23 | 'TEST': { |
24 | 'ENGINE': 'django.db.backends.sqlite3', | 24 | 'ENGINE': 'django.db.backends.sqlite3', |
25 | 'NAME': '/tmp/toaster-test-db.sqlite', | 25 | 'NAME': '%s/toaster-test-db.sqlite' % TMPDIR, |
26 | } | 26 | } |
27 | } | 27 | } |
28 | } | 28 | } |
diff --git a/bitbake/lib/toaster/toastermain/urls.py b/bitbake/lib/toaster/toastermain/urls.py index 5fb520b384..3be46fcf0c 100644 --- a/bitbake/lib/toaster/toastermain/urls.py +++ b/bitbake/lib/toaster/toastermain/urls.py | |||
@@ -6,7 +6,7 @@ | |||
6 | # SPDX-License-Identifier: GPL-2.0-only | 6 | # SPDX-License-Identifier: GPL-2.0-only |
7 | # | 7 | # |
8 | 8 | ||
9 | from django.conf.urls import include, url | 9 | from django.urls import re_path as url, include |
10 | from django.views.generic import RedirectView, TemplateView | 10 | from django.views.generic import RedirectView, TemplateView |
11 | from django.views.decorators.cache import never_cache | 11 | from django.views.decorators.cache import never_cache |
12 | import bldcollector.views | 12 | import bldcollector.views |
@@ -28,6 +28,8 @@ urlpatterns = [ | |||
28 | # url(r'^admin/doc/', include('django.contrib.admindocs.urls')), | 28 | # url(r'^admin/doc/', include('django.contrib.admindocs.urls')), |
29 | 29 | ||
30 | 30 | ||
31 | url(r'^logs/', include('log_viewer.urls')), | ||
32 | |||
31 | # This is here to maintain backward compatibility and will be deprecated | 33 | # This is here to maintain backward compatibility and will be deprecated |
32 | # in the future. | 34 | # in the future. |
33 | url(r'^orm/eventfile$', bldcollector.views.eventfile), | 35 | url(r'^orm/eventfile$', bldcollector.views.eventfile), |