Files
Odoo-Modules/network_logger/models/network_monitor.py
2026-02-22 01:22:18 -05:00

165 lines
5.8 KiB
Python

# -*- 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()