feat: improve error handling and reporting

This commit is contained in:
Vlad Stan 2023-06-26 12:20:06 +03:00
parent dabc26f8a6
commit f8d578e6aa
6 changed files with 101 additions and 42 deletions

View file

@ -8,12 +8,18 @@ from pydantic import BaseModel, Field
from lnbits.helpers import urlsafe_short_hash from lnbits.helpers import urlsafe_short_hash
class RelayStatus(BaseModel):
num_sent_events: Optional[int] = 0
num_received_events: Optional[int] = 0
error_counter: Optional[int] = 0
error_list: Optional[List] = []
class Relay(BaseModel): class Relay(BaseModel):
id: Optional[str] = None id: Optional[str] = None
url: Optional[str] = None url: Optional[str] = None
connected: Optional[bool] = None connected: Optional[bool] = None
connected_string: Optional[str] = None connected_string: Optional[str] = None
status: Optional[str] = None status: Optional[RelayStatus] = None
active: Optional[bool] = None active: Optional[bool] = None
ping: Optional[int] = None ping: Optional[int] = None

View file

@ -3,13 +3,20 @@ class ClientMessageType:
REQUEST = "REQ" REQUEST = "REQ"
CLOSE = "CLOSE" CLOSE = "CLOSE"
class RelayMessageType: class RelayMessageType:
EVENT = "EVENT" EVENT = "EVENT"
NOTICE = "NOTICE" NOTICE = "NOTICE"
END_OF_STORED_EVENTS = "EOSE" END_OF_STORED_EVENTS = "EOSE"
COMMAND_RESULT = "OK"
@staticmethod @staticmethod
def is_valid(type: str) -> bool: def is_valid(type: str) -> bool:
if type == RelayMessageType.EVENT or type == RelayMessageType.NOTICE or type == RelayMessageType.END_OF_STORED_EVENTS: if (
type == RelayMessageType.EVENT
or type == RelayMessageType.NOTICE
or type == RelayMessageType.END_OF_STORED_EVENTS
or type == RelayMessageType.COMMAND_RESULT
):
return True return True
return False return False

View file

@ -2,6 +2,7 @@ import json
import time import time
from queue import Queue from queue import Queue
from threading import Lock from threading import Lock
from typing import List
from loguru import logger from loguru import logger
from websocket import WebSocketApp from websocket import WebSocketApp
@ -39,6 +40,7 @@ class Relay:
self.shutdown: bool = False self.shutdown: bool = False
self.error_counter: int = 0 self.error_counter: int = 0
self.error_threshold: int = 100 self.error_threshold: int = 100
self.error_list: List[str] = []
self.num_received_events: int = 0 self.num_received_events: int = 0
self.num_sent_events: int = 0 self.num_sent_events: int = 0
self.num_subscriptions: int = 0 self.num_subscriptions: int = 0
@ -100,7 +102,7 @@ class Relay:
self.ws.send(message) self.ws.send(message)
except Exception as e: except Exception as e:
if self.shutdown: if self.shutdown:
logger.warning(f"Closing queue worker for {self.url}") logger.warning(f"Closing queue worker for '{self.url}'.")
break break
else: else:
time.sleep(0.1) time.sleep(0.1)
@ -133,18 +135,14 @@ class Relay:
logger.warning(f"Connection to relay {self.url} closed. Status: '{status_code}'. Message: '{message}'.") logger.warning(f"Connection to relay {self.url} closed. Status: '{status_code}'. Message: '{message}'.")
self.close() self.close()
def _on_message(self, _, message: str): def _on_message(self, _, message: str):
if self._is_valid_message(message): if self._is_valid_message(message):
self.num_received_events += 1 self.num_received_events += 1
self.message_pool.add_message(message, self.url) self.message_pool.add_message(message, self.url)
else:
logger.debug(f"Invalid relay message: '{message}'.")
def _on_error(self, _, error): def _on_error(self, _, error):
logger.warning(f"Relay error: '{str(error)}'") logger.warning(f"Relay error: '{str(error)}'")
self._append_error_message(str(error))
self.connected = False self.connected = False
self.error_counter += 1 self.error_counter += 1
@ -161,33 +159,57 @@ class Relay:
message_json = json.loads(message) message_json = json.loads(message)
message_type = message_json[0] message_type = message_json[0]
if not RelayMessageType.is_valid(message_type): if not RelayMessageType.is_valid(message_type):
return False return False
if message_type == RelayMessageType.EVENT: if message_type == RelayMessageType.EVENT:
if not len(message_json) == 3: return self._is_valid_event_message(message_json)
return False
if message_type == RelayMessageType.COMMAND_RESULT:
subscription_id = message_json[1] return self._is_valid_command_result_message(message, message_json)
with self.lock:
if subscription_id not in self.subscriptions:
return False
e = message_json[2]
event = Event(
e["content"],
e["pubkey"],
e["created_at"],
e["kind"],
e["tags"],
e["sig"],
)
if not event.verify():
return False
with self.lock:
subscription = self.subscriptions[subscription_id]
if subscription.filters and not subscription.filters.match(event):
return False
return True return True
def _is_valid_event_message(self, message_json):
if not len(message_json) == 3:
return False
subscription_id = message_json[1]
with self.lock:
if subscription_id not in self.subscriptions:
return False
e = message_json[2]
event = Event(
e["content"],
e["pubkey"],
e["created_at"],
e["kind"],
e["tags"],
e["sig"],
)
if not event.verify():
return False
with self.lock:
subscription = self.subscriptions[subscription_id]
if subscription.filters and not subscription.filters.match(event):
return False
return True
def _is_valid_command_result_message(self, message, message_json):
if not len(message_json) < 3:
return False
if message_json[2] != True:
logger.warning(f"Relay '{self.url}' negative command result: '{message}'")
self._append_error_message(message)
return False
return True
def _append_error_message(self, message):
self.error_list = ([message] + self.error_list)[:20]

