1
1
import os
2
+ import uuid
2
3
from pathlib import Path
3
4
from typing import Optional
4
5
from clp_logging .handlers import CLPFileHandler , CLPLogLevelTimeout , EOF_CHAR , FLUSH_FRAME
@@ -41,15 +42,65 @@ def __init__(
41
42
loglevel_timeout = loglevel_timeout ,
42
43
)
43
44
45
+ @staticmethod
46
+ def _get_persistent_machine_uuid () -> str :
47
+ """
48
+ Retrieves a persistent machine-specific UUID:
49
+ - First checks the `iCtrl_LOG_UUID` environment variable.
50
+ - If not found, checks a system-wide file (`/etc/iCtrl_machine_log_uuid` or `~/.iCtrl_machine_log_uuid`).
51
+ - If still not found, generates a new UUID, saves it, and returns only the first 8 characters.
52
+ """
53
+ env_var_name = "iCtrl_LOG_UUID"
54
+
55
+ # 1️⃣ Check if UUID exists in an environment variable
56
+ env_uuid = os .getenv (env_var_name )
57
+ if env_uuid :
58
+ return env_uuid .strip ()[:8 ] # Limit to first 8 characters
59
+
60
+ # 2️⃣ Define a persistent file path
61
+ if os .name == "nt" : # Windows
62
+ uuid_file = Path (os .getenv ("APPDATA" , "C:/ProgramData" )) / "iCtrl_machine_log_uuid.txt"
63
+ else : # Linux/macOS
64
+ uuid_file = Path (
65
+ "/etc/iCtrl_machine_log_uuid" ) if os .geteuid () == 0 else Path .home () / ".iCtrl_machine_log_uuid"
66
+
67
+ # 3️⃣ Check if the file exists and read the UUID
68
+ if uuid_file .exists ():
69
+ with open (uuid_file , "r" ) as f :
70
+ machine_uuid = f .read ().strip ()[:8 ] # Limit to first 8 characters
71
+ os .environ [env_var_name ] = machine_uuid # Store in environment for future use
72
+ return machine_uuid
73
+
74
+ # 4️⃣ Generate a new UUID and store only the first 8 characters
75
+ new_uuid = str (uuid .uuid4 ())[:8 ]
76
+
77
+ try :
78
+ with open (uuid_file , "w" ) as f :
79
+ f .write (new_uuid )
80
+ os .environ [env_var_name ] = new_uuid # Store in environment
81
+ except PermissionError :
82
+ print (f"Warning: Could not save machine UUID to { uuid_file } . Using in-memory only." )
83
+
84
+ return new_uuid
85
+
44
86
def _generate_log_filename (self ) -> Path :
45
87
"""
46
- Generate a log filename with the timestamp and prefix.
88
+ Generate a log filename with the prefix, persistent UUID and timestamp.
89
+ If a file with the same name already exists, append a counter (_1, _2, etc.).
47
90
"""
48
- start_time = datetime .now ()
49
- timestamp = start_time .strftime (self .timestamp_format )
50
- self .current_log_file = Path (f"{ self .log_dir } /{ self .filename_prefix } _{ timestamp } .clp.zst" )
91
+ uuid_str = self ._get_persistent_machine_uuid () # Get the persistent UUID
92
+ timestamp = datetime .now ().strftime (self .timestamp_format )
93
+
94
+ base_filename = f"{ self .filename_prefix } _{ uuid_str } _{ timestamp } "
95
+ file_path = self .log_dir / f"{ base_filename } .clp.zst"
51
96
52
- return self .current_log_file
97
+ # Check for name conflicts and append a counter if necessary
98
+ counter = 1
99
+ while file_path .exists ():
100
+ file_path = self .log_dir / f"{ base_filename } _{ counter } .clp.zst"
101
+ counter += 1
102
+
103
+ return file_path
53
104
54
105
def _should_rotate (self ) -> bool :
55
106
"""
@@ -62,7 +113,7 @@ def _should_rotate(self) -> bool:
62
113
63
114
def _rotate (self ) -> None :
64
115
"""
65
- Perform log rotation by finalizing the current stream, creating a new log file,
116
+ Perform log rotation by finalizing the current stream, creating a new log file,
66
117
and reinitializing the handler with the new stream.
67
118
68
119
This method is responsible for:
@@ -73,7 +124,7 @@ def _rotate(self) -> None:
73
124
5. Opening a new stream for the new log file and reinitializing the handler to use it.
74
125
6. Removing old log files if the number of backups exceeds the specified backup count.
75
126
76
- Thread safety is ensured by acquiring and releasing the handler's lock during the
127
+ Thread safety is ensured by acquiring and releasing the handler's lock during the
77
128
transition between streams.
78
129
"""
79
130
try :
@@ -82,7 +133,7 @@ def _rotate(self) -> None:
82
133
finally :
83
134
self .ostream .close ()
84
135
85
- # Generate a new log filename
136
+ # Generate a new log filename (same UUID, new timestamp)
86
137
new_log_file = self ._generate_log_filename ()
87
138
88
139
# Initialize the new stream
@@ -103,13 +154,15 @@ def _rotate(self) -> None:
103
154
104
155
def _remove_old_backups (self ) -> None :
105
156
"""
106
- Remove old log files exceeding the backup count.
157
+ Remove old log files exceeding the backup count, but only for the current machine UUID .
107
158
"""
108
159
if not self .backup_count :
109
160
return
110
161
162
+ uuid_str = self ._get_persistent_machine_uuid () # Get the persistent UUID
163
+
111
164
log_files = sorted (
112
- self .log_dir .glob (f"{ self .filename_prefix } _*.clp.zst" ),
165
+ self .log_dir .glob (f"{ self .filename_prefix } _{ uuid_str } _ *.clp.zst" ),
113
166
key = os .path .getmtime ,
114
167
)
115
168
if len (log_files ) > self .backup_count :
0 commit comments