feat: Integrate apt-layer.sh with apt-ostree.py daemon via D-Bus
Some checks failed
Compile apt-layer (v2) / compile (push) Has been cancelled
Some checks failed
Compile apt-layer (v2) / compile (push) Has been cancelled
- Added 20-daemon-integration.sh scriptlet for D-Bus and daemon lifecycle management - Updated 99-main.sh with new daemon subcommands (start, stop, status, install, uninstall, test, layer, deploy, upgrade, rollback) - Enhanced help and usage text for daemon integration - Fixed bash syntax errors in daemon integration scriptlet - Updated compile.sh to include daemon integration in build process - Updated .gitignore to exclude src/rpm-ostree/ reference source - Updated CHANGELOG.md and TODO.md to document daemon integration milestone - Removed src/rpm-ostree/ from git tracking (reference only, not committed)
This commit is contained in:
parent
b913406438
commit
a23b4e53fd
69 changed files with 24120 additions and 8 deletions
226
src/apt-ostree.py/python/utils/logging.py
Normal file
226
src/apt-ostree.py/python/utils/logging.py
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
"""
|
||||
Structured logging for apt-ostree daemon
|
||||
"""
|
||||
|
||||
import logging
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
from datetime import datetime
|
||||
from typing import Dict, Any, Optional
|
||||
from logging.handlers import RotatingFileHandler
|
||||
|
||||
class StructuredFormatter(logging.Formatter):
|
||||
"""JSON formatter for structured logging"""
|
||||
|
||||
def format(self, record):
|
||||
log_entry = {
|
||||
'timestamp': datetime.utcnow().isoformat(),
|
||||
'level': record.levelname,
|
||||
'logger': record.name,
|
||||
'message': record.getMessage(),
|
||||
'module': record.module,
|
||||
'function': record.funcName,
|
||||
'line': record.lineno,
|
||||
'process_id': record.process,
|
||||
'thread_id': record.thread
|
||||
}
|
||||
|
||||
# Add exception info if present
|
||||
if record.exc_info:
|
||||
log_entry['exception'] = self.formatException(record.exc_info)
|
||||
|
||||
# Add extra fields
|
||||
if hasattr(record, 'extra_fields'):
|
||||
log_entry.update(record.extra_fields)
|
||||
|
||||
return json.dumps(log_entry)
|
||||
|
||||
class TextFormatter(logging.Formatter):
|
||||
"""Text formatter for human-readable logs"""
|
||||
|
||||
def format(self, record):
|
||||
# Add extra fields to message if present
|
||||
if hasattr(record, 'extra_fields'):
|
||||
extra_str = ' '.join([f"{k}={v}" for k, v in record.extra_fields.items()])
|
||||
record.msg = f"{record.msg} [{extra_str}]"
|
||||
|
||||
return super().format(record)
|
||||
|
||||
class AptOstreeLogger:
|
||||
"""Centralized logging for apt-ostree daemon"""
|
||||
|
||||
def __init__(self, config: Dict[str, Any]):
|
||||
self.config = config
|
||||
self.logging_config = config.get('daemon', {}).get('logging', {})
|
||||
self._setup_logging()
|
||||
|
||||
def _setup_logging(self):
|
||||
"""Setup logging configuration"""
|
||||
# Create logger
|
||||
logger = logging.getLogger('apt-ostree')
|
||||
logger.setLevel(getattr(logging, self.logging_config.get('level', 'INFO')))
|
||||
|
||||
# Clear existing handlers
|
||||
logger.handlers.clear()
|
||||
|
||||
# Console handler
|
||||
console_handler = logging.StreamHandler(sys.stdout)
|
||||
console_handler.setLevel(logging.INFO)
|
||||
|
||||
# Set formatter based on configuration
|
||||
if self.logging_config.get('format') == 'json':
|
||||
console_formatter = StructuredFormatter()
|
||||
else:
|
||||
console_formatter = TextFormatter(
|
||||
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
|
||||
console_handler.setFormatter(console_formatter)
|
||||
logger.addHandler(console_handler)
|
||||
|
||||
# File handler
|
||||
log_file = self.logging_config.get('file', '/var/log/apt-ostree/daemon.log')
|
||||
if log_file:
|
||||
try:
|
||||
# Ensure log directory exists
|
||||
os.makedirs(os.path.dirname(log_file), exist_ok=True)
|
||||
|
||||
# Create rotating file handler
|
||||
max_size = self._parse_size(self.logging_config.get('max_size', '100MB'))
|
||||
max_files = self.logging_config.get('max_files', 5)
|
||||
|
||||
file_handler = RotatingFileHandler(
|
||||
log_file,
|
||||
maxBytes=max_size,
|
||||
backupCount=max_files
|
||||
)
|
||||
file_handler.setLevel(logging.DEBUG)
|
||||
|
||||
# Always use JSON format for file logging
|
||||
file_formatter = StructuredFormatter()
|
||||
file_handler.setFormatter(file_formatter)
|
||||
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
except Exception as e:
|
||||
# Fallback to console only if file logging fails
|
||||
print(f"Failed to setup file logging: {e}", file=sys.stderr)
|
||||
|
||||
def _parse_size(self, size_str: str) -> int:
|
||||
"""Parse size string (e.g., '100MB') to bytes"""
|
||||
try:
|
||||
size_str = size_str.upper()
|
||||
if size_str.endswith('KB'):
|
||||
return int(size_str[:-2]) * 1024
|
||||
elif size_str.endswith('MB'):
|
||||
return int(size_str[:-2]) * 1024 * 1024
|
||||
elif size_str.endswith('GB'):
|
||||
return int(size_str[:-2]) * 1024 * 1024 * 1024
|
||||
else:
|
||||
return int(size_str)
|
||||
except (ValueError, AttributeError):
|
||||
return 100 * 1024 * 1024 # Default to 100MB
|
||||
|
||||
def get_logger(self, name: str) -> logging.Logger:
|
||||
"""Get logger with structured logging support"""
|
||||
logger = logging.getLogger(f'apt-ostree.{name}')
|
||||
|
||||
# Add extra_fields method for structured logging
|
||||
def log_with_fields(level, message, **kwargs):
|
||||
record = logger.makeRecord(
|
||||
logger.name, level, '', 0, message, (), None
|
||||
)
|
||||
record.extra_fields = kwargs
|
||||
logger.handle(record)
|
||||
|
||||
logger.log_with_fields = log_with_fields
|
||||
return logger
|
||||
|
||||
def setup_systemd_logging(self):
|
||||
"""Setup systemd journal logging"""
|
||||
try:
|
||||
import systemd.journal
|
||||
|
||||
# Add systemd journal handler
|
||||
logger = logging.getLogger('apt-ostree')
|
||||
journal_handler = systemd.journal.JournalHandler(
|
||||
level=logging.INFO,
|
||||
identifier='apt-ostree'
|
||||
)
|
||||
|
||||
# Use JSON formatter for systemd
|
||||
journal_formatter = StructuredFormatter()
|
||||
journal_handler.setFormatter(journal_formatter)
|
||||
|
||||
logger.addHandler(journal_handler)
|
||||
|
||||
except ImportError:
|
||||
# systemd-python not available
|
||||
pass
|
||||
except Exception as e:
|
||||
print(f"Failed to setup systemd logging: {e}", file=sys.stderr)
|
||||
|
||||
def setup_logging(level: int = logging.INFO, format_type: str = 'json'):
|
||||
"""Setup basic logging configuration"""
|
||||
logging.basicConfig(
|
||||
level=level,
|
||||
format='%(asctime)s %(name)s[%(process)d]: %(levelname)s: %(message)s',
|
||||
handlers=[
|
||||
logging.StreamHandler(sys.stdout),
|
||||
logging.StreamHandler(sys.stderr)
|
||||
]
|
||||
)
|
||||
|
||||
def setup_signal_handlers(handler):
|
||||
"""Setup signal handlers for graceful shutdown"""
|
||||
import signal
|
||||
|
||||
def signal_handler(signum, frame):
|
||||
handler(signum, frame)
|
||||
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
signal.signal(signal.SIGTERM, signal_handler)
|
||||
|
||||
def setup_systemd_notification():
|
||||
"""Setup systemd notification"""
|
||||
try:
|
||||
import systemd.daemon
|
||||
systemd.daemon.notify("READY=1")
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
def update_systemd_status(status: str):
|
||||
"""Update systemd status"""
|
||||
try:
|
||||
import systemd.daemon
|
||||
systemd.daemon.notify(f"STATUS={status}")
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
def require_root():
|
||||
"""Require root privileges"""
|
||||
if os.geteuid() != 0:
|
||||
raise PermissionError("This operation requires root privileges")
|
||||
|
||||
def check_ostree_boot():
|
||||
"""Check if system is booted via OSTree"""
|
||||
return os.path.exists("/run/ostree-booted")
|
||||
|
||||
def get_sysroot_path() -> str:
|
||||
"""Get sysroot path"""
|
||||
return os.environ.get("APT_OSTREE_SYSROOT", "/")
|
||||
|
||||
def setup_environment():
|
||||
"""Setup environment variables"""
|
||||
# Disable GVFS
|
||||
os.environ["GIO_USE_VFS"] = "local"
|
||||
|
||||
# Disable dconf for root
|
||||
if os.geteuid() == 0:
|
||||
os.environ["GSETTINGS_BACKEND"] = "memory"
|
||||
|
||||
# Disable filelist downloads for performance
|
||||
os.environ["DOWNLOAD_FILELISTS"] = "false"
|
||||
Loading…
Add table
Add a link
Reference in a new issue