View file

@ -102,4 +102,5 @@ class RelayManager:
self.remove_relay(relay.url) self.remove_relay(relay.url)
new_relay = self.add_relay(relay.url) new_relay = self.add_relay(relay.url)
new_relay.error_counter = relay.error_counter new_relay.error_counter = relay.error_counter
new_relay.error_list = relay.error_list

View file

@ -51,6 +51,18 @@
<q-tr :props="props"> <q-tr :props="props">
<q-td v-for="col in props.cols" :key="col.name" :props="props" auto-width> <q-td v-for="col in props.cols" :key="col.name" :props="props" auto-width>
<div v-if="col.name == 'id'"></div> <div v-if="col.name == 'id'"></div>
<div v-else-if="col.name == 'status'">
<div>
⬆️ <span v-text="col.value.sentEvents"></span>
⬇️ <span v-text="col.value.receveidEvents"></span>
⚠️ <span v-text="col.value.errorCount">
</span>
<q-tooltip>
<span v-for="e in col.value.errorList" v-text="e"></span><br>
</q-tooltip>
</div>
</div>
<div v-else> <div v-else>
<div v-if="col.value == true" style="background-color: green"> <div v-if="col.value == true" style="background-color: green">
{{ col.value }} {{ col.value }}
@ -196,6 +208,13 @@
obj._data = _.clone(obj) obj._data = _.clone(obj)
obj.theTime = obj.time * 60 - (Date.now() / 1000 - obj.timestamp) obj.theTime = obj.time * 60 - (Date.now() / 1000 - obj.timestamp)
obj.time = obj.time + 'mins' obj.time = obj.time + 'mins'
obj.status = {
sentEvents: obj.status.num_sent_events,
receveidEvents: obj.status.num_received_events,
errorCount: obj.status.error_counter,
errorList: obj.status.error_list
}
obj.ping = obj.ping + ' ms' obj.ping = obj.ping + ' ms'

View file

@ -24,19 +24,23 @@ all_routers: list[NostrRouter] = []
async def api_get_relays() -> RelayList: async def api_get_relays() -> RelayList:
relays = RelayList(__root__=[]) relays = RelayList(__root__=[])
for url, r in nostr.client.relay_manager.relays.items(): for url, r in nostr.client.relay_manager.relays.items():
status_text = ( # status_text = (
f"⬆️ {r.num_sent_events} ⬇️ {r.num_received_events} ⚠️ {r.error_counter}" # f"⬆️ {r.num_sent_events} ⬇️ {r.num_received_events} ⚠️ {r.error_counter}"
) # )
connected_text = "🟢" if r.connected else "🔴" # connected_text = "🟢" if r.connected else "🔴"
relay_id = urlsafe_short_hash() relay_id = urlsafe_short_hash()
relays.__root__.append( relays.__root__.append(
Relay( Relay(
id=relay_id, id=relay_id,
url=url, url=url,
connected_string=connected_text, connected=r.connected,
status=status_text, status={
"num_sent_events": r.num_sent_events,
"num_received_events": r.num_received_events,
"error_counter": r.error_counter,
"error_list": r.error_list
},
ping=r.ping, ping=r.ping,
connected=True,
active=True, active=True,
) )
) )