ghosttrap-cli 0.3.20__tar.gz → 0.3.21__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.
- {ghosttrap_cli-0.3.20 → ghosttrap_cli-0.3.21}/PKG-INFO +1 -1
- {ghosttrap_cli-0.3.20 → ghosttrap_cli-0.3.21}/ghosttrap_cli/cli.py +49 -3
- {ghosttrap_cli-0.3.20 → ghosttrap_cli-0.3.21}/ghosttrap_cli.egg-info/PKG-INFO +1 -1
- {ghosttrap_cli-0.3.20 → ghosttrap_cli-0.3.21}/pyproject.toml +1 -1
- {ghosttrap_cli-0.3.20 → ghosttrap_cli-0.3.21}/README.md +0 -0
- {ghosttrap_cli-0.3.20 → ghosttrap_cli-0.3.21}/ghosttrap_cli/__init__.py +0 -0
- {ghosttrap_cli-0.3.20 → ghosttrap_cli-0.3.21}/ghosttrap_cli.egg-info/SOURCES.txt +0 -0
- {ghosttrap_cli-0.3.20 → ghosttrap_cli-0.3.21}/ghosttrap_cli.egg-info/dependency_links.txt +0 -0
- {ghosttrap_cli-0.3.20 → ghosttrap_cli-0.3.21}/ghosttrap_cli.egg-info/entry_points.txt +0 -0
- {ghosttrap_cli-0.3.20 → ghosttrap_cli-0.3.21}/ghosttrap_cli.egg-info/requires.txt +0 -0
- {ghosttrap_cli-0.3.20 → ghosttrap_cli-0.3.21}/ghosttrap_cli.egg-info/top_level.txt +0 -0
- {ghosttrap_cli-0.3.20 → ghosttrap_cli-0.3.21}/setup.cfg +0 -0
|
@@ -4,6 +4,7 @@ import argparse
|
|
|
4
4
|
import asyncio
|
|
5
5
|
import json
|
|
6
6
|
import os
|
|
7
|
+
import signal
|
|
7
8
|
import subprocess
|
|
8
9
|
import sys
|
|
9
10
|
import tempfile
|
|
@@ -13,7 +14,20 @@ import urllib.request
|
|
|
13
14
|
|
|
14
15
|
import websockets
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
|
|
18
|
+
def _harden_signals():
|
|
19
|
+
"""Explicitly ignore SIGURG so no process supervisor can nudge peek out
|
|
20
|
+
with an out-of-band signal. POSIX default is already ignore; this makes
|
|
21
|
+
it defensive against layers that change the disposition.
|
|
22
|
+
"""
|
|
23
|
+
s = getattr(signal, "SIGURG", None)
|
|
24
|
+
if s is not None:
|
|
25
|
+
try:
|
|
26
|
+
signal.signal(s, signal.SIG_IGN)
|
|
27
|
+
except (OSError, ValueError):
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
__version__ = "0.3.21"
|
|
17
31
|
|
|
18
32
|
GHOSTTRAP_SERVER = "wss://ghosttrap.io/stream/"
|
|
19
33
|
CONFIG_DIR = os.path.expanduser("~/.ghosttrap")
|
|
@@ -416,6 +430,31 @@ async def setup(server_url, token):
|
|
|
416
430
|
sys.exit(1)
|
|
417
431
|
|
|
418
432
|
|
|
433
|
+
# Exception types we knowingly retry on. All represent a transient network/transport
|
|
434
|
+
# blip rather than a semantic error from the server:
|
|
435
|
+
# - ConnectionClosed: peer closed the WebSocket (either side of the handshake)
|
|
436
|
+
# - InvalidStatus: non-101 HTTP response during upgrade (e.g. 502 from a proxy)
|
|
437
|
+
# - ConnectionError: builtin — refused, reset, unreachable
|
|
438
|
+
# - OSError: DNS failure, transient socket errors (gaierror is a subclass)
|
|
439
|
+
# Anything else escapes and prints a diagnostic line first so we can add it here
|
|
440
|
+
# in the next release. Semantic rejections from the server ({"type": "rejected"})
|
|
441
|
+
# raise SystemExit, which we deliberately do NOT catch — those are real errors.
|
|
442
|
+
_RETRYABLE = (
|
|
443
|
+
websockets.ConnectionClosed,
|
|
444
|
+
websockets.InvalidStatus,
|
|
445
|
+
ConnectionError,
|
|
446
|
+
OSError,
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def _log_unexpected(e):
|
|
451
|
+
print(
|
|
452
|
+
f"unexpected {type(e).__module__}.{type(e).__name__}: {e} — "
|
|
453
|
+
f"not currently in the retry list; please report so we can add it.",
|
|
454
|
+
file=sys.stderr,
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
|
|
419
458
|
async def watch(server_url, token):
|
|
420
459
|
config = _load_config()
|
|
421
460
|
print(f"connecting to {server_url}...", file=sys.stderr)
|
|
@@ -424,8 +463,11 @@ async def watch(server_url, token):
|
|
|
424
463
|
try:
|
|
425
464
|
await _connect_and_handle(server_url, token, config, once=False)
|
|
426
465
|
print("connection closed by server, reconnecting...", file=sys.stderr)
|
|
427
|
-
except
|
|
466
|
+
except _RETRYABLE:
|
|
428
467
|
print("connection lost, reconnecting...", file=sys.stderr)
|
|
468
|
+
except Exception as e:
|
|
469
|
+
_log_unexpected(e)
|
|
470
|
+
raise
|
|
429
471
|
await asyncio.sleep(60)
|
|
430
472
|
|
|
431
473
|
|
|
@@ -438,8 +480,11 @@ async def peek(server_url, token):
|
|
|
438
480
|
if got_error:
|
|
439
481
|
return
|
|
440
482
|
print("connection closed by server, reconnecting...", file=sys.stderr)
|
|
441
|
-
except
|
|
483
|
+
except _RETRYABLE:
|
|
442
484
|
print("connection lost, reconnecting...", file=sys.stderr)
|
|
485
|
+
except Exception as e:
|
|
486
|
+
_log_unexpected(e)
|
|
487
|
+
raise
|
|
443
488
|
await asyncio.sleep(60)
|
|
444
489
|
|
|
445
490
|
|
|
@@ -676,6 +721,7 @@ def last(do_clear=False, requested=None):
|
|
|
676
721
|
|
|
677
722
|
|
|
678
723
|
def main():
|
|
724
|
+
_harden_signals()
|
|
679
725
|
parser = argparse.ArgumentParser(prog="ghosttrap", description="Watch for errors from ghosttrap.io")
|
|
680
726
|
sub = parser.add_subparsers(dest="command")
|
|
681
727
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|