This commit is contained in:
gsinghpal
2026-02-22 01:37:50 -05:00
parent 5200d5baf0
commit d6bac8e623
1550 changed files with 263540 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import models

View File

@@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
{
'name': 'Network Traffic Logger',
'version': '19.0.1.0.0',
'category': 'Tools',
'summary': 'Logs and displays all outgoing HTTP requests from Odoo',
'description': """
Network Traffic Logger for Odoo 19
==================================
This module logs ALL outgoing HTTP requests made by Odoo to help identify
external communications.
**Features:**
- Logs all requests.get(), requests.post(), requests.put() calls
- Stores logs in database for viewing in Odoo
- Dashboard showing recent activity
- Filter by Odoo external calls
- Clear log functionality
**Access:**
Settings → Technical → Network Traffic Logs
""",
'author': 'Fusion Development',
'license': 'LGPL-3',
'depends': ['base'],
'data': [
'security/ir.model.access.csv',
'views/network_log_views.xml',
],
'installable': True,
'auto_install': False,
'application': True,
}

View File

@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
from . import network_log
from . import network_monitor

View File

@@ -0,0 +1,126 @@
# -*- coding: utf-8 -*-
"""
Network Log Model - Stores HTTP request logs in the database.
"""
import logging
from datetime import timedelta
from urllib.parse import urlparse
from odoo import api, fields, models
_logger = logging.getLogger(__name__)
class NetworkLog(models.Model):
_name = 'network.log'
_description = 'Network Traffic Log'
_order = 'timestamp desc'
timestamp = fields.Datetime(
string='Timestamp',
default=fields.Datetime.now,
required=True,
index=True,
)
method = fields.Selection([
('GET', 'GET'),
('POST', 'POST'),
('PUT', 'PUT'),
('DELETE', 'DELETE'),
('PATCH', 'PATCH'),
('HEAD', 'HEAD'),
('OPTIONS', 'OPTIONS'),
], string='Method', required=True, index=True)
url = fields.Char(string='URL', required=True, index=True)
domain = fields.Char(string='Domain', compute='_compute_domain', store=True, index=True)
is_odoo_call = fields.Boolean(
string='Odoo External Call',
default=False,
index=True,
help='True if this request was made to an Odoo server',
)
status_code = fields.Integer(string='Status Code')
status_type = fields.Selection([
('success', 'Success'),
('redirect', 'Redirect'),
('client_error', 'Client Error'),
('server_error', 'Server Error'),
('blocked', 'Blocked'),
('error', 'Error'),
('pending', 'Pending'),
], string='Status Type', compute='_compute_status_type', store=True)
response_time = fields.Float(string='Response Time (s)', digits=(10, 4))
error_message = fields.Text(string='Error Message')
request_headers = fields.Text(string='Request Headers')
request_body = fields.Text(string='Request Body')
@api.depends('url')
def _compute_domain(self):
for record in self:
try:
parsed = urlparse(record.url or '')
record.domain = parsed.netloc or 'unknown'
except Exception:
record.domain = 'unknown'
@api.depends('status_code', 'error_message')
def _compute_status_type(self):
for record in self:
if record.error_message:
if 'blocked' in (record.error_message or '').lower():
record.status_type = 'blocked'
else:
record.status_type = 'error'
elif not record.status_code:
record.status_type = 'pending'
elif 200 <= record.status_code < 300:
record.status_type = 'success'
elif 300 <= record.status_code < 400:
record.status_type = 'redirect'
elif 400 <= record.status_code < 500:
record.status_type = 'client_error'
elif record.status_code >= 500:
record.status_type = 'server_error'
else:
record.status_type = 'pending'
@api.model
def log_request(self, method, url, is_odoo_call=False, status_code=None,
response_time=None, error_message=None):
"""Create a new network log entry."""
try:
vals = {
'method': method.upper(),
'url': url[:2048] if url else '',
'is_odoo_call': is_odoo_call,
'status_code': status_code,
'response_time': response_time,
'error_message': error_message[:4096] if error_message else None,
}
return self.sudo().create(vals)
except Exception as e:
_logger.warning("Failed to log network request: %s", e)
return self.browse()
@api.model
def clear_old_logs(self, days=7):
"""Delete logs older than specified days."""
cutoff = fields.Datetime.now() - timedelta(days=days)
old_logs = self.search([('timestamp', '<', cutoff)])
count = len(old_logs)
old_logs.unlink()
_logger.info("Cleared %d network logs older than %d days", count, days)
return count
@api.model
def clear_all_logs(self):
"""Delete all network logs."""
count = self.search_count([])
self.search([]).unlink()
_logger.info("Cleared all %d network logs", count)
return count

