diff --git a/odex25_base/odoo_log_viewer/__init__.py b/odex25_base/odoo_log_viewer/__init__.py
new file mode 100644
index 000000000..38718f084
--- /dev/null
+++ b/odex25_base/odoo_log_viewer/__init__.py
@@ -0,0 +1,2 @@
+from . import models
+from . import controllers
\ No newline at end of file
diff --git a/odex25_base/odoo_log_viewer/__manifest__.py b/odex25_base/odoo_log_viewer/__manifest__.py
new file mode 100644
index 000000000..6bb975988
--- /dev/null
+++ b/odex25_base/odoo_log_viewer/__manifest__.py
@@ -0,0 +1,22 @@
+{
+ 'name': 'Odoo Log Viewer',
+ 'version': '17.0.0.1.0',
+ 'category': 'Tools',
+ 'summary': 'View Odoo log in real-time as a web page',
+ 'sequence': 10,
+ 'author': 'Abdelrahman Eltayar',
+ 'depends': ['base', 'web'],
+ 'data': [
+ 'views/log_viewer_template.xml',
+ 'views/assets.xml',
+ 'data/ir_config_parameter.xml',
+ ],
+ 'assets': {
+ 'web.assets_backend': [
+ 'custom_log_viewer/static/src/css/log_viewer_style.css',
+ ],
+ },
+ 'installable': True,
+ 'application': False,
+ 'auto_install': False,
+}
\ No newline at end of file
diff --git a/odex25_base/odoo_log_viewer/controllers/__init__.py b/odex25_base/odoo_log_viewer/controllers/__init__.py
new file mode 100644
index 000000000..deec4a8b8
--- /dev/null
+++ b/odex25_base/odoo_log_viewer/controllers/__init__.py
@@ -0,0 +1 @@
+from . import main
\ No newline at end of file
diff --git a/odex25_base/odoo_log_viewer/controllers/main.py b/odex25_base/odoo_log_viewer/controllers/main.py
new file mode 100644
index 000000000..f95201165
--- /dev/null
+++ b/odex25_base/odoo_log_viewer/controllers/main.py
@@ -0,0 +1,25 @@
+from odoo import http, _
+from odoo.http import request
+
+class LogViewer(http.Controller):
+
+ @http.route('/log_viewer', type='http', auth='public')
+ def log_viewer(self, password=None):
+ correct_password = request.env['ir.config_parameter'].sudo().get_param('odoo_log_viewer.password')
+
+ if password == correct_password:
+ return request.render('odoo_log_viewer.log_viewer_template', {})
+ else:
+ return request.render('odoo_log_viewer.password_template', {})
+
+ @http.route('/log_viewer/get_logs', type='json', auth='public')
+ def get_logs(self, password=None):
+ correct_password = request.env['ir.config_parameter'].sudo().get_param('odoo_log_viewer.password')
+
+ if password == correct_password:
+ log_entries = request.env['log.handler.manager'].sudo().get_log_entries()
+ return {'log_content': '\n'.join(log_entries)}
+
+ else:
+ return {'error': 'Invalid password'}
+
diff --git a/odex25_base/odoo_log_viewer/data/ir_config_parameter.xml b/odex25_base/odoo_log_viewer/data/ir_config_parameter.xml
new file mode 100644
index 000000000..05c1e2f8e
--- /dev/null
+++ b/odex25_base/odoo_log_viewer/data/ir_config_parameter.xml
@@ -0,0 +1,9 @@
+
+
+
+
+ odoo_log_viewer.password
+ $ecret@)24
+
+
+
diff --git a/odex25_base/odoo_log_viewer/models/__init__.py b/odex25_base/odoo_log_viewer/models/__init__.py
new file mode 100644
index 000000000..e33386e53
--- /dev/null
+++ b/odex25_base/odoo_log_viewer/models/__init__.py
@@ -0,0 +1 @@
+from . import log_handler
\ No newline at end of file
diff --git a/odex25_base/odoo_log_viewer/models/log_handler.py b/odex25_base/odoo_log_viewer/models/log_handler.py
new file mode 100644
index 000000000..643044448
--- /dev/null
+++ b/odex25_base/odoo_log_viewer/models/log_handler.py
@@ -0,0 +1,46 @@
+import logging
+from odoo import api, models
+from werkzeug._internal import _log
+
+class MemoryLogHandler(logging.Handler):
+ def __init__(self, max_entries=1000):
+ super().__init__()
+ self.max_entries = max_entries
+ self.entries = []
+
+ def emit(self, record):
+ if '/log_viewer/get_logs' in getattr(record, 'message', ''):
+ return
+ self.entries.append(self.format(record))
+ if len(self.entries) > self.max_entries:
+ self.entries.pop(0)
+
+class LogHandlerManager(models.AbstractModel):
+ _name = 'log.handler.manager'
+ _description = 'Log Handler Manager'
+
+ @api.model
+ def _get_memory_handler(self):
+ if not hasattr(LogHandlerManager, '_memory_handler'):
+ LogHandlerManager._memory_handler = MemoryLogHandler()
+ formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
+ LogHandlerManager._memory_handler.setFormatter(formatter)
+ logging.getLogger().addHandler(LogHandlerManager._memory_handler)
+
+
+ def custom_log(type, message, *args, **kwargs):
+ if '/log_viewer/get_logs' not in message:
+ _log(type, message, *args, **kwargs)
+
+ import werkzeug
+ werkzeug._internal._log = custom_log
+
+ return LogHandlerManager._memory_handler
+
+ @api.model
+ def _register_hook(self):
+ self._get_memory_handler()
+
+ @api.model
+ def get_log_entries(self):
+ return self._get_memory_handler().entries
\ No newline at end of file
diff --git a/odex25_base/odoo_log_viewer/static/description/icon.png b/odex25_base/odoo_log_viewer/static/description/icon.png
new file mode 100644
index 000000000..eb4606020
Binary files /dev/null and b/odex25_base/odoo_log_viewer/static/description/icon.png differ
diff --git a/odex25_base/odoo_log_viewer/static/src/css/log_viewer_style.css b/odex25_base/odoo_log_viewer/static/src/css/log_viewer_style.css
new file mode 100644
index 000000000..6dfa8e767
--- /dev/null
+++ b/odex25_base/odoo_log_viewer/static/src/css/log_viewer_style.css
@@ -0,0 +1,12 @@
+form label {
+ font-weight: 700;
+}
+
+label {
+ display: inline-block;
+ margin-bottom: 0.5rem;
+}
+
+body {
+ background-color: #464646;
+}
diff --git a/odex25_base/odoo_log_viewer/views/assets.xml b/odex25_base/odoo_log_viewer/views/assets.xml
new file mode 100644
index 000000000..65b908453
--- /dev/null
+++ b/odex25_base/odoo_log_viewer/views/assets.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/odex25_base/odoo_log_viewer/views/log_viewer_template.xml b/odex25_base/odoo_log_viewer/views/log_viewer_template.xml
new file mode 100644
index 000000000..996ed90bb
--- /dev/null
+++ b/odex25_base/odoo_log_viewer/views/log_viewer_template.xml
@@ -0,0 +1,66 @@
+
+
+
+
+
+ Log Viewer - Login
+
+
+
+
+
+
+ Odoo Log Viewer
+
+
+
+
+