-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathutils.py
More file actions
136 lines (108 loc) · 3.81 KB
/
Copy pathutils.py
File metadata and controls
136 lines (108 loc) · 3.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
"""
Utility functions for Linux Incident Time Machine.
"""
import logging
import sys
import threading
from collections import OrderedDict
from pathlib import Path
from typing import Optional
import pwd
def setup_logging(
log_level: str = "INFO",
log_file: Optional[Path] = None,
quiet: bool = False
) -> logging.Logger:
"""
Setup professional logging configuration.
Args:
log_level: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
log_file: Optional file path for log output
quiet: If True, only log warnings and errors
Returns:
Configured logger instance
"""
level = logging.WARNING if quiet else getattr(logging, log_level.upper(), logging.INFO)
handlers = [logging.StreamHandler(sys.stderr)]
if log_file:
handlers.append(logging.FileHandler(log_file))
logging.basicConfig(
level=level,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
handlers=handlers
)
return logging.getLogger(__name__)
class UserResolver:
"""Resolve UID to username with thread-safe LRU cache."""
_cache: OrderedDict[int, Optional[str]] = OrderedDict()
_max_cache_size: int = 10000
_lock = threading.Lock()
@classmethod
def resolve(cls, uid: int) -> Optional[str]:
"""
Resolve UID to username with thread-safe LRU cache.
Args:
uid: User ID to resolve
Returns:
Username or None if not found
"""
# Check cache first (thread-safe)
with cls._lock:
if uid in cls._cache:
# Move to end (most recently used)
cls._cache.move_to_end(uid)
return cls._cache[uid]
# Resolve UID (outside lock to avoid blocking)
try:
user_info = pwd.getpwuid(uid)
username = user_info.pw_name
except KeyError:
username = None
except Exception:
# Fallback if pwd module is not available or fails
username = None
# Update cache (thread-safe)
with cls._lock:
# Check again in case another thread added it
if uid in cls._cache:
cls._cache.move_to_end(uid)
return cls._cache[uid]
# Check cache size and evict if needed
if len(cls._cache) >= cls._max_cache_size:
# Remove oldest entry (LRU eviction)
cls._cache.popitem(last=False)
# Add to cache
cls._cache[uid] = username
cls._cache.move_to_end(uid)
return username
@classmethod
def clear_cache(cls):
"""Clear the username cache (thread-safe)."""
with cls._lock:
cls._cache.clear()
@classmethod
def get_cache_size(cls) -> int:
"""Get current cache size (thread-safe)."""
with cls._lock:
return len(cls._cache)
def validate_audit_log_file(filepath: Path, max_size_mb: int = 1000) -> None:
"""
Validate audit log file before processing.
Args:
filepath: Path to audit log file
max_size_mb: Maximum file size in MB
Raises:
FileNotFoundError: If file doesn't exist
ValueError: If file is empty or too large
"""
if not filepath.exists():
raise FileNotFoundError(f"Audit log file not found: {filepath}")
file_size = filepath.stat().st_size
if file_size == 0:
raise ValueError(f"Audit log file is empty: {filepath}")
size_mb = file_size / (1024 * 1024)
if size_mb > max_size_mb:
raise ValueError(
f"Audit log file too large ({size_mb:.1f}MB > {max_size_mb}MB): {filepath}"
)