File indexing completed on 2026-04-25 08:29:08
0001 """
0002 REST logging module for swf-monitor agents.
0003
0004 This module provides a simple way for agents to send logs to the swf-monitor
0005 database via REST API using standard Python logging.
0006 """
0007
0008 import logging
0009 import requests
0010 from datetime import datetime
0011
0012
0013 class RestLogHandler(logging.Handler):
0014 """Logging handler that sends logs to swf-monitor REST API."""
0015
0016 def __init__(self, base_url, app_name, instance_name, fallback_handler=None, timeout=5):
0017 super().__init__()
0018 self.logs_url = f"{base_url.rstrip('/')}/api/logs/"
0019 self.app_name = app_name
0020 self.instance_name = instance_name
0021 self.session = requests.Session()
0022 self.connection_failed = False
0023 self.fallback_handler = fallback_handler
0024 self.timeout = timeout
0025
0026
0027 self._load_proxy_settings()
0028
0029 def _load_proxy_settings(self):
0030 """Load proxy bypass settings from ~/.env file."""
0031 import os
0032 from pathlib import Path
0033
0034 env_file = Path.home() / ".env"
0035 if env_file.exists():
0036 with open(env_file) as f:
0037 for line in f:
0038 line = line.strip()
0039 if line and not line.startswith('#') and '=' in line:
0040 if line.startswith('export '):
0041 line = line[7:]
0042 key, value = line.split('=', 1)
0043 key = key.strip()
0044 value = value.strip('"\'')
0045
0046
0047 if key in ['NO_PROXY', 'no_proxy']:
0048 os.environ[key] = value
0049
0050
0051 if 'localhost' in self.logs_url or '127.0.0.1' in self.logs_url:
0052 for proxy_var in ['http_proxy', 'https_proxy', 'HTTP_PROXY', 'HTTPS_PROXY']:
0053 if proxy_var in os.environ:
0054 del os.environ[proxy_var]
0055
0056 def emit(self, record):
0057 """Send log record to REST API."""
0058 try:
0059 extra_data = {}
0060 for key in ('execution_id', 'workflow_name', 'run_id', 'username'):
0061 if hasattr(record, key):
0062 extra_data[key] = getattr(record, key)
0063
0064 log_data = {
0065 'app_name': self.app_name,
0066 'instance_name': self.instance_name,
0067 'timestamp': datetime.fromtimestamp(record.created).isoformat(),
0068 'level': record.levelno,
0069 'levelname': record.levelname,
0070 'message': record.getMessage(),
0071 'module': record.module or 'unknown',
0072 'funcname': record.funcName or 'unknown',
0073 'lineno': record.lineno or 0,
0074 'process': record.process or 0,
0075 'thread': record.thread or 0,
0076 'extra_data': extra_data or None,
0077 }
0078
0079 response = self.session.post(self.logs_url, json=log_data, timeout=self.timeout)
0080 if response.status_code == 400:
0081
0082 import logging
0083 debug_logger = logging.getLogger('swf_common_lib.rest_logging.debug')
0084 debug_logger.error(f"400 Bad Request details: {response.text}")
0085 debug_logger.error(f"Sent data: {log_data}")
0086 response.raise_for_status()
0087
0088
0089 if self.fallback_handler:
0090 self.fallback_handler.emit(record)
0091
0092 except Exception as e:
0093
0094 if not self.connection_failed:
0095
0096 import logging
0097 infra_logger = logging.getLogger('swf_common_lib.rest_logging')
0098 if not infra_logger.handlers:
0099
0100 console_handler = logging.StreamHandler()
0101 console_handler.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
0102 infra_logger.addHandler(console_handler)
0103 infra_logger.setLevel(logging.WARNING)
0104
0105 infra_logger.warning(f"REST logging failed to send log to swf-monitor at {self.logs_url}: {e}")
0106 infra_logger.warning("REST logging falling back to standard console logging")
0107 self.connection_failed = True
0108
0109
0110 if self.fallback_handler:
0111 self.fallback_handler.emit(record)
0112 else:
0113
0114 raise RuntimeError(
0115 f"REST logging failed and no fallback handler is configured. "
0116 f"This indicates setup_rest_logging() was not used correctly. "
0117 f"Original log message: {record.levelname}: {record.getMessage()}"
0118 )
0119
0120
0121 def setup_rest_logging(app_name, instance_name, base_url=None, timeout=10):
0122 """
0123 Setup REST logging for an agent.
0124
0125 Args:
0126 app_name: Name of your application/agent
0127 instance_name: Unique identifier for this instance
0128 base_url: URL of swf-monitor service
0129 timeout: Timeout in seconds for REST requests (default: 10)
0130
0131 Returns:
0132 Configured logger ready to use
0133 """
0134
0135 if base_url is None:
0136 import os
0137 base_url = os.getenv('SWF_MONITOR_HTTP_URL', 'http://localhost:8002')
0138
0139 logger = logging.getLogger(app_name)
0140
0141
0142 for handler in logger.handlers[:]:
0143 logger.removeHandler(handler)
0144
0145 logger.setLevel(logging.DEBUG)
0146
0147
0148 console_handler = logging.StreamHandler()
0149 console_handler.setLevel(logging.DEBUG)
0150 formatter = logging.Formatter('%(levelname)s: %(message)s')
0151 console_handler.setFormatter(formatter)
0152
0153
0154 rest_handler = RestLogHandler(base_url, app_name, instance_name, console_handler, timeout)
0155 logger.addHandler(rest_handler)
0156
0157 return logger