debugger-help 4.2.1__tar.gz → 4.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {debugger_help-4.2.1 → debugger_help-4.2.3}/PKG-INFO +1 -1
- {debugger_help-4.2.1 → debugger_help-4.2.3}/debugger_help/__init__.py +1 -1
- {debugger_help-4.2.1 → debugger_help-4.2.3}/debugger_help/agent.py +46 -10
- {debugger_help-4.2.1 → debugger_help-4.2.3}/debugger_help.egg-info/PKG-INFO +1 -1
- {debugger_help-4.2.1 → debugger_help-4.2.3}/pyproject.toml +1 -1
- {debugger_help-4.2.1 → debugger_help-4.2.3}/README.md +0 -0
- {debugger_help-4.2.1 → debugger_help-4.2.3}/debugger_help/security.py +0 -0
- {debugger_help-4.2.1 → debugger_help-4.2.3}/debugger_help.egg-info/SOURCES.txt +0 -0
- {debugger_help-4.2.1 → debugger_help-4.2.3}/debugger_help.egg-info/dependency_links.txt +0 -0
- {debugger_help-4.2.1 → debugger_help-4.2.3}/debugger_help.egg-info/entry_points.txt +0 -0
- {debugger_help-4.2.1 → debugger_help-4.2.3}/debugger_help.egg-info/requires.txt +0 -0
- {debugger_help-4.2.1 → debugger_help-4.2.3}/debugger_help.egg-info/top_level.txt +0 -0
- {debugger_help-4.2.1 → debugger_help-4.2.3}/setup.cfg +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""debugger.help VPS Agent — Deep system monitoring + ComfyUI workflow management."""
|
|
2
|
-
__version__ = "4.2.
|
|
2
|
+
__version__ = "4.2.2"
|
|
@@ -79,7 +79,7 @@ INGEST_URL = os.environ.get("DEBUGGER_INGEST_URL", "")
|
|
|
79
79
|
SOURCE_NAME = os.environ.get("DEBUGGER_SOURCE", "vps-{}".format(socket.gethostname()))
|
|
80
80
|
PLATFORM = os.environ.get("DEBUGGER_PLATFORM", "Python (VPS)")
|
|
81
81
|
INTERVAL = int(os.environ.get("DEBUGGER_INTERVAL", "10"))
|
|
82
|
-
VERSION = "4.2.
|
|
82
|
+
VERSION = "4.2.3"
|
|
83
83
|
|
|
84
84
|
# Derive poll-commands URL from ingest URL
|
|
85
85
|
POLL_COMMANDS_URL = INGEST_URL.replace("/ingest", "/poll-commands") if INGEST_URL else ""
|
|
@@ -226,17 +226,30 @@ class StreamCapture(io.TextIOBase):
|
|
|
226
226
|
self._sending = False
|
|
227
227
|
self._recent_hashes = deque(maxlen=200)
|
|
228
228
|
|
|
229
|
+
# Patterns that identify the agent's own log output — never re-send these
|
|
230
|
+
_SELF_PATTERNS = ("debugger-agent", "debugger_agent", "[info]", "[warning]", "[error]",
|
|
231
|
+
"send failed", "send error", "command poll", "executing action",
|
|
232
|
+
"action ", "main loop error")
|
|
233
|
+
|
|
229
234
|
def write(self, text):
|
|
230
235
|
self.original.write(text)
|
|
231
|
-
|
|
232
|
-
|
|
236
|
+
stripped = text.strip()
|
|
237
|
+
if stripped and not self._sending:
|
|
238
|
+
lower = stripped.lower()
|
|
239
|
+
# Filter out the agent's own log lines to prevent feedback loops
|
|
240
|
+
if any(p in lower for p in self._SELF_PATTERNS):
|
|
241
|
+
self.buffer.append(stripped)
|
|
242
|
+
return len(text)
|
|
243
|
+
|
|
244
|
+
# Normalize: strip timestamps/numbers for smarter dedup
|
|
245
|
+
normalized = re.sub(r'\d{2,4}[:\-/\.]\d{2}[:\-/\.]\d{2,4}[\sT]?\d{0,2}:?\d{0,2}:?\d{0,2}\.?\d*|\b\d{4,}\b', '_', stripped[:300])
|
|
246
|
+
msg_hash = hash(normalized)
|
|
233
247
|
with self.lock:
|
|
234
|
-
# Deduplicate: skip if we've seen
|
|
248
|
+
# Deduplicate: skip if we've seen a similar message recently
|
|
235
249
|
if msg_hash in self._recent_hashes:
|
|
236
250
|
return len(text)
|
|
237
251
|
self._recent_hashes.append(msg_hash)
|
|
238
|
-
self.buffer.append(
|
|
239
|
-
lower = text.lower()
|
|
252
|
+
self.buffer.append(stripped)
|
|
240
253
|
detected_level = self.level
|
|
241
254
|
if any(kw in lower for kw in [
|
|
242
255
|
"error", "exception", "traceback", "failed", "critical",
|
|
@@ -245,7 +258,7 @@ class StreamCapture(io.TextIOBase):
|
|
|
245
258
|
detected_level = "error"
|
|
246
259
|
elif any(kw in lower for kw in ["warning", "warn", "deprecat"]):
|
|
247
260
|
detected_level = "warn"
|
|
248
|
-
self.pending.append((detected_level,
|
|
261
|
+
self.pending.append((detected_level, stripped[:2000]))
|
|
249
262
|
return len(text)
|
|
250
263
|
|
|
251
264
|
def flush(self):
|
|
@@ -713,11 +726,20 @@ def get_firewall_rules():
|
|
|
713
726
|
# =============================================================================
|
|
714
727
|
|
|
715
728
|
class LogFileWatcher(threading.Thread):
|
|
729
|
+
# Normalize timestamps/numbers for smarter dedup
|
|
730
|
+
_NORMALIZE_RE = re.compile(r'\d{2,4}[:\-/\.]\d{2}[:\-/\.]\d{2,4}[\sT]?\d{0,2}:?\d{0,2}:?\d{0,2}\.?\d*|\b\d{4,}\b')
|
|
731
|
+
|
|
716
732
|
def __init__(self, files):
|
|
717
733
|
super().__init__(daemon=True)
|
|
718
734
|
self.files = files
|
|
719
735
|
self.positions = {}
|
|
720
736
|
self._recent_hashes = deque(maxlen=500)
|
|
737
|
+
self._file_rate = {} # filepath -> (last_send_time, skip_count)
|
|
738
|
+
self._MIN_INTERVAL = 10 # min seconds between sends per file for similar content
|
|
739
|
+
|
|
740
|
+
def _normalize(self, text):
|
|
741
|
+
"""Strip timestamps and long numbers so similar lines produce the same hash."""
|
|
742
|
+
return self._NORMALIZE_RE.sub("_", text.strip()[:300])
|
|
721
743
|
|
|
722
744
|
def run(self):
|
|
723
745
|
for f in self.files:
|
|
@@ -737,12 +759,20 @@ class LogFileWatcher(threading.Thread):
|
|
|
737
759
|
self.positions[filepath] = fh.tell()
|
|
738
760
|
|
|
739
761
|
if new_lines.strip():
|
|
740
|
-
# Deduplicate
|
|
741
|
-
|
|
762
|
+
# Deduplicate with normalized content (ignores timestamps)
|
|
763
|
+
normalized = self._normalize(new_lines)
|
|
764
|
+
msg_hash = hash(normalized)
|
|
742
765
|
if msg_hash in self._recent_hashes:
|
|
743
766
|
continue
|
|
744
767
|
self._recent_hashes.append(msg_hash)
|
|
745
768
|
|
|
769
|
+
# Per-file rate limiting — max 1 log per file per 10s
|
|
770
|
+
now = time.time()
|
|
771
|
+
last_send, skip_count = self._file_rate.get(filepath, (0, 0))
|
|
772
|
+
if now - last_send < self._MIN_INTERVAL:
|
|
773
|
+
self._file_rate[filepath] = (last_send, skip_count + 1)
|
|
774
|
+
continue
|
|
775
|
+
|
|
746
776
|
level = "info"
|
|
747
777
|
lower = new_lines.lower()
|
|
748
778
|
if any(kw in lower for kw in ["error", "exception", "traceback", "failed", "critical"]):
|
|
@@ -750,13 +780,19 @@ class LogFileWatcher(threading.Thread):
|
|
|
750
780
|
elif "warn" in lower:
|
|
751
781
|
level = "warn"
|
|
752
782
|
|
|
783
|
+
msg = new_lines.strip()[:2000]
|
|
784
|
+
if skip_count > 0:
|
|
785
|
+
msg = "[+{} similar skipped] {}".format(skip_count, msg)
|
|
786
|
+
|
|
787
|
+
self._file_rate[filepath] = (now, 0)
|
|
788
|
+
|
|
753
789
|
send({
|
|
754
790
|
"type": "log",
|
|
755
791
|
"source": SOURCE_NAME,
|
|
756
792
|
"platform": PLATFORM,
|
|
757
793
|
"version": VERSION,
|
|
758
794
|
"level": level,
|
|
759
|
-
"message": "[file:{}] {}".format(os.path.basename(filepath),
|
|
795
|
+
"message": "[file:{}] {}".format(os.path.basename(filepath), msg),
|
|
760
796
|
"context": {"capturedFrom": "file_watcher", "file": filepath},
|
|
761
797
|
})
|
|
762
798
|
elif size < self.positions.get(filepath, 0):
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "debugger-help"
|
|
7
|
-
version = "4.2.
|
|
7
|
+
version = "4.2.3"
|
|
8
8
|
description = "debugger.help VPS Agent — Deep system monitoring for logs, GPU, PM2, Docker, and more"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|