first commit

This commit is contained in:
Ruslan Grak
2025-01-07 10:00:02 +03:00
commit 626d8d3c56
349 changed files with 44175 additions and 0 deletions

134
smile_log/README.rst Executable file
View 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
View 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
View 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
View 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
View 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
View 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

View 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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 smile_log_user smile_log group_user model_smile_log base.group_user 1 1 0 0

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View 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 &gt; Technical &gt; Logging</tt>&gt; 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 &gt; Technical &gt; Automation &gt; 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&amp;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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

6
smile_log/tools/__init__.py Executable file
View 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
View 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
View 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
View 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

View 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>