1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
|
try:
from dotenv import load_dotenv
print("Found .env file, loading environment variables from it.")
load_dotenv(override=True)
except ModuleNotFoundError:
pass
import asyncio
import logging
import logging.handlers
import os
from functools import partial, partialmethod
from pathlib import Path
import arrow
from discord.ext import commands
from bot.command import Command
from bot.constants import Client
from bot.group import Group
# Configure the "TRACE" logging level (e.g. "log.trace(message)")
logging.TRACE = 5
logging.addLevelName(logging.TRACE, "TRACE")
def monkeypatch_trace(self: logging.Logger, msg: str, *args, **kwargs) -> None:
"""
Log 'msg % args' with severity 'TRACE'.
To pass exception information, use the keyword argument exc_info with a true value, e.g.
logger.trace("Houston, we have an %s", "interesting problem", exc_info=1)
"""
if self.isEnabledFor(logging.TRACE):
self._log(logging.TRACE, msg, args, **kwargs)
logging.Logger.trace = monkeypatch_trace
# Set timestamp of when execution started (approximately)
start_time = arrow.utcnow()
# Set up file logging
log_dir = Path("bot/log")
log_file = log_dir / "hackbot.log"
os.makedirs(log_dir, exist_ok=True)
# File handler rotates logs every 5 MB
file_handler = logging.handlers.RotatingFileHandler(
log_file, maxBytes=5 * (2**20), backupCount=10, encoding="utf-8",
)
file_handler.setLevel(logging.TRACE if Client.debug else logging.DEBUG)
# Console handler prints to terminal
console_handler = logging.StreamHandler()
level = logging.TRACE if Client.debug else logging.INFO
console_handler.setLevel(level)
# Remove old loggers, if any
root = logging.getLogger()
if root.handlers:
for handler in root.handlers:
root.removeHandler(handler)
# Silence irrelevant loggers
logging.getLogger("discord").setLevel(logging.ERROR)
logging.getLogger("websockets").setLevel(logging.ERROR)
logging.getLogger("PIL").setLevel(logging.ERROR)
logging.getLogger("matplotlib").setLevel(logging.ERROR)
logging.getLogger("async_rediscache").setLevel(logging.WARNING)
# Setup new logging configuration
logging.basicConfig(
format="%(asctime)s - %(name)s %(levelname)s: %(message)s",
datefmt="%D %H:%M:%S",
level=logging.TRACE if Client.debug else logging.DEBUG,
handlers=[console_handler, file_handler],
)
logging.getLogger().info("Logging initialization complete")
# On Windows, the selector event loop is required for aiodns.
if os.name == "nt":
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
# Monkey-patch discord.py decorators to use the both the Command and Group subclasses which supports root aliases.
# Must be patched before any cogs are added.
commands.command = partial(commands.command, cls=Command)
commands.GroupMixin.command = partialmethod(commands.GroupMixin.command, cls=Command)
commands.group = partial(commands.group, cls=Group)
commands.GroupMixin.group = partialmethod(commands.GroupMixin.group, cls=Group)
|