From: Tim Orling <ticotimo@gmail.com>
To: Alassane Yattara <alassane.yattara@savoirfairelinux.com>
Cc: bitbake-devel@lists.openembedded.org, toaster@lists.yoctoproject.org
Subject: Re: [bitbake-devel] [PATCH] toaster: Monitoring - implement Django logging system
Date: Thu, 19 Oct 2023 07:00:35 -0700 [thread overview]
Message-ID: <CANx9H-AsiPDOTScyii-FmBzWJQterFaE4bZSrSO+iTVkyQiwXg@mail.gmail.com> (raw)
In-Reply-To: <178D4218DDB89A92.29357@lists.openembedded.org>
[-- Attachment #1: Type: text/plain, Size: 18667 bytes --]
Unfortunately, this commit has broken running in a container.
The system will start.
Traceback (most recent call last):
File "/usr/lib/python3.8/logging/config.py", line 563, in configure
handler = self.configure_handler(handlers[name])
File "/usr/lib/python3.8/logging/config.py", line 744, in
configure_handler
result = factory(**kwargs)
File "/usr/lib/python3.8/logging/handlers.py", line 200, in __init__
BaseRotatingHandler.__init__(self, filename, 'a', encoding, delay)
File "/usr/lib/python3.8/logging/handlers.py", line 55, in __init__
logging.FileHandler.__init__(self, filename, mode, encoding, delay)
File "/usr/lib/python3.8/logging/__init__.py", line 1147, in __init__
StreamHandler.__init__(self, self._open())
File "/usr/lib/python3.8/logging/__init__.py", line 1176, in _open
return open(self.baseFilename, self.mode, encoding=self.encoding)
PermissionError: [Errno 13] Permission denied:
'/home/usersetup/poky/bitbake/lib/toaster/logs/api.log'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/usersetup/poky/bitbake/bin/../lib/toaster/manage.py", line
16, in <module>
execute_from_command_line(sys.argv)
File
"/opt/venv/lib/python3.8/site-packages/django/core/management/__init__.py",
line 442, in execute_from_command_line
utility.execute()
File
"/opt/venv/lib/python3.8/site-packages/django/core/management/__init__.py",
line 416, in execute
django.setup()
File "/opt/venv/lib/python3.8/site-packages/django/__init__.py", line 19,
in setup
configure_logging(settings.LOGGING_CONFIG, settings.LOGGING)
File "/opt/venv/lib/python3.8/site-packages/django/utils/log.py", line
76, in configure_logging
logging_config_func(logging_settings)
File "/usr/lib/python3.8/logging/config.py", line 808, in dictConfig
dictConfigClass(config).configure()
File "/usr/lib/python3.8/logging/config.py", line 570, in configure
raise ValueError('Unable to configure handler '
ValueError: Unable to configure handler 'file_api'
The reason is that BASE_DIR is resolving to bitbake/lib/toaster and the
logs are in this case are attempting to write to a read only file system.
When running locally, this works, but perhaps is not where OpenEmbedded
users expect the logs to be:
$ ls bitbake/lib/toaster/logs
api.log toaster.log.2023-10-13 toaster.log.2023-10-16
django.log toaster.log.2023-10-14 toaster.log.2023-10-17
toaster.log toaster.log.2023-10-15
Previously, the logs were written into the build directory, like the
toaster_ui.log still is:
build-toaster-2/toaster_ui.log
This also pointed out an issue with the toaster script:
https://git.yoctoproject.org/poky/tree/bitbake/bin/toaster#n308
When we have a Python trace back, the code is not catching that there was a
failure to fully start nor fail.
e.g. "Successful start." was not output, but neither was "Toaster build
server not started."
The health check in
https://github.com/crops/toaster-container/blob/master/tests/runtests.sh#L91
never sees "Successful start." so unfortunately, the test stage just
eventually times out.
On Wed, Oct 11, 2023 at 9:35 PM Tim Orling via lists.openembedded.org
<ticotimo=gmail.com@lists.openembedded.org> wrote:
> i think this introduces a missing dependency in toaster-requirements.txt
> on django-log-viewer or similar, as there is now a failure on
> https://github.com/crops/toaster-container for "master"
> 03:55:49 E: 0.862 File "<frozen importlib._bootstrap>", line 1014, in
> _gcd_import
> 03:55:49 E: 0.862 File "<frozen importlib._bootstrap>", line 991, in
> _find_and_load
> 03:55:49 E: 0.862 File "<frozen importlib._bootstrap>", line 973, in
> _find_and_load_unlocked
> 03:55:49 E: 0.862 ModuleNotFoundError: No module named 'log_viewer'"
> "
>
>
> On Wed, Oct 4, 2023 at 6:45 AM Alassane Yattara <
> alassane.yattara@savoirfairelinux.com> wrote:
>
>> ---
>> lib/toaster/bldcollector/views.py | 3 +
>> lib/toaster/logs/.gitignore | 1 +
>> lib/toaster/toastergui/views.py | 7 ++
>> lib/toaster/toastergui/widgets.py | 4 +
>> lib/toaster/toastermain/logs.py | 153 ++++++++++++++++++++++++++++
>> lib/toaster/toastermain/settings.py | 66 +++++-------
>> lib/toaster/toastermain/urls.py | 2 +
>> 7 files changed, 198 insertions(+), 38 deletions(-)
>> create mode 100644 lib/toaster/logs/.gitignore
>> create mode 100644 lib/toaster/toastermain/logs.py
>>
>> diff --git a/lib/toaster/bldcollector/views.py
>> b/lib/toaster/bldcollector/views.py
>> index 04cd8b3d..bdf38ae6 100644
>> --- a/lib/toaster/bldcollector/views.py
>> +++ b/lib/toaster/bldcollector/views.py
>> @@ -14,8 +14,11 @@ import subprocess
>> import toastermain
>> from django.views.decorators.csrf import csrf_exempt
>>
>> +from toastermain.logs import log_view_mixin
>> +
>>
>> @csrf_exempt
>> +@log_view_mixin
>> def eventfile(request):
>> """ Receives a file by POST, and runs toaster-eventreply on this
>> file """
>> if request.method != "POST":
>> diff --git a/lib/toaster/logs/.gitignore b/lib/toaster/logs/.gitignore
>> new file mode 100644
>> index 00000000..e5ebf25a
>> --- /dev/null
>> +++ b/lib/toaster/logs/.gitignore
>> @@ -0,0 +1 @@
>> +*.log*
>> diff --git a/lib/toaster/toastergui/views.py
>> b/lib/toaster/toastergui/views.py
>> index 552ff164..cc8517ba 100644
>> --- a/lib/toaster/toastergui/views.py
>> +++ b/lib/toaster/toastergui/views.py
>> @@ -34,6 +34,8 @@ import mimetypes
>>
>> import logging
>>
>> +from toastermain.logs import log_view_mixin
>> +
>> logger = logging.getLogger("toaster")
>>
>> # Project creation and managed build enable
>> @@ -56,6 +58,7 @@ class MimeTypeFinder(object):
>> return guessed_type
>>
>> # single point to add global values into the context before rendering
>> +@log_view_mixin
>> def toaster_render(request, page, context):
>> context['project_enable'] = project_enable
>> context['project_specific'] = is_project_specific
>> @@ -665,6 +668,7 @@ def recipe_packages(request, build_id, recipe_id):
>> return response
>>
>> from django.http import HttpResponse
>> +@log_view_mixin
>> def xhr_dirinfo(request, build_id, target_id):
>> top = request.GET.get('start', '/')
>> return HttpResponse(_get_dir_entries(build_id, target_id, top),
>> content_type = "application/json")
>> @@ -1612,6 +1616,7 @@ if True:
>>
>> from django.views.decorators.csrf import csrf_exempt
>> @csrf_exempt
>> + @log_view_mixin
>> def xhr_testreleasechange(request, pid):
>> def response(data):
>> return HttpResponse(jsonfilter(data),
>> @@ -1648,6 +1653,7 @@ if True:
>> except Exception as e:
>> return response({"error": str(e) })
>>
>> + @log_view_mixin
>> def xhr_configvaredit(request, pid):
>> try:
>> prj = Project.objects.get(id = pid)
>> @@ -1726,6 +1732,7 @@ if True:
>> return HttpResponse(json.dumps({"error":str(e) + "\n" +
>> traceback.format_exc()}), content_type = "application/json")
>>
>>
>> + @log_view_mixin
>> def customrecipe_download(request, pid, recipe_id):
>> recipe = get_object_or_404(CustomImageRecipe, pk=recipe_id)
>>
>> diff --git a/lib/toaster/toastergui/widgets.py
>> b/lib/toaster/toastergui/widgets.py
>> index 53696912..51ed153a 100644
>> --- a/lib/toaster/toastergui/widgets.py
>> +++ b/lib/toaster/toastergui/widgets.py
>> @@ -32,6 +32,7 @@ import re
>> import os
>>
>> from toastergui.tablefilter import TableFilterMap
>> +from toastermain.logs import log_view_mixin
>>
>> try:
>> from urllib import unquote_plus
>> @@ -84,6 +85,7 @@ class ToasterTable(TemplateView):
>>
>> return context
>>
>> + @log_view_mixin
>> def get(self, request, *args, **kwargs):
>> if request.GET.get('format', None) == 'json':
>>
>> @@ -414,6 +416,7 @@ class ToasterTypeAhead(View):
>> def __init__(self, *args, **kwargs):
>> super(ToasterTypeAhead, self).__init__()
>>
>> + @log_view_mixin
>> def get(self, request, *args, **kwargs):
>> def response(data):
>> return HttpResponse(json.dumps(data,
>> @@ -469,6 +472,7 @@ class MostRecentBuildsView(View):
>>
>> return False
>>
>> + @log_view_mixin
>> def get(self, request, *args, **kwargs):
>> """
>> Returns a list of builds in JSON format.
>> diff --git a/lib/toaster/toastermain/logs.py
>> b/lib/toaster/toastermain/logs.py
>> new file mode 100644
>> index 00000000..f9953982
>> --- /dev/null
>> +++ b/lib/toaster/toastermain/logs.py
>> @@ -0,0 +1,153 @@
>> +#!/usr/bin/env python3
>> +# -*- coding: utf-8 -*-
>> +
>> +import logging
>> +import json
>> +from pathlib import Path
>> +from django.http import HttpRequest
>> +
>> +BASE_DIR = Path(__file__).resolve(strict=True).parent.parent
>> +
>> +
>> +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)
>> + log_api_request(
>> + request, response, request.resolver_match.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': BASE_DIR / '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': BASE_DIR / 'logs/api.log',
>> + 'when': 'D',
>> + 'interval': 1,
>> + 'backupCount': 10,
>> + 'formatter': 'verbose',
>> + },
>> + 'file_toaster': {
>> + 'level': 'INFO',
>> + 'class': 'logging.handlers.TimedRotatingFileHandler',
>> + 'filename': BASE_DIR / 'logs/toaster.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,
>> + }
>> + }
>> +}
>> diff --git a/lib/toaster/toastermain/settings.py
>> b/lib/toaster/toastermain/settings.py
>> index 609c85d9..b083cf58 100644
>> --- a/lib/toaster/toastermain/settings.py
>> +++ b/lib/toaster/toastermain/settings.py
>> @@ -9,6 +9,8 @@
>> # Django settings for Toaster project.
>>
>> import os
>> +from pathlib import Path
>> +from toastermain.logs import LOGGING_SETTINGS
>>
>> DEBUG = True
>>
>> @@ -186,7 +188,13 @@ TEMPLATES = [
>> 'django.template.loaders.app_directories.Loader',
>> #'django.template.loaders.eggs.Loader',
>> ],
>> - 'string_if_invalid': InvalidString("%s"),
>> + #
>> https://docs.djangoproject.com/en/4.2/ref/templates/api/#how-invalid-variables-are-handled
>> + # Generally, string_if_invalid should only be enabled in
>> order to debug
>> + # a specific template problem, then cleared once debugging
>> is complete.
>> + # If you assign a value other than '' to string_if_invalid,
>> + # you will experience rendering problems with these
>> templates and sites.
>> + # 'string_if_invalid': InvalidString("%s"),
>> + 'string_if_invalid': "",
>> 'debug': DEBUG,
>> },
>> },
>> @@ -242,6 +250,9 @@ INSTALLED_APPS = (
>> 'django.contrib.humanize',
>> 'bldcollector',
>> 'toastermain',
>> +
>> + # 3rd-lib
>> + "log_viewer",
>> )
>>
>>
>> @@ -302,43 +313,22 @@ for t in os.walk(os.path.dirname(currentdir)):
>> # the site admins on every HTTP 500 error when DEBUG=False.
>> # See http://docs.djangoproject.com/en/dev/topics/logging for
>> # more details on how to customize your logging configuration.
>> -LOGGING = {
>> - 'version': 1,
>> - 'disable_existing_loggers': False,
>> - 'filters': {
>> - 'require_debug_false': {
>> - '()': 'django.utils.log.RequireDebugFalse'
>> - }
>> - },
>> - 'formatters': {
>> - 'datetime': {
>> - 'format': '%(asctime)s %(levelname)s %(message)s'
>> - }
>> - },
>> - 'handlers': {
>> - 'mail_admins': {
>> - 'level': 'ERROR',
>> - 'filters': ['require_debug_false'],
>> - 'class': 'django.utils.log.AdminEmailHandler'
>> - },
>> - 'console': {
>> - 'level': 'DEBUG',
>> - 'class': 'logging.StreamHandler',
>> - 'formatter': 'datetime',
>> - }
>> - },
>> - 'loggers': {
>> - 'toaster' : {
>> - 'handlers': ['console'],
>> - 'level': 'DEBUG',
>> - },
>> - 'django.request': {
>> - 'handlers': ['console'],
>> - 'level': 'WARN',
>> - 'propagate': True,
>> - },
>> - }
>> -}
>> +LOGGING = LOGGING_SETTINGS
>> +
>> +# Build paths inside the project like this: BASE_DIR / 'subdir'.
>> +BASE_DIR = Path(__file__).resolve(strict=True).parent.parent
>> +
>> +# LOG VIEWER
>> +# https://pypi.org/project/django-log-viewer/
>> +LOG_VIEWER_FILES_PATTERN = '*.log*'
>> +LOG_VIEWER_FILES_DIR = os.path.join(BASE_DIR, 'logs')
>> +LOG_VIEWER_PAGE_LENGTH = 25 # total log lines per-page
>> +LOG_VIEWER_MAX_READ_LINES = 100000 # total log lines will be read
>> +LOG_VIEWER_PATTERNS = ['INFO', 'DEBUG', 'WARNING', 'ERROR', 'CRITICAL']
>> +
>> +# Optionally you can set the next variables in order to customize the
>> admin:
>> +LOG_VIEWER_FILE_LIST_TITLE = "Logs list"
>> +
>>
>> if DEBUG and SQL_DEBUG:
>> LOGGING['loggers']['django.db.backends'] = {
>> diff --git a/lib/toaster/toastermain/urls.py
>> b/lib/toaster/toastermain/urls.py
>> index 03603026..3be46fcf 100644
>> --- a/lib/toaster/toastermain/urls.py
>> +++ b/lib/toaster/toastermain/urls.py
>> @@ -28,6 +28,8 @@ urlpatterns = [
>> # url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
>>
>>
>> + url(r'^logs/', include('log_viewer.urls')),
>> +
>> # This is here to maintain backward compatibility and will be
>> deprecated
>> # in the future.
>> url(r'^orm/eventfile$', bldcollector.views.eventfile),
>> --
>> 2.34.1
>>
>>
>>
>>
>>
> -=-=-=-=-=-=-=-=-=-=-=-
> Links: You receive all messages sent to this group.
> View/Reply Online (#15212):
> https://lists.openembedded.org/g/bitbake-devel/message/15212
> Mute This Topic: https://lists.openembedded.org/mt/101755228/924729
> Group Owner: bitbake-devel+owner@lists.openembedded.org
> Unsubscribe: https://lists.openembedded.org/g/bitbake-devel/unsub [
> ticotimo@gmail.com]
> -=-=-=-=-=-=-=-=-=-=-=-
>
>
[-- Attachment #2: Type: text/html, Size: 24410 bytes --]
next parent reply other threads:[~2023-10-19 14:00 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
[not found] <20231004134415.11070-1-alassane.yattara@savoirfairelinux.com>
[not found] ` <178D4218DDB89A92.29357@lists.openembedded.org>
2023-10-19 14:00 ` Tim Orling [this message]
2023-10-19 16:51 ` [Toaster] [bitbake-devel] [PATCH] toaster: Monitoring - implement Django logging system Alassane Yattara
2023-10-19 17:12 ` Tim Orling
[not found] ` <178F917CE0158B76.20272@lists.yoctoproject.org>
2023-10-19 18:01 ` Tim Orling
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=CANx9H-AsiPDOTScyii-FmBzWJQterFaE4bZSrSO+iTVkyQiwXg@mail.gmail.com \
--to=ticotimo@gmail.com \
--cc=alassane.yattara@savoirfairelinux.com \
--cc=bitbake-devel@lists.openembedded.org \
--cc=toaster@lists.yoctoproject.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).