first commit
134
smile_log/README.rst
Executable file
@@ -0,0 +1,134 @@
|
||||
====================
|
||||
Logging in database
|
||||
====================
|
||||
|
||||
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
|
||||
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
|
||||
:alt: License: AGPL-3
|
||||
.. |badge3| image:: https://img.shields.io/badge/github-Smile_SA%2Fodoo_addons-lightgray.png?logo=github
|
||||
:target: https://github.com/Smile-SA/odoo_addons/tree/14.0/smile_log
|
||||
:alt: Smile-SA/odoo_addons
|
||||
|
||||
|badge2| |badge3|
|
||||
|
||||
This module adds a logs handler writing to database.
|
||||
|
||||
Notice
|
||||
|
||||
* Following code will create a log in db with a unique pid per logger:
|
||||
import logging
|
||||
logger = SmileLogger(dbname, model_name, res_id, uid)
|
||||
logger.info(your_message)
|
||||
|
||||
Features :
|
||||
|
||||
* Create logs when executing an action.
|
||||
* Archive and delete old logs from database.
|
||||
* Give users access right to see logs.
|
||||
|
||||
|
||||
**Table of contents**
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
* Developer adds ``import logging`` to his python file.
|
||||
* Developer must add following code to his action and specify the database, the model name, the res_id, and uid. Then give a message to log for information:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
logger = SmileLogger(dbname, model_name, res_id, uid)
|
||||
logger.info(your_message)
|
||||
|
||||
* Administrator must create a ``Scheduled Action`` to call the function ``archive_and_delete_old_logs``, configure archiving path and the number of days to archive and delete logs.
|
||||
|
||||
Usage
|
||||
=====
|
||||
To add Logs handler to an action :
|
||||
|
||||
1. Import SmileDBLogger to your python code and add code lines as shown in following example :
|
||||
|
||||
.. figure:: static/description/inherit_and_import_smile_log.png
|
||||
:alt: Import SmileDBLogger
|
||||
:width: 600px
|
||||
|
||||
2. Add ``smile_log`` to your module dependence:
|
||||
|
||||
.. figure:: static/description/manifest.png
|
||||
:alt: Depends manifest
|
||||
:width: 500px
|
||||
|
||||
3. Now execute the action.:
|
||||
|
||||
.. figure:: static/description/action.png
|
||||
:alt: Button validate
|
||||
:width: 850px
|
||||
|
||||
4. Go to ``Settings > Technical > Logging``> Logs menu to see logs.
|
||||
|
||||
.. figure:: static/description/logs.png
|
||||
:alt: Logs
|
||||
:width: 850px
|
||||
|
||||
Administrator can give access right to users, to see logs, by checking ``Smile Logs / User``.
|
||||
|
||||
.. figure:: static/description/smile_logs_user.png
|
||||
:alt: Smile Logs
|
||||
:width: 600px
|
||||
|
||||
To create the scheduled action:
|
||||
1. Go to ``Settings > Technical > Automation > Scheduled Actions`` and fill fields as follow:
|
||||
|
||||
.. figure:: static/description/scheduled_action.png
|
||||
:alt: Schedules Action
|
||||
:width: 850px
|
||||
|
||||
``(Make sure that the given folder has a write access!)``
|
||||
|
||||
2. After running the action, the extracted logs file in csv format is as shown in next figure:
|
||||
|
||||
.. figure:: static/description/exported_log.png
|
||||
:alt: Smile Logs
|
||||
:width: 380px
|
||||
|
||||
Bug Tracker
|
||||
===========
|
||||
|
||||
Bugs are tracked on `GitHub Issues <https://github.com/Smile-SA/odoo_addons/issues>`_.
|
||||
In case of trouble, please check there if your issue has already been reported.
|
||||
If you spotted it first, help us smashing it by providing a detailed and welcomed feedback
|
||||
`here <https://github.com/Smile-SA/odoo_addons/issues/new?body=module:%20smile_log%0Aversion:%2013.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
|
||||
|
||||
Do not contact contributors directly about support or help with technical issues.
|
||||
|
||||
GDPR / EU Privacy
|
||||
=================
|
||||
This addons does not collect any data and does not set any browser cookies.
|
||||
|
||||
Credits
|
||||
=======
|
||||
|
||||
Authors
|
||||
-------
|
||||
|
||||
* Corentin POUHET-BRUNERIE
|
||||
* Xavier FERNANDEZ
|
||||
* Majda EL MARIOULI
|
||||
|
||||
Maintainer
|
||||
----------
|
||||
This module is maintained by the Smile SA.
|
||||
|
||||
Since 1991 Smile has been a pioneer of technology and also the European expert in open source solutions.
|
||||
|
||||
.. image:: https://avatars0.githubusercontent.com/u/572339?s=200&v=4
|
||||
:alt: Smile SA
|
||||
:target: http://smile.fr
|
||||
|
||||
This module is part of the `odoo-addons <https://github.com/Smile-SA/odoo_addons>`_ project on GitHub.
|
||||
|
||||
You are welcome to contribute.
|
||||
|
||||
6
smile_log/__init__.py
Executable file
@@ -0,0 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# (C) 2020 Smile (<http://www.smile.fr>)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
|
||||
from . import models
|
||||
from . import tools
|
||||
33
smile_log/__manifest__.py
Executable file
@@ -0,0 +1,33 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# (C) 2020 Smile (<http://www.smile.fr>)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
|
||||
{
|
||||
"name": "Logging in database",
|
||||
"version": "3.0",
|
||||
"author": "Smile",
|
||||
"website": 'http://www.smile.fr',
|
||||
"category": "Tools",
|
||||
"license": 'AGPL-3',
|
||||
"description": """
|
||||
Logs handler writing to database
|
||||
|
||||
Notice
|
||||
|
||||
* Following code will create a log in db with a unique pid per logger:
|
||||
import logging
|
||||
logger = SmileLogger(dbname, model_name, res_id, uid)
|
||||
logger.info(your_message)
|
||||
|
||||
Suggestions & Feedback to: xavier.fernandez@smile.fr,
|
||||
corentin.pouhet-brunerie@smile.fr
|
||||
""",
|
||||
"depends": ['base'],
|
||||
"data": [
|
||||
"security/smile_log_security.xml",
|
||||
"security/ir.model.access.csv",
|
||||
"views/smile_log_view.xml",
|
||||
],
|
||||
"installable": True,
|
||||
"active": False,
|
||||
}
|
||||
111
smile_log/i18n/fr.po
Executable file
@@ -0,0 +1,111 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * smile_log
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 14.0+e\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-07-13 14:15+0000\n"
|
||||
"PO-Revision-Date: 2020-07-13 14:15+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: smile_log
|
||||
#: model:ir.model.fields,field_description:smile_log.field_smile_log__log_date
|
||||
msgid "Date"
|
||||
msgstr "Date"
|
||||
|
||||
#. module: smile_log
|
||||
#: model:ir.model.fields,field_description:smile_log.field_smile_log__display_name
|
||||
msgid "Display Name"
|
||||
msgstr "Afficher Nom"
|
||||
|
||||
#. module: smile_log
|
||||
#: model_terms:ir.ui.view,arch_db:smile_log.smile_log_search_view
|
||||
msgid "Group By..."
|
||||
msgstr "Grouper par..."
|
||||
|
||||
#. module: smile_log
|
||||
#: model:ir.model.fields,field_description:smile_log.field_smile_log__id
|
||||
msgid "ID"
|
||||
msgstr "ID"
|
||||
|
||||
#. module: smile_log
|
||||
#: model:ir.model.fields,field_description:smile_log.field_smile_log____last_update
|
||||
msgid "Last Modified on"
|
||||
msgstr "Dernière modification le"
|
||||
|
||||
#. module: smile_log
|
||||
#: model:ir.model.fields,field_description:smile_log.field_smile_log__level
|
||||
msgid "Level"
|
||||
msgstr "Niveau"
|
||||
|
||||
#. module: smile_log
|
||||
#: model:ir.ui.menu,name:smile_log.menu_logging
|
||||
msgid "Logging"
|
||||
msgstr "Historisation"
|
||||
|
||||
#. module: smile_log
|
||||
#: model:ir.actions.act_window,name:smile_log.act_smile_log
|
||||
#: model:ir.ui.menu,name:smile_log.menu_smile_log
|
||||
#: model_terms:ir.ui.view,arch_db:smile_log.smile_log_search_view
|
||||
#: model_terms:ir.ui.view,arch_db:smile_log.smile_log_simple_tree_view
|
||||
#: model_terms:ir.ui.view,arch_db:smile_log.smile_log_tree_view
|
||||
msgid "Logs"
|
||||
msgstr "Historiques"
|
||||
|
||||
#. module: smile_log
|
||||
#: model:ir.model.fields,field_description:smile_log.field_smile_log__message
|
||||
msgid "Message"
|
||||
msgstr "Message"
|
||||
|
||||
#. module: smile_log
|
||||
#: model_terms:ir.ui.view,arch_db:smile_log.smile_log_search_view
|
||||
msgid "Model"
|
||||
msgstr "Modèle"
|
||||
|
||||
#. module: smile_log
|
||||
#: model:ir.model.fields,field_description:smile_log.field_smile_log__model_name
|
||||
msgid "Model name"
|
||||
msgstr "Nom du modèle"
|
||||
|
||||
#. module: smile_log
|
||||
#: model_terms:ir.ui.view,arch_db:smile_log.smile_log_search_view
|
||||
msgid "PID"
|
||||
msgstr "PID"
|
||||
|
||||
#. module: smile_log
|
||||
#: model:ir.model.fields,field_description:smile_log.field_smile_log__pid
|
||||
msgid "Pid"
|
||||
msgstr "Pid"
|
||||
|
||||
#. module: smile_log
|
||||
#: model:ir.model.fields,field_description:smile_log.field_smile_log__res_id
|
||||
msgid "Ressource id"
|
||||
msgstr "Id de la ressource"
|
||||
|
||||
#. module: smile_log
|
||||
#: model:ir.model.fields,field_description:smile_log.field_smile_log__log_res_name
|
||||
msgid "Ressource name"
|
||||
msgstr "Nom de la ressource"
|
||||
|
||||
#. module: smile_log
|
||||
#: model:ir.model,name:smile_log.model_smile_log
|
||||
msgid "Smile Logs"
|
||||
msgstr "Logs"
|
||||
|
||||
#. module: smile_log
|
||||
#: model:res.groups,name:smile_log.group_smile_log_user
|
||||
msgid "Smile Logs / User"
|
||||
msgstr "Smile Logs / Utilisateur"
|
||||
|
||||
#. module: smile_log
|
||||
#: model:ir.model.fields,field_description:smile_log.field_smile_log__log_uid
|
||||
#: model:ir.model.fields,field_description:smile_log.field_smile_log__log_user_name
|
||||
msgid "User"
|
||||
msgstr "Utilisateur"
|
||||
5
smile_log/models/__init__.py
Executable file
@@ -0,0 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# (C) 2020 Smile (<http://www.smile.fr>)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
|
||||
from . import smile_log
|
||||
65
smile_log/models/smile_log.py
Executable file
@@ -0,0 +1,65 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# (C) 2020 Smile (<http://www.smile.fr>)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
|
||||
import os
|
||||
import time
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class SmileLog(models.Model):
|
||||
_name = 'smile.log'
|
||||
_description = 'Smile Logs'
|
||||
_rec_name = 'message'
|
||||
_log_access = False
|
||||
_order = 'log_date desc'
|
||||
|
||||
@api.depends('log_uid')
|
||||
def _get_user_name(self):
|
||||
for log in self:
|
||||
user = self.env['res.users'].browse(log.log_uid)
|
||||
if user.exists():
|
||||
log.log_user_name = "%s [%s]" % (user.name, log.log_uid)
|
||||
else:
|
||||
log.log_user_name = "[%s]" % log.log_uid
|
||||
|
||||
@api.depends('res_id')
|
||||
def _get_res_name(self):
|
||||
for log in self:
|
||||
log.log_res_name = ""
|
||||
if log.model_name:
|
||||
res = self.env[log.model_name].browse(log.res_id)
|
||||
infos = res.name_get()
|
||||
if infos:
|
||||
log.log_res_name = infos[0][1]
|
||||
|
||||
log_date = fields.Datetime('Date', readonly=True)
|
||||
log_uid = fields.Integer('User', readonly=True)
|
||||
log_user_name = fields.Char(
|
||||
string='User', size=256, compute='_get_user_name')
|
||||
log_res_name = fields.Char(
|
||||
string='Ressource name', size=256, compute='_get_res_name')
|
||||
model_name = fields.Char('Model name', size=64, readonly=True, index=True)
|
||||
res_id = fields.Integer(
|
||||
'Ressource id', readonly=True, group_operator="count", index=True)
|
||||
pid = fields.Integer(readonly=True, group_operator="count")
|
||||
level = fields.Char(size=16, readonly=True)
|
||||
message = fields.Text('Message', readonly=True)
|
||||
|
||||
@api.model
|
||||
def archive_and_delete_old_logs(self, nb_days=90, archive_path=''):
|
||||
# Thanks to transaction isolation, the COPY and DELETE will find
|
||||
# the same smile_log records
|
||||
if archive_path:
|
||||
file_name = time.strftime("%Y%m%d_%H%M%S.log.csv")
|
||||
file_path = os.path.join(archive_path, file_name)
|
||||
self.env.cr.execute("""COPY (SELECT * FROM smile_log
|
||||
WHERE log_date + interval'%s days' < NOW() at time zone 'UTC')
|
||||
TO %s
|
||||
WITH (FORMAT csv, ENCODING utf8)""", (nb_days, file_path,))
|
||||
self.env.cr.execute(
|
||||
"DELETE FROM smile_log "
|
||||
"WHERE log_date + interval '%s days' < NOW() at time zone 'UTC'",
|
||||
(nb_days,))
|
||||
return True
|
||||
2
smile_log/security/ir.model.access.csv
Executable file
@@ -0,0 +1,2 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
smile_log_user,smile_log group_user,model_smile_log,base.group_user,1,1,0,0
|
||||
|
10
smile_log/security/smile_log_security.xml
Executable file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
<data noupdate="0">
|
||||
|
||||
<record id="group_smile_log_user" model="res.groups">
|
||||
<field name="name">Smile Logs / User</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
BIN
smile_log/static/description/action.png
Executable file
|
After Width: | Height: | Size: 75 KiB |
BIN
smile_log/static/description/exported_log.png
Executable file
|
After Width: | Height: | Size: 130 KiB |
BIN
smile_log/static/description/icon.png
Executable file
|
After Width: | Height: | Size: 9.2 KiB |
686
smile_log/static/description/index.html
Executable file
@@ -0,0 +1,686 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!DOCTYPE html
|
||||
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="generator" content="Docutils 0.14: http://docutils.sourceforge.net/" />
|
||||
<title>Logging in database</title>
|
||||
<style type="text/css">
|
||||
/*
|
||||
:Author: David Goodger (goodger@python.org)
|
||||
:Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $
|
||||
:Copyright: This stylesheet has been placed in the public domain.
|
||||
|
||||
Default cascading style sheet for the HTML output of Docutils.
|
||||
|
||||
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
|
||||
customize this style sheet.
|
||||
*/
|
||||
|
||||
/* used to remove borders from tables and images */
|
||||
.borderless,
|
||||
table.borderless td,
|
||||
table.borderless th {
|
||||
border: 0
|
||||
}
|
||||
|
||||
table.borderless td,
|
||||
table.borderless th {
|
||||
/* Override padding for "table.docutils td" with "! important".
|
||||
The right padding separates the table cells. */
|
||||
padding: 0 0.5em 0 0 ! important
|
||||
}
|
||||
|
||||
.first {
|
||||
/* Override more specific margin styles with "! important". */
|
||||
margin-top: 0 ! important
|
||||
}
|
||||
|
||||
.last,
|
||||
.with-subtitle {
|
||||
margin-bottom: 0 ! important
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none
|
||||
}
|
||||
|
||||
.subscript {
|
||||
vertical-align: sub;
|
||||
font-size: smaller
|
||||
}
|
||||
|
||||
.superscript {
|
||||
vertical-align: super;
|
||||
font-size: smaller
|
||||
}
|
||||
|
||||
a.toc-backref {
|
||||
text-decoration: none;
|
||||
color: black
|
||||
}
|
||||
|
||||
blockquote.epigraph {
|
||||
margin: 2em 5em;
|
||||
}
|
||||
|
||||
dl.docutils dd {
|
||||
margin-bottom: 0.5em
|
||||
}
|
||||
|
||||
object[type="image/svg+xml"],
|
||||
object[type="application/x-shockwave-flash"] {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Uncomment (and remove this text!) to get bold-faced definition list terms
|
||||
dl.docutils dt {
|
||||
font-weight: bold }
|
||||
*/
|
||||
|
||||
div.abstract {
|
||||
margin: 2em 5em
|
||||
}
|
||||
|
||||
div.abstract p.topic-title {
|
||||
font-weight: bold;
|
||||
text-align: center
|
||||
}
|
||||
|
||||
div.admonition,
|
||||
div.attention,
|
||||
div.caution,
|
||||
div.danger,
|
||||
div.error,
|
||||
div.hint,
|
||||
div.important,
|
||||
div.note,
|
||||
div.tip,
|
||||
div.warning {
|
||||
margin: 2em;
|
||||
border: medium outset;
|
||||
padding: 1em
|
||||
}
|
||||
|
||||
div.admonition p.admonition-title,
|
||||
div.hint p.admonition-title,
|
||||
div.important p.admonition-title,
|
||||
div.note p.admonition-title,
|
||||
div.tip p.admonition-title {
|
||||
font-weight: bold;
|
||||
font-family: sans-serif
|
||||
}
|
||||
|
||||
div.attention p.admonition-title,
|
||||
div.caution p.admonition-title,
|
||||
div.danger p.admonition-title,
|
||||
div.error p.admonition-title,
|
||||
div.warning p.admonition-title,
|
||||
.code .error {
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
font-family: sans-serif
|
||||
}
|
||||
|
||||
/* Uncomment (and remove this text!) to get reduced vertical space in
|
||||
compound paragraphs.
|
||||
div.compound .compound-first, div.compound .compound-middle {
|
||||
margin-bottom: 0.5em }
|
||||
|
||||
div.compound .compound-last, div.compound .compound-middle {
|
||||
margin-top: 0.5em }
|
||||
*/
|
||||
|
||||
div.dedication {
|
||||
margin: 2em 5em;
|
||||
text-align: center;
|
||||
font-style: italic
|
||||
}
|
||||
|
||||
div.dedication p.topic-title {
|
||||
font-weight: bold;
|
||||
font-style: normal
|
||||
}
|
||||
|
||||
div.figure {
|
||||
margin-left: 2em;
|
||||
margin-right: 2em
|
||||
}
|
||||
|
||||
div.footer,
|
||||
div.header {
|
||||
clear: both;
|
||||
font-size: smaller
|
||||
}
|
||||
|
||||
div.line-block {
|
||||
display: block;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em
|
||||
}
|
||||
|
||||
div.line-block div.line-block {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
margin-left: 1.5em
|
||||
}
|
||||
|
||||
div.sidebar {
|
||||
margin: 0 0 0.5em 1em;
|
||||
border: medium outset;
|
||||
padding: 1em;
|
||||
background-color: #ffffee;
|
||||
width: 40%;
|
||||
float: right;
|
||||
clear: right
|
||||
}
|
||||
|
||||
div.sidebar p.rubric {
|
||||
font-family: sans-serif;
|
||||
font-size: medium
|
||||
}
|
||||
|
||||
div.system-messages {
|
||||
margin: 5em
|
||||
}
|
||||
|
||||
div.system-messages h1 {
|
||||
color: red
|
||||
}
|
||||
|
||||
div.system-message {
|
||||
border: medium outset;
|
||||
padding: 1em
|
||||
}
|
||||
|
||||
div.system-message p.system-message-title {
|
||||
color: red;
|
||||
font-weight: bold
|
||||
}
|
||||
|
||||
div.topic {
|
||||
margin: 2em
|
||||
}
|
||||
|
||||
h1.section-subtitle,
|
||||
h2.section-subtitle,
|
||||
h3.section-subtitle,
|
||||
h4.section-subtitle,
|
||||
h5.section-subtitle,
|
||||
h6.section-subtitle {
|
||||
margin-top: 0.4em
|
||||
}
|
||||
|
||||
h1.title {
|
||||
text-align: center
|
||||
}
|
||||
|
||||
h2.subtitle {
|
||||
text-align: center
|
||||
}
|
||||
|
||||
hr.docutils {
|
||||
width: 75%
|
||||
}
|
||||
|
||||
img.align-left,
|
||||
.figure.align-left,
|
||||
object.align-left,
|
||||
table.align-left {
|
||||
clear: left;
|
||||
float: left;
|
||||
margin-right: 1em
|
||||
}
|
||||
|
||||
img.align-right,
|
||||
.figure.align-right,
|
||||
object.align-right,
|
||||
table.align-right {
|
||||
clear: right;
|
||||
float: right;
|
||||
margin-left: 1em
|
||||
}
|
||||
|
||||
img.align-center,
|
||||
.figure.align-center,
|
||||
object.align-center {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
table.align-center {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.align-left {
|
||||
text-align: left
|
||||
}
|
||||
|
||||
.align-center {
|
||||
clear: both;
|
||||
text-align: center
|
||||
}
|
||||
|
||||
.align-right {
|
||||
text-align: right
|
||||
}
|
||||
|
||||
/* reset inner alignment in figures */
|
||||
div.align-right {
|
||||
text-align: inherit
|
||||
}
|
||||
|
||||
/* div.align-center * { */
|
||||
/* text-align: left } */
|
||||
|
||||
.align-top {
|
||||
vertical-align: top
|
||||
}
|
||||
|
||||
.align-middle {
|
||||
vertical-align: middle
|
||||
}
|
||||
|
||||
.align-bottom {
|
||||
vertical-align: bottom
|
||||
}
|
||||
|
||||
ol.simple,
|
||||
ul.simple {
|
||||
margin-bottom: 1em
|
||||
}
|
||||
|
||||
ol.arabic {
|
||||
list-style: decimal
|
||||
}
|
||||
|
||||
ol.loweralpha {
|
||||
list-style: lower-alpha
|
||||
}
|
||||
|
||||
ol.upperalpha {
|
||||
list-style: upper-alpha
|
||||
}
|
||||
|
||||
ol.lowerroman {
|
||||
list-style: lower-roman
|
||||
}
|
||||
|
||||
ol.upperroman {
|
||||
list-style: upper-roman
|
||||
}
|
||||
|
||||
p.attribution {
|
||||
text-align: right;
|
||||
margin-left: 50%
|
||||
}
|
||||
|
||||
p.caption {
|
||||
font-style: italic
|
||||
}
|
||||
|
||||
p.credits {
|
||||
font-style: italic;
|
||||
font-size: smaller
|
||||
}
|
||||
|
||||
p.label {
|
||||
white-space: nowrap
|
||||
}
|
||||
|
||||
p.rubric {
|
||||
font-weight: bold;
|
||||
font-size: larger;
|
||||
color: maroon;
|
||||
text-align: center
|
||||
}
|
||||
|
||||
p.sidebar-title {
|
||||
font-family: sans-serif;
|
||||
font-weight: bold;
|
||||
font-size: larger
|
||||
}
|
||||
|
||||
p.sidebar-subtitle {
|
||||
font-family: sans-serif;
|
||||
font-weight: bold
|
||||
}
|
||||
|
||||
p.topic-title {
|
||||
font-weight: bold
|
||||
}
|
||||
|
||||
pre.address {
|
||||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
font: inherit
|
||||
}
|
||||
|
||||
pre.literal-block,
|
||||
pre.doctest-block,
|
||||
pre.math,
|
||||
pre.code {
|
||||
margin-left: 2em;
|
||||
margin-right: 2em
|
||||
}
|
||||
|
||||
pre.code .ln {
|
||||
color: grey;
|
||||
}
|
||||
|
||||
/* line numbers */
|
||||
pre.code,
|
||||
code {
|
||||
background-color: #eeeeee
|
||||
}
|
||||
|
||||
pre.code .comment,
|
||||
code .comment {
|
||||
color: #5C6576
|
||||
}
|
||||
|
||||
pre.code .keyword,
|
||||
code .keyword {
|
||||
color: #3B0D06;
|
||||
font-weight: bold
|
||||
}
|
||||
|
||||
pre.code .literal.string,
|
||||
code .literal.string {
|
||||
color: #0C5404
|
||||
}
|
||||
|
||||
pre.code .name.builtin,
|
||||
code .name.builtin {
|
||||
color: #352B84
|
||||
}
|
||||
|
||||
pre.code .deleted,
|
||||
code .deleted {
|
||||
background-color: #DEB0A1
|
||||
}
|
||||
|
||||
pre.code .inserted,
|
||||
code .inserted {
|
||||
background-color: #A3D289
|
||||
}
|
||||
|
||||
span.classifier {
|
||||
font-family: sans-serif;
|
||||
font-style: oblique
|
||||
}
|
||||
|
||||
span.classifier-delimiter {
|
||||
font-family: sans-serif;
|
||||
font-weight: bold
|
||||
}
|
||||
|
||||
span.interpreted {
|
||||
font-family: sans-serif
|
||||
}
|
||||
|
||||
span.option {
|
||||
white-space: nowrap
|
||||
}
|
||||
|
||||
span.pre {
|
||||
white-space: pre
|
||||
}
|
||||
|
||||
span.problematic {
|
||||
color: red
|
||||
}
|
||||
|
||||
span.section-subtitle {
|
||||
/* font-size relative to parent (h1..h6 element) */
|
||||
font-size: 80%
|
||||
}
|
||||
|
||||
table.citation {
|
||||
border-left: solid 1px gray;
|
||||
margin-left: 1px
|
||||
}
|
||||
|
||||
table.docinfo {
|
||||
margin: 2em 4em
|
||||
}
|
||||
|
||||
table.docutils {
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 0.5em
|
||||
}
|
||||
|
||||
table.footnote {
|
||||
border-left: solid 1px black;
|
||||
margin-left: 1px
|
||||
}
|
||||
|
||||
table.docutils td,
|
||||
table.docutils th,
|
||||
table.docinfo td,
|
||||
table.docinfo th {
|
||||
padding-left: 0.5em;
|
||||
padding-right: 0.5em;
|
||||
vertical-align: top
|
||||
}
|
||||
|
||||
table.docutils th.field-name,
|
||||
table.docinfo th.docinfo-name {
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
padding-left: 0
|
||||
}
|
||||
|
||||
/* "booktabs" style (no vertical lines) */
|
||||
table.docutils.booktabs {
|
||||
border: 0px;
|
||||
border-top: 2px solid;
|
||||
border-bottom: 2px solid;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table.docutils.booktabs * {
|
||||
border: 0px;
|
||||
}
|
||||
|
||||
table.docutils.booktabs th {
|
||||
border-bottom: thin solid;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
h1 tt.docutils,
|
||||
h2 tt.docutils,
|
||||
h3 tt.docutils,
|
||||
h4 tt.docutils,
|
||||
h5 tt.docutils,
|
||||
h6 tt.docutils {
|
||||
font-size: 100%
|
||||
}
|
||||
|
||||
ul.auto-toc {
|
||||
list-style-type: none
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="document" id="logging-in-database">
|
||||
<h1 class="title">Logging in database</h1>
|
||||
|
||||
<p><a class="reference external" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img
|
||||
alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a
|
||||
class="reference external" href="https://github.com/Smile-SA/odoo_addons/tree/14.0/smile_log"><img
|
||||
alt="Smile-SA/odoo_addons"
|
||||
src="https://img.shields.io/badge/github-Smile_SA%2Fodoo_addons-lightgray.png?logo=github" /></a></p>
|
||||
<p>This module adds a logs handler writing to database.</p>
|
||||
<p>Notice</p>
|
||||
<blockquote>
|
||||
<ul class="simple">
|
||||
<li>
|
||||
<dl class="first docutils">
|
||||
<dt>Following code will create a log in db with a unique pid per logger:</dt>
|
||||
<dd>import logging
|
||||
logger = SmileLogger(dbname, model_name, res_id, uid)
|
||||
logger.info(your_message)</dd>
|
||||
</dl>
|
||||
</li>
|
||||
</ul>
|
||||
</blockquote>
|
||||
<p>Features :</p>
|
||||
<ul class="simple">
|
||||
<li>Create logs when executing an action.</li>
|
||||
<li>Archive and delete old logs from database.</li>
|
||||
<li>Give users access right to see logs.</li>
|
||||
</ul>
|
||||
<p><strong>Table of contents</strong></p>
|
||||
<div class="contents local topic" id="contents">
|
||||
<ul class="simple">
|
||||
<li><a class="reference internal" href="#configuration" id="id1">Configuration</a></li>
|
||||
<li><a class="reference internal" href="#usage" id="id2">Usage</a></li>
|
||||
<li><a class="reference internal" href="#bug-tracker" id="id3">Bug Tracker</a></li>
|
||||
<li><a class="reference internal" href="#gdpr-eu-privacy" id="id4">GDPR / EU Privacy</a></li>
|
||||
<li><a class="reference internal" href="#credits" id="id5">Credits</a>
|
||||
<ul>
|
||||
<li><a class="reference internal" href="#authors" id="id6">Authors</a></li>
|
||||
<li><a class="reference internal" href="#maintainer" id="id7">Maintainer</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="configuration">
|
||||
<h1><a class="toc-backref" href="#id1">Configuration</a></h1>
|
||||
<ul class="simple">
|
||||
<li>Developer adds <tt class="docutils literal">import logging</tt> to his python file.</li>
|
||||
<li>Developer must add following code to his action and specify the database, the model name, the res_id, and
|
||||
uid. Then give a message to log for information:</li>
|
||||
</ul>
|
||||
<pre class="code python literal-block">
|
||||
<span class="name">logger</span> <span class="operator">=</span> <span class="name">SmileLogger</span><span class="punctuation">(</span><span class="name">dbname</span><span class="punctuation">,</span> <span class="name">model_name</span><span class="punctuation">,</span> <span class="name">res_id</span><span class="punctuation">,</span> <span class="name">uid</span><span class="punctuation">)</span>
|
||||
<span class="name">logger</span><span class="operator">.</span><span class="name">info</span><span class="punctuation">(</span><span class="name">your_message</span><span class="punctuation">)</span>
|
||||
</pre>
|
||||
<ul class="simple">
|
||||
<li>Administrator must create a <tt class="docutils literal">Scheduled Action</tt> to call the function <tt
|
||||
class="docutils literal">archive_and_delete_old_logs</tt>, configure archiving path and the number of days
|
||||
to archive and delete logs.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="usage">
|
||||
<h1><a class="toc-backref" href="#id2">Usage</a></h1>
|
||||
<p>To add Logs handler to an action :</p>
|
||||
<blockquote>
|
||||
<ol class="arabic">
|
||||
<li>
|
||||
<p class="first">Import SmileDBLogger to your python code and add code lines as shown in following example :
|
||||
</p>
|
||||
<blockquote>
|
||||
<div class="figure">
|
||||
<img alt="Import SmileDBLogger" src="inherit_and_import_smile_log.png" style="width: 600px;" />
|
||||
</div>
|
||||
</blockquote>
|
||||
</li>
|
||||
<li>
|
||||
<p class="first">Add <tt class="docutils literal">smile_log</tt> to your module dependence:</p>
|
||||
<blockquote>
|
||||
<div class="figure">
|
||||
<img alt="Depends manifest" src="manifest.png" style="width: 500px;" />
|
||||
</div>
|
||||
</blockquote>
|
||||
</li>
|
||||
<li>
|
||||
<p class="first">Now execute the action.:</p>
|
||||
<blockquote>
|
||||
<div class="figure">
|
||||
<img alt="Button validate" src="action.png" style="width: 850px;" />
|
||||
</div>
|
||||
</blockquote>
|
||||
</li>
|
||||
<li>
|
||||
<p class="first">Go to <tt class="docutils literal">Settings > Technical > Logging</tt>> Logs menu
|
||||
to see logs.</p>
|
||||
<blockquote>
|
||||
<div class="figure">
|
||||
<img alt="Logs" src="logs.png" style="width: 850px;" />
|
||||
</div>
|
||||
</blockquote>
|
||||
</li>
|
||||
</ol>
|
||||
</blockquote>
|
||||
<p>Administrator can give access right to users, to see logs, by checking <tt class="docutils literal">Smile Logs
|
||||
/ User</tt>.</p>
|
||||
<blockquote>
|
||||
<div class="figure">
|
||||
<img alt="Smile Logs" src="smile_logs_user.png" style="width: 600px;" />
|
||||
</div>
|
||||
</blockquote>
|
||||
<dl class="docutils">
|
||||
<dt>To create the scheduled action:</dt>
|
||||
<dd>
|
||||
<ol class="first last arabic">
|
||||
<li>
|
||||
<p class="first">Go to <tt class="docutils literal">Settings > Technical > Automation > Scheduled
|
||||
Actions</tt> and fill fields as follow:</p>
|
||||
<blockquote>
|
||||
<div class="figure">
|
||||
<img alt="Schedules Action" src="scheduled_action.png" style="width: 850px;" />
|
||||
</div>
|
||||
<p><tt class="docutils literal">(Make sure that the given folder has a write access!)</tt></p>
|
||||
</blockquote>
|
||||
</li>
|
||||
<li>
|
||||
<p class="first">After running the action, the extracted logs file in csv format is as shown in next
|
||||
figure:</p>
|
||||
<blockquote>
|
||||
<div class="figure">
|
||||
<img alt="Smile Logs" src="exported_log.png" style="width: 380px;" />
|
||||
</div>
|
||||
</blockquote>
|
||||
</li>
|
||||
</ol>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="section" id="bug-tracker">
|
||||
<h1><a class="toc-backref" href="#id3">Bug Tracker</a></h1>
|
||||
<p>Bugs are tracked on <a class="reference external" href="https://github.com/Smile-SA/odoo_addons/issues">GitHub
|
||||
Issues</a>.
|
||||
In case of trouble, please check there if your issue has already been reported.
|
||||
If you spotted it first, help us smashing it by providing a detailed and welcomed feedback
|
||||
<a class="reference external"
|
||||
href="https://github.com/Smile-SA/odoo_addons/issues/new?body=module:%20smile_log%0Aversion:%2013.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">here</a>.
|
||||
</p>
|
||||
<p>Do not contact contributors directly about support or help with technical issues.</p>
|
||||
</div>
|
||||
<div class="section" id="gdpr-eu-privacy">
|
||||
<h1><a class="toc-backref" href="#id4">GDPR / EU Privacy</a></h1>
|
||||
<p>This addons does not collect any data and does not set any browser cookies.</p>
|
||||
</div>
|
||||
<div class="section" id="credits">
|
||||
<h1><a class="toc-backref" href="#id5">Credits</a></h1>
|
||||
<div class="section" id="authors">
|
||||
<h2><a class="toc-backref" href="#id6">Authors</a></h2>
|
||||
<ul class="simple">
|
||||
<li>Corentin POUHET-BRUNERIE</li>
|
||||
<li>Xavier FERNANDEZ</li>
|
||||
<li>Majda EL MARIOULI</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="maintainer">
|
||||
<h2><a class="toc-backref" href="#id7">Maintainer</a></h2>
|
||||
<p>This module is maintained by the Smile SA.</p>
|
||||
<p>Since 1991 Smile has been a pioneer of technology and also the European expert in open source solutions.</p>
|
||||
<a class="reference external image-reference" href="http://smile.fr"><img alt="Smile SA"
|
||||
src="https://avatars0.githubusercontent.com/u/572339?s=200&v=4" /></a>
|
||||
<p>This module is part of the <a class="reference external"
|
||||
href="https://github.com/Smile-SA/odoo_addons">odoo-addons</a> project on GitHub.</p>
|
||||
<p>You are welcome to contribute.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
BIN
smile_log/static/description/inherit_and_import_smile_log.png
Executable file
|
After Width: | Height: | Size: 42 KiB |
BIN
smile_log/static/description/logs.png
Executable file
|
After Width: | Height: | Size: 109 KiB |
BIN
smile_log/static/description/manifest.png
Executable file
|
After Width: | Height: | Size: 21 KiB |
BIN
smile_log/static/description/scheduled_action.png
Executable file
|
After Width: | Height: | Size: 48 KiB |
BIN
smile_log/static/description/smile_logs_user.png
Executable file
|
After Width: | Height: | Size: 59 KiB |
6
smile_log/tools/__init__.py
Executable file
@@ -0,0 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# (C) 2020 Smile (<http://www.smile.fr>)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
|
||||
from .db_handler import *
|
||||
from .db_logger import *
|
||||
64
smile_log/tools/db_handler.py
Executable file
@@ -0,0 +1,64 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# (C) 2020 Smile (<http://www.smile.fr>)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
|
||||
import logging
|
||||
|
||||
from odoo import registry
|
||||
|
||||
|
||||
class SmileDBHandler(logging.Handler):
|
||||
|
||||
def __init__(self, level=logging.NOTSET):
|
||||
logging.Handler.__init__(self, level)
|
||||
self._dbname_to_cr = {}
|
||||
|
||||
def _get_cursor(self, dbname):
|
||||
cr = self._dbname_to_cr.get(dbname)
|
||||
if not cr or (cr and cr.closed):
|
||||
cr = registry(dbname).cursor()
|
||||
cr.autocommit(True)
|
||||
self._dbname_to_cr[dbname] = cr
|
||||
return cr
|
||||
|
||||
def emit(self, record):
|
||||
if not (record.args and isinstance(record.args, dict)):
|
||||
return False
|
||||
|
||||
dbname = record.args.get('dbname', '')
|
||||
cr = self._get_cursor(dbname)
|
||||
|
||||
res_id = record.args.get('res_id', 0)
|
||||
pid = record.args.get('pid', 0)
|
||||
uid = record.args.get('uid', 0)
|
||||
model_name = record.args.get('model_name', '')
|
||||
|
||||
request = """INSERT INTO smile_log
|
||||
(log_date, log_uid, model_name, res_id, pid, level, message)
|
||||
VALUES (now() at time zone 'UTC', %s, %s, %s, %s, %s, %s)"""
|
||||
params = (uid, model_name, res_id, pid, record.levelname, record.msg,)
|
||||
|
||||
try:
|
||||
cr.execute(request, params)
|
||||
except Exception:
|
||||
# retry
|
||||
cr = self._get_cursor(dbname)
|
||||
cr.execute(request, params)
|
||||
return True
|
||||
|
||||
def close(self):
|
||||
logging.Handler.close(self)
|
||||
for cr in self._dbname_to_cr.values():
|
||||
try:
|
||||
cr.execute(
|
||||
"INSERT INTO smile_log "
|
||||
"(log_date, log_uid, model_name, "
|
||||
"res_id, pid, level, message) "
|
||||
"VALUES (now() at time zone 'UTC', 0, '', "
|
||||
"0, 0, 'INFO', 'Odoo server stopped')")
|
||||
finally:
|
||||
cr.close()
|
||||
self._dbname_to_cr = {}
|
||||
|
||||
|
||||
logging.getLogger('smile_log').addHandler(SmileDBHandler())
|
||||
84
smile_log/tools/db_logger.py
Executable file
@@ -0,0 +1,84 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# (C) 2020 Smile (<http://www.smile.fr>)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from odoo import registry
|
||||
|
||||
from .misc import add_timing, add_trace
|
||||
|
||||
if sys.version_info > (3,):
|
||||
long = int
|
||||
|
||||
|
||||
class SmileDBLogger:
|
||||
|
||||
def __init__(self, dbname, model_name, res_id, uid=0):
|
||||
assert isinstance(uid, (int, long)), 'uid should be an integer'
|
||||
self._logger = logging.getLogger('smile_log')
|
||||
|
||||
pid = 0
|
||||
|
||||
try:
|
||||
cr = registry(dbname).cursor()
|
||||
cr.autocommit(True)
|
||||
cr.execute(
|
||||
"select relname from pg_class "
|
||||
"where relname='smile_log_seq'")
|
||||
if not cr.rowcount:
|
||||
cr.execute("create sequence smile_log_seq")
|
||||
cr.execute("select nextval('smile_log_seq')")
|
||||
res = cr.fetchone()
|
||||
pid = res and res[0] or 0
|
||||
finally:
|
||||
cr.close()
|
||||
|
||||
self._logger_start = datetime.datetime.now()
|
||||
self._logger_args = {
|
||||
'dbname': dbname, 'model_name': model_name,
|
||||
'res_id': res_id, 'uid': uid, 'pid': pid}
|
||||
|
||||
@property
|
||||
def pid(self):
|
||||
return self._logger_args['pid']
|
||||
|
||||
def setLevel(self, level):
|
||||
self._logger.setLevel(level)
|
||||
|
||||
def getEffectiveLevel(self):
|
||||
return self._logger.getEffectiveLevel()
|
||||
|
||||
def debug(self, msg):
|
||||
self._logger.debug(msg, self._logger_args)
|
||||
|
||||
def info(self, msg):
|
||||
self._logger.info(msg, self._logger_args)
|
||||
|
||||
def warning(self, msg):
|
||||
self._logger.warning(msg, self._logger_args)
|
||||
|
||||
def log(self, msg):
|
||||
self._logger.log(msg, self._logger_args)
|
||||
|
||||
@add_trace
|
||||
def error(self, msg):
|
||||
self._logger.error(msg, self._logger_args)
|
||||
|
||||
@add_trace
|
||||
def critical(self, msg):
|
||||
self._logger.critical(msg, self._logger_args)
|
||||
|
||||
@add_trace
|
||||
def exception(self, msg):
|
||||
self._logger.exception(msg, self._logger_args)
|
||||
|
||||
@add_timing
|
||||
def time_info(self, msg):
|
||||
self._logger.info(msg, self._logger_args)
|
||||
|
||||
@add_timing
|
||||
def time_debug(self, msg):
|
||||
self._logger.debug(msg, self._logger_args)
|
||||
23
smile_log/tools/misc.py
Executable file
@@ -0,0 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# (C) 2020 Smile (<http://www.smile.fr>)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
|
||||
import datetime
|
||||
import traceback
|
||||
|
||||
|
||||
def add_timing(original_method):
|
||||
def new_method(self, msg):
|
||||
delay = datetime.datetime.now() - self._logger_start
|
||||
msg += " after %sh %smin %ss" % tuple(str(delay).split(':'))
|
||||
return original_method(self, msg)
|
||||
return new_method
|
||||
|
||||
|
||||
def add_trace(original_method):
|
||||
def new_method(self, msg):
|
||||
stack = traceback.format_exc()
|
||||
stack = stack.replace('%', '%%')
|
||||
msg += '\n%s' % stack
|
||||
return original_method(self, msg)
|
||||
return new_method
|
||||
69
smile_log/views/smile_log_view.xml
Executable file
@@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="0">
|
||||
|
||||
<record id="smile_log_tree_view" model="ir.ui.view">
|
||||
<field name="name">Smile Log - Tree</field>
|
||||
<field name="model">smile.log</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Logs">
|
||||
<field name="log_date"/>
|
||||
<field name="pid"/>
|
||||
<field name="model_name"/>
|
||||
<field name="res_id"/>
|
||||
<field name="level"/>
|
||||
<field name="message"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="smile_log_search_view" model="ir.ui.view">
|
||||
<field name="name">Smile Log - Search</field>
|
||||
<field name="model">smile.log</field>
|
||||
<field name="type">search</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Logs">
|
||||
<field name="log_date"/>
|
||||
<field name="pid"/>
|
||||
<field name="model_name"/>
|
||||
<field name="level"/>
|
||||
<field name="message"/>
|
||||
<field name="res_id"/>
|
||||
<newline/>
|
||||
<group expand="0" string="Group By..." colspan="4" col="4">
|
||||
<filter string="Model" name="model_name" icon="terp-stage" domain="[]" context="{'group_by':'model_name'}"/>
|
||||
<filter string="PID" name="pid" icon="terp-account" domain="[]" context="{'group_by':'pid'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="act_smile_log" model="ir.actions.act_window">
|
||||
<field name="name">Logs</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">smile.log</field>
|
||||
<field name="view_mode">tree</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_logging" parent="base.menu_custom" name="Logging" sequence="110"/>
|
||||
<menuitem id="menu_smile_log" parent="menu_logging" name="Logs" action="act_smile_log"/>
|
||||
|
||||
<record id="smile_log_simple_tree_view" model="ir.ui.view">
|
||||
<field name="name">Smile Log - Tree</field>
|
||||
<field name="model">smile.log</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority">50</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Logs" editable="top" create="false">
|
||||
<field name="log_date"/>
|
||||
<field name="pid"/>
|
||||
<field name="level"/>
|
||||
<field name="message"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||