View File

@@ -0,0 +1,164 @@
# -*- coding: utf-8 -*-
"""
Network Traffic Monitor - Logs all outgoing HTTP requests from Odoo.
This module patches the 'requests' library to log every HTTP call.
"""
import logging
import functools
import threading
from datetime import datetime
_logger = logging.getLogger(__name__)
_network_logger = logging.getLogger('odoo.network_traffic')
# Store for recent requests (in-memory buffer)
_request_log = []
_MAX_LOG_SIZE = 1000
_log_lock = threading.Lock()
# Flag to prevent recursive logging
_is_logging = threading.local()
def _is_odoo_call(url):
"""Check if URL is an Odoo external call."""
odoo_domains = [
'odoo.com', 'odoo.sh', 'iap.odoo', 'api.odoo',
'services.odoo', 'apps.odoo', 'accounts.odoo',
'upgrade.odoo', 'partner-autocomplete.odoo',
]
url_lower = str(url).lower()
return any(domain in url_lower for domain in odoo_domains)
def _save_to_database(log_entry):
"""Save log entry to database (async-safe)."""
try:
if getattr(_is_logging, 'value', False):
return
_is_logging.value = True
try:
from odoo import api, SUPERUSER_ID
from odoo.modules.registry import Registry
db_name = getattr(threading.current_thread(), 'dbname', None)
if not db_name:
from odoo.tools import config
db_name = config.get('db_name')
if db_name:
registry = Registry(db_name)
with registry.cursor() as cr:
env = api.Environment(cr, SUPERUSER_ID, {})
if 'network.log' in env:
env['network.log'].log_request(
method=log_entry.get('method', 'GET'),
url=log_entry.get('url', ''),
is_odoo_call=log_entry.get('is_odoo_call', False),
status_code=log_entry.get('status_code'),
response_time=log_entry.get('response_time_seconds'),
error_message=log_entry.get('error'),
)
cr.commit()
except Exception as e:
_logger.debug("Could not save network log to database: %s", e)
finally:
_is_logging.value = False
def _log_request(method, url, **kwargs):
"""Log details of an HTTP request."""
timestamp = datetime.now().isoformat()
log_entry = {
'timestamp': timestamp,
'method': method.upper(),
'url': str(url),
'timeout': kwargs.get('timeout'),
'is_odoo_call': _is_odoo_call(url),
}
if log_entry['is_odoo_call']:
_network_logger.warning("ODOO EXTERNAL CALL: %s %s", method.upper(), url)
else:
_network_logger.info("HTTP Request: %s %s", method.upper(), url)
with _log_lock:
_request_log.append(log_entry)
if len(_request_log) > _MAX_LOG_SIZE:
_request_log.pop(0)
return log_entry
def _log_response(log_entry, response=None, error=None):
"""Log the response of an HTTP request."""
if response is not None:
log_entry['status_code'] = response.status_code
if hasattr(response, 'elapsed') and hasattr(response.elapsed, 'total_seconds'):
log_entry['response_time_seconds'] = response.elapsed.total_seconds()
_network_logger.info("Response: %s %s -> %d",
log_entry['method'], log_entry['url'], response.status_code)
elif error is not None:
log_entry['error'] = str(error)
_network_logger.error("Error: %s %s -> %s",
log_entry['method'], log_entry['url'], error)
_save_to_database(log_entry)
def _wrap_request_method(original_method, method_name):
"""Wrap a requests method to add logging."""
@functools.wraps(original_method)
def wrapper(url, *args, **kwargs):
if 'localhost:65535' in str(url):
return original_method(url, *args, **kwargs)
log_entry = _log_request(method_name, url, **kwargs)
try:
response = original_method(url, *args, **kwargs)
_log_response(log_entry, response=response)
return response
except Exception as e:
_log_response(log_entry, error=e)
raise
return wrapper
def patch_requests_library():
"""Monkey-patch the requests library to log all HTTP calls."""
try:
import requests
if not hasattr(requests, '_original_get'):
requests._original_get = requests.get
requests._original_post = requests.post
requests._original_put = requests.put
requests._original_delete = requests.delete
requests._original_patch = requests.patch
requests._original_head = requests.head
requests._original_options = requests.options
requests.get = _wrap_request_method(requests._original_get, 'GET')
requests.post = _wrap_request_method(requests._original_post, 'POST')
requests.put = _wrap_request_method(requests._original_put, 'PUT')
requests.delete = _wrap_request_method(requests._original_delete, 'DELETE')
requests.patch = _wrap_request_method(requests._original_patch, 'PATCH')
requests.head = _wrap_request_method(requests._original_head, 'HEAD')
requests.options = _wrap_request_method(requests._original_options, 'OPTIONS')
_logger.info("NETWORK LOGGER: All HTTP requests are now being monitored")
except ImportError:
_logger.warning("Could not import requests library for patching")
except Exception as e:
_logger.error("Error patching requests library: %s", e)
# Apply patch when module is loaded
patch_requests_library()

