Browse Source

Add /status endpoint for monitoring

master
JustAnotherArchivist 3 years ago
parent
commit
5b809b1b99
1 changed files with 18 additions and 4 deletions
  1. +18
    -4
      irclog.py

+ 18
- 4
irclog.py View File

@@ -188,8 +188,8 @@ class Config(dict):
raise InvalidConfig(f'Invalid channel {key!r} path: not a string')
if any(x in channel['path'] for x in itertools.chain(map(chr, range(32)), ('/', '\\', '"', '\x7F'))):
raise InvalidConfig(f'Invalid channel {key!r} path: contains invalid characters')
if channel['path'] == 'general':
raise InvalidConfig(f'Invalid channel {key!r} path: cannot be "general"')
if channel['path'] in ('general', 'status'):
raise InvalidConfig(f'Invalid channel {key!r} path: cannot be "general" or "status"')
if channel['path'] in seenPaths:
raise InvalidConfig(f'Invalid channel {key!r} path: collides with channel {seenPaths[channel["path"]]!r}')
seenPaths[channel['path']] = key
@@ -274,6 +274,7 @@ class IRCClientProtocol(asyncio.Protocol):
self.connectionClosedEvent = connectionClosedEvent
self.loop = loop
self.config = config
self.lastRecvTime = None
self.lastSentTime = None # float timestamp or None; the latter disables the send rate limit
self.sendQueue = asyncio.Queue()
self.buffer = b''
@@ -410,6 +411,7 @@ class IRCClientProtocol(asyncio.Protocol):
def data_received(self, data):
time_ = time.time()
self.logger.debug(f'Data received: {data!r}')
self.lastRecvTime = time_
# If there's any data left in the buffer, prepend it to the data. Split on CRLF.
# Then, process all messages except the last one (since data might not end on a CRLF) and keep the remainder in the buffer.
# If data does end with CRLF, all messages will have been processed and the buffer will be empty again.
@@ -699,6 +701,8 @@ class IRCClient:
if not connectionClosedEvent.is_set():
self.logger.debug('Quitting connection')
await self._protocol.quit()
self._transport = None
self._protocol = None
except (ConnectionRefusedError, ssl.SSLError, asyncio.TimeoutError, asyncio.CancelledError) as e:
self.logger.error(f'{type(e).__module__}.{type(e).__name__}: {e!s}')
await asyncio.wait({asyncio.create_task(sigintEvent.wait())}, timeout = 5)
@@ -707,6 +711,10 @@ class IRCClient:
self.messageQueue.put_nowait(messageEOF)
break

@property
def lastRecvTime(self):
return self._protocol.lastRecvTime if self._protocol else None


class Storage:
logger = logging.getLogger('irclog.Storage')
@@ -829,7 +837,8 @@ class WebServer:
'.linkbar a:last-of-type { border-right: none; }',
]) + '</style>'

def __init__(self, config):
def __init__(self, ircClient, config):
self.ircClient = ircClient
self.config = config

self._paths = {} # '/path' => ('#channel', auth, hidden, extrasearchpaths, description) where auth is either False (no authentication) or the HTTP header value for basic auth
@@ -837,6 +846,7 @@ class WebServer:
self._app = aiohttp.web.Application()
self._app.add_routes([
aiohttp.web.get('/', self.get_homepage),
aiohttp.web.get('/status', self.get_status),
aiohttp.web.get(r'/{path:[^/]+}', functools.partial(self._channel_handler, handler = self.get_channel_info)),
aiohttp.web.get(r'/{path:[^/]+}/{date:\d{4}-\d{2}-\d{2}}', functools.partial(self._channel_handler, handler = self.get_log)),
aiohttp.web.get(r'/{path:[^/]+}/today', functools.partial(self._channel_handler, handler = self.log_redirect_today)),
@@ -898,6 +908,10 @@ class WebServer:
lines.append(f'{"(PW) " if auth else ""}<a href="/{html.escape(path)}">{html.escape(channel)}</a> (<a href="/{html.escape(path)}/today">today&#x27;s log</a>, <a href="/{html.escape(path)}/search">search</a>)')
return aiohttp.web.Response(text = f'<!DOCTYPE html><html lang="en"><head><title>IRC logs</title></head><body>{"<br />".join(lines)}</body></html>', content_type = 'text/html')

async def get_status(self, request):
self.logger.info(f'Received request {id(request)} from {request.remote!r} for {request.path!r}')
return (aiohttp.web.Response if (self.ircClient.lastRecvTime or 0) > time.time() - 600 else aiohttp.web.HTTPInternalServerError)()

async def get_channel_info(self, request):
self.logger.info(f'Received request {id(request)} from {request.remote!r} for {request.path!r}')
description = html.escape(self._paths[request.match_info["path"]][4]) if self._paths[request.match_info["path"]][4] else '<span style="font-style: italic">(not available)</span>'
@@ -1177,7 +1191,7 @@ async def main():
# The queue can also contain messageEOF, which signals to the storage layer to stop logging.

irc = IRCClient(messageQueue, config)
webserver = WebServer(config)
webserver = WebServer(irc, config)
storage = Storage(messageQueue, config)

sigintEvent = asyncio.Event()


Loading…
Cancel
Save