mirror of
https://github.com/MelisaDev/melisa.git
synced 2024-11-11 19:07:28 +03:00
add logging
This commit is contained in:
parent
8e6cbd711c
commit
e17a8bf0b0
7 changed files with 186 additions and 14 deletions
|
@ -45,7 +45,6 @@ extensions = [
|
||||||
"attributable"
|
"attributable"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
autodoc_default_options = {
|
autodoc_default_options = {
|
||||||
'members': True,
|
'members': True,
|
||||||
'show-inheritance': True
|
'show-inheritance': True
|
||||||
|
|
|
@ -1,17 +1,20 @@
|
||||||
# Copyright MelisaDev 2022 - Present
|
# Copyright MelisaDev 2022 - Present
|
||||||
# Full MIT License can be found in `LICENSE.txt` at the project root.
|
# Full MIT License can be found in `LICENSE.txt` at the project root.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import asyncio
|
||||||
|
from typing import Dict, List, Union, Any
|
||||||
|
|
||||||
from .models import User, Guild
|
from .models import User, Guild
|
||||||
from .models.app import Shard
|
from .models.app import Shard
|
||||||
from .utils import Snowflake, APIModelBase
|
from .utils import Snowflake, APIModelBase
|
||||||
from .utils.types import Coro
|
from .utils.types import Coro
|
||||||
|
|
||||||
from .core.http import HTTPClient
|
from .core.http import HTTPClient
|
||||||
from .core.gateway import GatewayBotInfo
|
from .core.gateway import GatewayBotInfo
|
||||||
from .models.guild.channel import Channel, ChannelType, channel_types_for_converting
|
from .models.guild.channel import Channel, ChannelType, channel_types_for_converting
|
||||||
|
from .utils.logging import init_logging
|
||||||
|
|
||||||
import asyncio
|
_logger = logging.getLogger("melisa")
|
||||||
from typing import Dict, List, Union, Any
|
|
||||||
|
|
||||||
|
|
||||||
class Client:
|
class Client:
|
||||||
|
@ -22,15 +25,21 @@ class Client:
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
token : :class:`str`
|
token: :class:`str`
|
||||||
The token to login (you can found it in the developer portal)
|
The token to login (you can found it in the developer portal)
|
||||||
intents : :class:`~melisa.Intents`
|
intents: :class:`~melisa.Intents`
|
||||||
The Discord Intents values.
|
The Discord Intents values.
|
||||||
activity : :class:`~models.user.presence.BotActivity`
|
activity: :class:`~models.user.presence.BotActivity`
|
||||||
The Activity to set (on connecting)
|
The Activity to set (on connecting)
|
||||||
status : :class:`str`
|
status: :class:`str`
|
||||||
The Status to set (on connecting).
|
The Status to set (on connecting).
|
||||||
Can be generated using :class:`~models.user.presence.StatusType`
|
Can be generated using :class:`~models.user.presence.StatusType`
|
||||||
|
logs: :class:`Optional[None, str, Dict[str, Any]]`
|
||||||
|
The hint for configuring logging.
|
||||||
|
This can be `None` to disable logging automatically.
|
||||||
|
If you pass a :class:`str` or a :class:`int`, it is interpreted as
|
||||||
|
the global logging level to use, and should match one of **DEBUG**,
|
||||||
|
**INFO**, **WARNING**, **ERROR** or **CRITICAL**, if :class:`str`.
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
----------
|
----------
|
||||||
|
@ -42,7 +51,15 @@ class Client:
|
||||||
Bot's shards.
|
Bot's shards.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, token: str, intents, *, activity=None, status: str = None):
|
def __init__(
|
||||||
|
self,
|
||||||
|
token: str,
|
||||||
|
intents,
|
||||||
|
*,
|
||||||
|
activity=None,
|
||||||
|
status: str = None,
|
||||||
|
logs: Union[None, int, str, Dict[str, Any]] = "INFO",
|
||||||
|
):
|
||||||
self.shards: Dict[int, Shard] = {}
|
self.shards: Dict[int, Shard] = {}
|
||||||
self.http: HTTPClient = HTTPClient(token)
|
self.http: HTTPClient = HTTPClient(token)
|
||||||
self._events: Dict[str, Coro] = {}
|
self._events: Dict[str, Coro] = {}
|
||||||
|
@ -64,6 +81,8 @@ class Client:
|
||||||
|
|
||||||
APIModelBase.set_client(self)
|
APIModelBase.set_client(self)
|
||||||
|
|
||||||
|
init_logging(logs)
|
||||||
|
|
||||||
async def _get_gateway(self):
|
async def _get_gateway(self):
|
||||||
"""Get Gateway information"""
|
"""Get Gateway information"""
|
||||||
return GatewayBotInfo.from_dict(await self.http.get("gateway/bot"))
|
return GatewayBotInfo.from_dict(await self.http.get("gateway/bot"))
|
||||||
|
@ -80,6 +99,7 @@ class Client:
|
||||||
raise TypeError(f"<{callback.__qualname__}> must be a coroutine function")
|
raise TypeError(f"<{callback.__qualname__}> must be a coroutine function")
|
||||||
|
|
||||||
self._events[callback.__qualname__] = callback
|
self._events[callback.__qualname__] = callback
|
||||||
|
_logger.debug(f"Listener {callback.__qualname__} added successfully!")
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
# Full MIT License can be found in `LICENSE.txt` at the project root.
|
# Full MIT License can be found in `LICENSE.txt` at the project root.
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import logging
|
||||||
import sys
|
import sys
|
||||||
import zlib
|
import zlib
|
||||||
import time
|
import time
|
||||||
|
@ -16,6 +17,8 @@ from ..listeners import listeners
|
||||||
from ..models.user import BotActivity
|
from ..models.user import BotActivity
|
||||||
from ..utils import APIModelBase, json
|
from ..utils import APIModelBase, json
|
||||||
|
|
||||||
|
_logger = logging.getLogger("melisa.gateway")
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class GatewayBotInfo(APIModelBase):
|
class GatewayBotInfo(APIModelBase):
|
||||||
|
@ -89,12 +92,14 @@ class Gateway:
|
||||||
self.ws = await self.__session.ws_connect(
|
self.ws = await self.__session.ws_connect(
|
||||||
f"wss://gateway.discord.gg/?v={self.GATEWAY_VERSION}&encoding=json&compress=zlib-stream"
|
f"wss://gateway.discord.gg/?v={self.GATEWAY_VERSION}&encoding=json&compress=zlib-stream"
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.session_id is None:
|
if self.session_id is None:
|
||||||
|
_logger.debug("(Shard %s) Starting...", self.shard_id)
|
||||||
|
|
||||||
await self.send_identify()
|
await self.send_identify()
|
||||||
self.loop.create_task(self.receive())
|
self.loop.create_task(self.receive())
|
||||||
await self.check_heartbeating()
|
await self.check_heartbeating()
|
||||||
else:
|
else:
|
||||||
|
_logger.debug("(Shard %s) Resumed.", self.shard_id)
|
||||||
await self.resume()
|
await self.resume()
|
||||||
|
|
||||||
async def check_heartbeating(self):
|
async def check_heartbeating(self):
|
||||||
|
@ -102,10 +107,15 @@ class Gateway:
|
||||||
await asyncio.sleep(20)
|
await asyncio.sleep(20)
|
||||||
|
|
||||||
if self._last_send + 60.0 < time.perf_counter():
|
if self._last_send + 60.0 < time.perf_counter():
|
||||||
|
_logger.warning(
|
||||||
|
"(Shard %s) ack not received. Attempting to reconnect.",
|
||||||
|
self.shard_id,
|
||||||
|
)
|
||||||
await self.ws.close(code=4000)
|
await self.ws.close(code=4000)
|
||||||
await self.handle_close(4000)
|
await self.handle_close(4000)
|
||||||
|
|
||||||
async def send(self, payload: str) -> None:
|
async def send(self, payload: str) -> None:
|
||||||
|
_logger.debug("(Shard %s) Sending payload: %s", self.shard_id, payload)
|
||||||
await self.ws.send_str(payload)
|
await self.ws.send_str(payload)
|
||||||
|
|
||||||
async def parse_websocket_message(self, msg):
|
async def parse_websocket_message(self, msg):
|
||||||
|
@ -125,8 +135,18 @@ class Gateway:
|
||||||
|
|
||||||
async def handle_data(self, data):
|
async def handle_data(self, data):
|
||||||
"""Handles received data and process it"""
|
"""Handles received data and process it"""
|
||||||
|
|
||||||
|
_logger.debug(
|
||||||
|
"(Shard %s) Data with %s opcode received", self.shard_id, data["op"]
|
||||||
|
)
|
||||||
|
|
||||||
if data["op"] == self.DISPATCH:
|
if data["op"] == self.DISPATCH:
|
||||||
self.sequence = int(data["s"])
|
self.sequence = int(data["s"])
|
||||||
|
|
||||||
|
_logger.debug(
|
||||||
|
"(Shard %s) Set sequence number to %s", self.shard_id, data["s"]
|
||||||
|
)
|
||||||
|
|
||||||
event_type = data["t"].lower()
|
event_type = data["t"].lower()
|
||||||
|
|
||||||
event_to_call = self.listeners.get(event_type)
|
event_to_call = self.listeners.get(event_type)
|
||||||
|
@ -135,12 +155,26 @@ class Gateway:
|
||||||
ensure_future(event_to_call(self.client, self, data["d"]))
|
ensure_future(event_to_call(self.client, self, data["d"]))
|
||||||
|
|
||||||
elif data["op"] == self.INVALID_SESSION:
|
elif data["op"] == self.INVALID_SESSION:
|
||||||
|
_logger.debug(
|
||||||
|
"(Shard %s) Invalid session, attempting to reconnect", self.shard_id
|
||||||
|
)
|
||||||
await self.ws.close(code=4000)
|
await self.ws.close(code=4000)
|
||||||
await self.handle_close(4000)
|
await self.handle_close(4000)
|
||||||
elif data["op"] == self.HELLO:
|
elif data["op"] == self.HELLO:
|
||||||
await self.send_hello(data)
|
await self.send_hello(data)
|
||||||
elif data["op"] == self.HEARTBEAT_ACK:
|
elif data["op"] == self.HEARTBEAT_ACK:
|
||||||
|
_logger.debug(
|
||||||
|
"(Shard %s) received heartbeat ack",
|
||||||
|
self.shard_id,
|
||||||
|
)
|
||||||
self.latency = time.perf_counter() - self._last_send
|
self.latency = time.perf_counter() - self._last_send
|
||||||
|
elif data["op"] == self.RECONNECT:
|
||||||
|
_logger.debug(
|
||||||
|
"(Shard %s) Requested to reconnect to Discord. "
|
||||||
|
"Closing session and attempting to resume",
|
||||||
|
self.shard_id,
|
||||||
|
)
|
||||||
|
await self.close(1012)
|
||||||
|
|
||||||
async def receive(self) -> None:
|
async def receive(self) -> None:
|
||||||
"""Receives and parses received data"""
|
"""Receives and parses received data"""
|
||||||
|
@ -155,8 +189,10 @@ class Gateway:
|
||||||
raise RuntimeError
|
raise RuntimeError
|
||||||
|
|
||||||
close_code = self.ws.close_code
|
close_code = self.ws.close_code
|
||||||
|
|
||||||
if close_code is None:
|
if close_code is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
await self.handle_close(close_code)
|
await self.handle_close(close_code)
|
||||||
|
|
||||||
async def handle_close(self, code: int) -> None:
|
async def handle_close(self, code: int) -> None:
|
||||||
|
@ -169,10 +205,18 @@ class Gateway:
|
||||||
if err:
|
if err:
|
||||||
raise err
|
raise err
|
||||||
|
|
||||||
|
_logger.info(
|
||||||
|
"(Shard %s) disconnected from the Discord Gateway without any errors. Reconnecting...",
|
||||||
|
self.shard_id,
|
||||||
|
)
|
||||||
|
|
||||||
await self.connect()
|
await self.connect()
|
||||||
|
|
||||||
async def send_heartbeat(self, interval: float) -> None:
|
async def send_heartbeat(self, interval: float) -> None:
|
||||||
if not self.ws.closed:
|
if not self.ws.closed:
|
||||||
|
_logger.debug(
|
||||||
|
"(Shard %s) sending heartbeat in %sms", self.shard_id, interval
|
||||||
|
)
|
||||||
await self.send(self.opcode(self.HEARTBEAT, self.sequence))
|
await self.send(self.opcode(self.HEARTBEAT, self.sequence))
|
||||||
self._last_send = time.perf_counter()
|
self._last_send = time.perf_counter()
|
||||||
await asyncio.sleep(interval)
|
await asyncio.sleep(interval)
|
||||||
|
@ -181,6 +225,7 @@ class Gateway:
|
||||||
async def close(self, code: int = 4000) -> None:
|
async def close(self, code: int = 4000) -> None:
|
||||||
if self.ws:
|
if self.ws:
|
||||||
await self.ws.close(code=code)
|
await self.ws.close(code=code)
|
||||||
|
|
||||||
self._buffer.clear()
|
self._buffer.clear()
|
||||||
|
|
||||||
async def send_hello(self, data: Dict) -> None:
|
async def send_hello(self, data: Dict) -> None:
|
||||||
|
@ -188,7 +233,22 @@ class Gateway:
|
||||||
await asyncio.sleep((interval - 2000) / 1000)
|
await asyncio.sleep((interval - 2000) / 1000)
|
||||||
self.loop.create_task(self.send_heartbeat(interval))
|
self.loop.create_task(self.send_heartbeat(interval))
|
||||||
|
|
||||||
async def send_identify(self) -> None:
|
async def send_identify(self, resume: bool = False) -> None:
|
||||||
|
if resume:
|
||||||
|
_logger.debug("(Shard %s) Resuming connection with Discord", self.shard_id)
|
||||||
|
|
||||||
|
await self.send(
|
||||||
|
self.opcode(
|
||||||
|
self.RESUME,
|
||||||
|
{
|
||||||
|
"token": self.client._token,
|
||||||
|
"session_id": self.session_id,
|
||||||
|
"seq": self.sequence,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
await self.send(self.opcode(self.IDENTIFY, self.auth))
|
await self.send(self.opcode(self.IDENTIFY, self.auth))
|
||||||
|
|
||||||
async def resume(self) -> None:
|
async def resume(self) -> None:
|
||||||
|
@ -216,6 +276,7 @@ class Gateway:
|
||||||
return data
|
return data
|
||||||
|
|
||||||
async def update_presence(self, data: dict):
|
async def update_presence(self, data: dict):
|
||||||
|
_logger.debug("[Shard %s] Updating presence...", self.shard_id)
|
||||||
await self.send(self.opcode(self.PRESENCE_UPDATE, data))
|
await self.send(self.opcode(self.PRESENCE_UPDATE, data))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import logging
|
||||||
from typing import Dict, Optional, Any
|
from typing import Dict, Optional, Any
|
||||||
|
|
||||||
from aiohttp import ClientSession, ClientResponse
|
from aiohttp import ClientSession, ClientResponse
|
||||||
|
@ -22,6 +23,8 @@ from melisa.exceptions import (
|
||||||
from .ratelimiter import RateLimiter
|
from .ratelimiter import RateLimiter
|
||||||
from ..utils import remove_none
|
from ..utils import remove_none
|
||||||
|
|
||||||
|
_logger = logging.getLogger("melisa.http")
|
||||||
|
|
||||||
|
|
||||||
class HTTPClient:
|
class HTTPClient:
|
||||||
API_VERSION = 9
|
API_VERSION = 9
|
||||||
|
@ -76,6 +79,8 @@ class HTTPClient:
|
||||||
if ttl == 0:
|
if ttl == 0:
|
||||||
raise ServerError(f"Maximum amount of retries for `{endpoint}`.")
|
raise ServerError(f"Maximum amount of retries for `{endpoint}`.")
|
||||||
|
|
||||||
|
_logger.debug(f"Send {method} request to the {endpoint}")
|
||||||
|
|
||||||
await self.__rate_limiter.wait_until_not_ratelimited(endpoint, method)
|
await self.__rate_limiter.wait_until_not_ratelimited(endpoint, method)
|
||||||
|
|
||||||
url = f"{self.url}/{endpoint}"
|
url = f"{self.url}/{endpoint}"
|
||||||
|
@ -105,12 +110,19 @@ class HTTPClient:
|
||||||
) -> Optional[Dict]:
|
) -> Optional[Dict]:
|
||||||
"""Handle responses from the Discord API."""
|
"""Handle responses from the Discord API."""
|
||||||
|
|
||||||
|
_logger.debug(f"Received response for the {endpoint} ({await res.text()})")
|
||||||
|
|
||||||
self.__rate_limiter.save_response_bucket(endpoint, method, res.headers)
|
self.__rate_limiter.save_response_bucket(endpoint, method, res.headers)
|
||||||
|
|
||||||
if res.ok:
|
if res.ok:
|
||||||
if res.status == 204:
|
if res.status == 204:
|
||||||
|
_logger.debug("Request has been sent successfully.")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
_logger.debug(
|
||||||
|
"Request has been sent successfully and returned json response."
|
||||||
|
)
|
||||||
|
|
||||||
return await res.json()
|
return await res.json()
|
||||||
|
|
||||||
exception = self.__http_exceptions.get(res.status)
|
exception = self.__http_exceptions.get(res.status)
|
||||||
|
@ -119,14 +131,30 @@ class HTTPClient:
|
||||||
if isinstance(exception, RateLimitError):
|
if isinstance(exception, RateLimitError):
|
||||||
timeout = (await res.json()).get("retry_after", 40)
|
timeout = (await res.json()).get("retry_after", 40)
|
||||||
|
|
||||||
|
_logger.exception(
|
||||||
|
f"You are being ratelimited: {res.reason}."
|
||||||
|
f" The scope is {res.headers.get('X-RateLimit-Scope')}."
|
||||||
|
f" I will retry in {timeout} seconds"
|
||||||
|
)
|
||||||
|
|
||||||
await asyncio.sleep(timeout)
|
await asyncio.sleep(timeout)
|
||||||
return await self.__send(method, endpoint, **kwargs)
|
return await self.__send(method, endpoint, **kwargs)
|
||||||
|
|
||||||
|
_logger.error(
|
||||||
|
f" HTTP exception occurred while trying to send "
|
||||||
|
f"a request to {endpoint}. ({res.status}, {res.reason})"
|
||||||
|
)
|
||||||
|
|
||||||
exception.__init__(res.reason)
|
exception.__init__(res.reason)
|
||||||
raise exception
|
raise exception
|
||||||
|
|
||||||
retry_in = 1 + (self.max_ttl - _ttl) * 2
|
retry_in = 1 + (self.max_ttl - _ttl) * 2
|
||||||
|
|
||||||
|
_logger.debug(
|
||||||
|
"Discord side error occurred (hahahhaha, discord, fix yourself)."
|
||||||
|
f" Status-Code: {res.status}. I will retry sending in {retry_in}s."
|
||||||
|
)
|
||||||
|
|
||||||
await asyncio.sleep(retry_in)
|
await asyncio.sleep(retry_in)
|
||||||
|
|
||||||
return await self.__send(method, endpoint, _ttl=_ttl - 1, **kwargs)
|
return await self.__send(method, endpoint, _ttl=_ttl - 1, **kwargs)
|
||||||
|
|
|
@ -3,11 +3,14 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
from asyncio import sleep
|
from asyncio import sleep
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from time import time
|
from time import time
|
||||||
from typing import Dict, Tuple, Any
|
from typing import Dict, Tuple, Any
|
||||||
|
|
||||||
|
_logger = logging.getLogger("melisa.http")
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class RateLimitBucket:
|
class RateLimitBucket:
|
||||||
|
@ -16,7 +19,7 @@ class RateLimitBucket:
|
||||||
limit: int
|
limit: int
|
||||||
remaining: int
|
remaining: int
|
||||||
reset: float
|
reset: float
|
||||||
reset_after_timestamp: float
|
reset_after: float
|
||||||
since_timestamp: float
|
since_timestamp: float
|
||||||
|
|
||||||
|
|
||||||
|
@ -41,10 +44,16 @@ class RateLimiter:
|
||||||
limit=int(header["X-RateLimit-Limit"]),
|
limit=int(header["X-RateLimit-Limit"]),
|
||||||
remaining=int(header["X-RateLimit-Remaining"]),
|
remaining=int(header["X-RateLimit-Remaining"]),
|
||||||
reset=float(header["X-RateLimit-Reset"]),
|
reset=float(header["X-RateLimit-Reset"]),
|
||||||
reset_after_timestamp=float(header["X-RateLimit-Reset-After"]),
|
reset_after=float(header["X-RateLimit-Reset-After"]),
|
||||||
since_timestamp=time(),
|
since_timestamp=time(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
_logger.info(
|
||||||
|
"Rate limit bucket detected: %s - %r.",
|
||||||
|
ratelimit_bucket_id,
|
||||||
|
self.buckets[ratelimit_bucket_id],
|
||||||
|
)
|
||||||
|
|
||||||
async def wait_until_not_ratelimited(self, endpoint: str, method: str):
|
async def wait_until_not_ratelimited(self, endpoint: str, method: str):
|
||||||
bucket_id = self.bucket_map.get((endpoint, method))
|
bucket_id = self.bucket_map.get((endpoint, method))
|
||||||
|
|
||||||
|
@ -55,4 +64,11 @@ class RateLimiter:
|
||||||
|
|
||||||
if bucket.remaining == 0:
|
if bucket.remaining == 0:
|
||||||
sleep_time = time() - bucket.since_timestamp + bucket.reset_after_timestamp
|
sleep_time = time() - bucket.since_timestamp + bucket.reset_after_timestamp
|
||||||
|
|
||||||
|
_logger.info(
|
||||||
|
"Waiting until rate limit for bucket %s is over.", sleep_time, bucket_id
|
||||||
|
)
|
||||||
|
|
||||||
await sleep(sleep_time)
|
await sleep(sleep_time)
|
||||||
|
|
||||||
|
_logger.info("Message sent. Bucket %s rate limit ended.", bucket_id)
|
||||||
|
|
|
@ -485,7 +485,6 @@ class TextChannel(Channel):
|
||||||
count += 1
|
count += 1
|
||||||
|
|
||||||
if count == 100:
|
if count == 100:
|
||||||
print("abobatelecom")
|
|
||||||
await self.bulk_delete_messages(message_ids, reason=reason)
|
await self.bulk_delete_messages(message_ids, reason=reason)
|
||||||
message_ids = []
|
message_ids = []
|
||||||
count = 0
|
count = 0
|
||||||
|
|
49
melisa/utils/logging.py
Normal file
49
melisa/utils/logging.py
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
# Copyright MelisaDev 2022 - Present
|
||||||
|
# Full MIT License can be found in `LICENSE.txt` at the project root.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import logging.config
|
||||||
|
import sys
|
||||||
|
from typing import Union, Dict, Any
|
||||||
|
|
||||||
|
|
||||||
|
def init_logging(
|
||||||
|
flavor: Union[None, str, int, Dict[str, Any]],
|
||||||
|
) -> None:
|
||||||
|
"""Attempt to initialize logging for the user.
|
||||||
|
If any handlers already exist, this is ignored entirely. This ensures the
|
||||||
|
user can use any existing logging configuration without us interfering.
|
||||||
|
You can manually disable logging by passing `None` as the `flavor` parameter.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
flavor: Optional[None, :class:`str`, Dict[:class:`str`, Any]]
|
||||||
|
This can be `None` to disable logging automatically.
|
||||||
|
If you pass a :class:`str` or a :class:`int`, it is interpreted as
|
||||||
|
the global logging level to use, and should match one of **DEBUG**,
|
||||||
|
**INFO**, **WARNING**, **ERROR** or **CRITICAL**, if :class:`str`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# This method was found in the hikari source code and modified, thank you, hikari devs
|
||||||
|
|
||||||
|
if len(logging.root.handlers) != 0 or flavor is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if isinstance(flavor, dict):
|
||||||
|
logging.config.dictConfig(flavor)
|
||||||
|
|
||||||
|
if flavor.get("handlers"):
|
||||||
|
return
|
||||||
|
|
||||||
|
flavor = None
|
||||||
|
|
||||||
|
logging.logThreads = False
|
||||||
|
logging.logProcesses = False
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
level=flavor,
|
||||||
|
format="[%(asctime)s] %(name)s: %(message)s",
|
||||||
|
datefmt="%Y-%m-%d %H:%M:%S",
|
||||||
|
stream=sys.stderr,
|
||||||
|
)
|
||||||
|
logging.captureWarnings(True)
|
Loading…
Reference in a new issue