View File

@@ -0,0 +1,4 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_network_log_admin,network.log.admin,model_network_log,base.group_system,1,1,1,1
access_network_log_user,network.log.user,model_network_log,base.group_user,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_network_log_admin network.log.admin model_network_log base.group_system 1 1 1 1
3 access_network_log_user network.log.user model_network_log base.group_user 1 0 0 0

View File

@@ -0,0 +1,103 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Tree View -->
<record id="view_network_log_list" model="ir.ui.view">
<field name="name">network.log.list</field>
<field name="model">network.log</field>
<field name="arch" type="xml">
<list string="Network Logs" default_order="timestamp desc"
decoration-danger="is_odoo_call"
decoration-success="status_type == 'success'">
<field name="timestamp"/>
<field name="method"/>
<field name="domain"/>
<field name="url" optional="hide"/>
<field name="is_odoo_call"/>
<field name="status_code"/>
<field name="status_type"/>
<field name="response_time" optional="show"/>
</list>
</field>
</record>
<!-- Form View -->
<record id="view_network_log_form" model="ir.ui.view">
<field name="name">network.log.form</field>
<field name="model">network.log</field>
<field name="arch" type="xml">
<form string="Network Log Entry" create="false" edit="false">
<sheet>
<group>
<group string="Request Details">
<field name="timestamp"/>
<field name="method"/>
<field name="domain"/>
<field name="url"/>
<field name="is_odoo_call"/>
</group>
<group string="Response Details">
<field name="status_code"/>
<field name="status_type"/>
<field name="response_time"/>
</group>
</group>
<group string="Error Details" invisible="error_message == False">
<field name="error_message" readonly="1"/>
</group>
</sheet>
</form>
</field>
</record>
<!-- Search View - Minimal -->
<record id="view_network_log_search" model="ir.ui.view">
<field name="name">network.log.search</field>
<field name="model">network.log</field>
<field name="arch" type="xml">
<search>
<field name="url"/>
<field name="domain"/>
</search>
</field>
</record>
<!-- Action -->
<record id="action_network_log" model="ir.actions.act_window">
<field name="name">Network Traffic Logs</field>
<field name="res_model">network.log</field>
<field name="view_mode">list,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
No network traffic logged yet
</p>
</field>
</record>
<!-- Action for Odoo Calls Only -->
<record id="action_network_log_odoo_calls" model="ir.actions.act_window">
<field name="name">Odoo External Calls</field>
<field name="res_model">network.log</field>
<field name="view_mode">list,form</field>
<field name="domain">[('is_odoo_call', '=', True)]</field>
</record>
<!-- Menu Items -->
<menuitem id="menu_network_logger_root"
name="Network Logger"
parent="base.menu_administration"
sequence="100"/>
<menuitem id="menu_network_log_all"
name="All Traffic"
parent="menu_network_logger_root"
action="action_network_log"
sequence="10"/>
<menuitem id="menu_network_log_odoo"
name="Odoo External Calls"
parent="menu_network_logger_root"
action="action_network_log_odoo_calls"
sequence="20"/>
</odoo>