rewrite: New live-logger.

This commit is contained in:
2022-11-03 15:35:30 +02:00
parent 1e50105542
commit 88861f3880

View File

@@ -1,15 +1,37 @@
import sys
import logging
import asyncio
from logging.handlers import QueueListener, QueueHandler
from queue import SimpleQueue
from contextlib import contextmanager
from contextvars import ContextVar
from discord import AllowedMentions
from discord import AllowedMentions, Webhook
import aiohttp
from .config import conf
from . import sharding
from utils.lib import split_text, utc_now
log_context: ContextVar[str] = ContextVar('logging_context', default='CTX: ROOT CONTEXT')
log_action: ContextVar[str] = ContextVar('logging_action', default='UNKNOWN ACTION')
log_app: ContextVar[str] = ContextVar('logging_shard', default="SHARD {:03}".format(sharding.shard_number))
@contextmanager
def logging_context(context=None, action=None):
if context is not None:
context_t = log_context.set(context)
if action is not None:
action_t = log_action.set(action)
try:
yield
finally:
if context is not None:
log_context.reset(context_t)
if action is not None:
log_action.reset(action_t)
RESET_SEQ = "\033[0m"
@@ -37,7 +59,7 @@ def colour_escape(fmt: str) -> str:
log_format = ('[%(green)%(asctime)-19s%(reset)][%(red)%(levelname)-8s%(reset)]' +
'[%(cyan)SHARD {:02}%(reset)]'.format(sharding.shard_number) +
'[%(cyan)%(app)-15s%(reset)]' +
'[%(cyan)%(context)-22s%(reset)]' +
'[%(cyan)%(action)-22s%(reset)]' +
' %(bold)%(cyan)%(name)s:%(reset)' +
@@ -70,6 +92,7 @@ class ContextInjection(logging.Filter):
record.context = log_context.get()
if not hasattr(record, 'action'):
record.action = log_action.get()
record.app = log_app.get()
return True
@@ -86,6 +109,114 @@ logging_handler_err.setFormatter(log_fmt)
logging_handler_err.addFilter(ContextInjection())
logger.addHandler(logging_handler_err)
class LocalQueueHandler(QueueHandler):
def emit(self, record: logging.LogRecord) -> None:
# Removed the call to self.prepare(), handle task cancellation
try:
self.enqueue(record)
except asyncio.CancelledError:
raise
except Exception:
self.handleError(record)
class WebHookHandler(logging.StreamHandler):
def __init__(self, webhook_url, batch=False):
super().__init__(self)
self.webhook_url = webhook_url
self.batched = ""
self.batch = batch
self.loop = None
def get_loop(self):
if self.loop is None:
self.loop = asyncio.new_event_loop()
return self.loop
def emit(self, record):
self.get_loop().run_until_complete(self.post(record))
async def post(self, record):
try:
timestamp = utc_now().strftime("%d/%m/%Y, %H:%M:%S")
header = f"[{record.levelname}][{record.app}][{record.context}][{record.action}][{timestamp}]"
message = record.msg
# TODO: Maybe send file instead of splitting?
# TODO: Reformat header a little
if len(message) > 1900:
blocks = split_text(message, blocksize=1900, code=False)
else:
blocks = [message]
if len(blocks) > 1:
blocks = [
"```md\n{}[{}/{}]\n{}\n```".format(header, i+1, len(blocks), block) for i, block in enumerate(blocks)
]
else:
blocks = ["```md\n{}\n{}\n```".format(header, blocks[0])]
# Post the log message(s)
if self.batch:
if len(message) > 500:
await self._send_batched()
await self._send(*blocks)
elif len(self.batched) + len(blocks[0]) > 500:
self.batched += blocks[0]
await self._send_batched()
else:
self.batched += blocks[0]
else:
await self._send(*blocks)
except Exception as ex:
print(ex)
async def _send_batched(self):
if self.batched:
batched = self.batched
self.batched = ""
await self._send(batched)
async def _send(self, *blocks):
async with aiohttp.ClientSession() as session:
webhook = Webhook.from_url(self.webhook_url, session=session)
for block in blocks:
await webhook.send(block)
handlers = []
if webhook := conf.logging['general_log']:
handler = WebHookHandler(webhook, batch=True)
handlers.append(handler)
if webhook := conf.logging['error_log']:
handler = WebHookHandler(webhook, batch=False)
handler.setLevel(logging.ERROR)
handlers.append(handler)
if webhook := conf.logging['critical_log']:
handler = WebHookHandler(webhook, batch=False)
handler.setLevel(logging.CRITICAL)
handlers.append(handler)
if handlers:
queue: SimpleQueue[logging.LogRecord] = SimpleQueue()
handler = QueueHandler(queue)
handler.setLevel(logging.INFO)
handler.addFilter(ContextInjection())
logger.addHandler(handler)
listener = QueueListener(
queue, *handlers, respect_handler_level=True
)
listener.start()
# QueueHandler to feed entries to a Queue
# On the other end of the Queue, feed to the webhook
# TODO: Add an async handler for posting
# Subclass this, create a DiscordChannelHandler, taking a Client and a channel as an argument
# Then we can handle error channels etc differently