Merge pull request #27 from expsa/odoo_log

odoo_log
This commit is contained in:
esam-sermah 2025-09-30 13:20:24 +03:00 committed by GitHub
commit dbc3dbd482
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 234 additions and 0 deletions

View File

@ -0,0 +1,2 @@
from . import models
from . import controllers

View File

@ -0,0 +1,22 @@
{
'name': 'Odoo Log Viewer',
'version': '18.0.1.0.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',
'data/ir_config_parameter.xml',
],
'assets': {
'web.assets_frontend': [
('include', 'web._assets_helpers'), # إضافة هامة للتوافقية
'odoo_log_viewer/static/src/css/log_viewer_style.css',
],
},
'installable': True,
'application': False,
'auto_install': False,
}

View File

@ -0,0 +1 @@
from . import main

View File

@ -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'}

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="log_viewer_password" model="ir.config_parameter">
<field name="key">odoo_log_viewer.password</field>
<field name="value">$ecret@)24</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1 @@
from . import log_handler

View File

@ -0,0 +1,67 @@
import logging
from odoo import api, models
# استخدام المسجل القياسي لإضافة رسائل تصحيح خاصة بهذا الموديول
_logger = logging.getLogger(__name__)
class MemoryLogHandler(logging.Handler):
def __init__(self):
super().__init__()
self.max_entries = 10000 # الاحتفاظ بآخر 10000 سجل
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'
# سنحتفظ بالمعالج كمتغير على مستوى الكلاس لضمان وجود نسخة واحدة فقط
_memory_handler = None
@api.model
def _get_memory_handler(self):
# هذه الدالة الآن أصبحت أبسط، فقط تعيد المعالج الموجود
return LogHandlerManager._memory_handler
@api.model
def _register_hook(self):
# هذه الدالة تُستدعى عند تحميل سجل الموديلات في أودو
if LogHandlerManager._memory_handler is None:
_logger.info("Initializing MemoryLogHandler for Odoo Log Viewer.")
# 1. إنشاء معالج السجلات الخاص بنا
handler = MemoryLogHandler()
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
# 2. الحصول على مسجل الجذر وإضافة معالجنا إليه
root_logger = logging.getLogger()
root_logger.addHandler(handler)
# 3. !! التغيير الرئيسي هنا !!
# ضبط المستوى على INFO لتجاهل رسائل DEBUG المزعجة.
if root_logger.level > logging.INFO:
root_logger.setLevel(logging.INFO)
LogHandlerManager._memory_handler = handler
_logger.info("MemoryLogHandler successfully attached to the root logger at INFO level.")
@api.model
def get_log_entries(self):
handler = self._get_memory_handler()
if handler:
# إرجاع نسخة من القائمة لتجنب المشاكل المتعلقة بالتزامن
return list(handler.entries)
return []

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,28 @@
/* Style for the login form */
form label {
font-weight: 700;
}
label {
display: inline-block;
margin-bottom: 0.5rem;
}
body {
background-color: #464646;
}
#log-content {
white-space: pre-wrap;
word-wrap: break-word;
overflow-y: auto;
max-height: 80vh;
background-color: #1e1e1e;
color: #d4d4d4;
padding: 1em;
border-radius: 5px;
font-family: Consolas, Monaco, 'Courier New', monospace;
font-size: 13px;
border: 1px solid #444;
}

View File

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<template id="password_template" name="Log Viewer Password">
<t t-call="web.layout">
<t t-call-assets="odoo_log_viewer.assets"/>
<t t-set="title">Log Viewer - Login</t>
<div class="container py-5">
<div class="card border-0 mx-auto bg-100 o_database_list" style="max-width: 300px;">
<div class="card-body">
<h2 class="card-title text-center mb-4">Log Viewer</h2>
<form action="/log_viewer" method="get" class="oe_login_form">
<div class="form-group field-login">
<label for="password">Password</label>
<input type="password" class="form-control" id="password" name="password" autofocus="autofocus" required="required" autocomplete="off"/>
</div>
<div class="d-grid gap-2 mt-4">
<button type="submit" class="btn btn-primary btn-block">Log In</button>
</div>
</form>
</div>
</div>
</div>
</t>
</template>
<template id="log_viewer_template" name="Log Viewer">
<t t-call="web.layout">
<t t-set="title">Odoo Log Viewer</t>
<div class="container">
<h1>Odoo Log</h1>
<pre id="log-content" style="white-space: pre-wrap;"></pre>
</div>
<script type="text/javascript">
var password = <t t-out="json.dumps(request.params.get('password'))"/>;
function updateLogs() {
var xhr = new XMLHttpRequest();
xhr.open('POST', '/log_viewer/get_logs', true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = function() {
if (xhr.status === 200) {
try {
var result = JSON.parse(xhr.responseText);
if (result.error) {
alert("Server Error: " + result.error);
} else {
var logContent = document.getElementById('log-content');
if (logContent) {
logContent.textContent = result.result.log_content;
}
}
} catch (e) {
console.error("Error processing server response:", e);
}
} else {
console.error("Request failed with status:", xhr.status);
}
};
xhr.onerror = function() {
console.error("A network error occurred.");
};
xhr.send(JSON.stringify({
jsonrpc: "2.0",
method: "call",
params: { password: password },
id: new Date().getTime()
}));
}
updateLogs();
setInterval(updateLogs, 1000);
</script>
</t>
</template>
</odoo>