add logging and fix other things

This commit is contained in:
cloudwithax 2023-03-12 22:30:43 -04:00
parent c2b952438a
commit 52a45afd70
4 changed files with 165 additions and 13 deletions

View File

@ -174,7 +174,7 @@ class RouteIPType(Enum):
class URLRegex: class URLRegex:
""" """
The enums for all the URL Regexes in use by Pomice. The enum for all the URL Regexes in use by Pomice.
URLRegex.SPOTIFY_URL returns the Spotify URL Regex. URLRegex.SPOTIFY_URL returns the Spotify URL Regex.
@ -245,3 +245,33 @@ class URLRegex:
LAVALINK_SEARCH = re.compile(r"(?P<type>ytm?|sc)search:") LAVALINK_SEARCH = re.compile(r"(?P<type>ytm?|sc)search:")
BASE_URL = re.compile(r"https?://(?:www\.)?.+") BASE_URL = re.compile(r"https?://(?:www\.)?.+")
class LogLevel(Enum):
"""
The enum for specifying the logging level within Pomice.
This class serves as shorthand for logging.<level>
This enum is exclusively for the logging feature in Pomice.
If you are not using this feature, this class is not necessary.
LogLevel.DEBUG sets the logging level to "debug".
LogLevel.INFO sets the logging level to "info".
LogLevel.WARN sets the logging level to "warn".
LogLevel.ERROR sets the logging level to "error".
LogLevel.CRITICAL sets the logging level to "CRITICAL".
"""
DEBUG = 10
INFO = 20
WARN = 30
ERROR = 40
CRITICAL = 50
def __str__(self) -> str:
return self.value

View File

@ -30,6 +30,7 @@ from .objects import Playlist
from .objects import Track from .objects import Track
from .pool import Node from .pool import Node
from .pool import NodePool from .pool import NodePool
from pomice.utils import LavalinkVersion
if TYPE_CHECKING: if TYPE_CHECKING:
from discord.types.voice import VoiceServerUpdate from discord.types.voice import VoiceServerUpdate
@ -158,6 +159,7 @@ class Player(VoiceProtocol):
self._last_position: int = 0 self._last_position: int = 0
self._last_update: float = 0 self._last_update: float = 0
self._ending_track: Optional[Track] = None self._ending_track: Optional[Track] = None
self._log = self._node._log
self._voice_state: dict = {} self._voice_state: dict = {}
@ -260,20 +262,17 @@ class Player(VoiceProtocol):
return self.guild.id not in self._node._players return self.guild.id not in self._node._players
def _adjust_end_time(self): def _adjust_end_time(self):
version = self._node._version if self._node._version >= LavalinkVersion(3, 7, 5):
if version.major == 4:
return None return None
if version.major == 3:
if version.minor == 7 and version.fix == 5: return "0"
return None
else:
return "0"
async def _update_state(self, data: dict) -> None: async def _update_state(self, data: dict) -> None:
state: dict = data.get("state", {}) state: dict = data.get("state", {})
self._last_update = int(state.get("time", 0)) self._last_update = int(state.get("time", 0))
self._is_connected = bool(state.get("connected")) self._is_connected = bool(state.get("connected"))
self._last_position = int(state.get("position", 0)) self._last_position = int(state.get("position", 0))
self._log.debug(f"Got player update state with data {state}")
async def _dispatch_voice_update(self, voice_data: Optional[Dict[str, Any]] = None) -> None: async def _dispatch_voice_update(self, voice_data: Optional[Dict[str, Any]] = None) -> None:
if {"sessionId", "event"} != self._voice_state.keys(): if {"sessionId", "event"} != self._voice_state.keys():
@ -294,6 +293,8 @@ class Player(VoiceProtocol):
data={"voice": data}, data={"voice": data},
) )
self._log.debug(f"Dispatched voice update to {state['event']['endpoint']} with data {data}")
async def on_voice_server_update(self, data: VoiceServerUpdate) -> None: async def on_voice_server_update(self, data: VoiceServerUpdate) -> None:
self._voice_state.update({"event": data}) self._voice_state.update({"event": data})
await self._dispatch_voice_update(self._voice_state) await self._dispatch_voice_update(self._voice_state)
@ -330,6 +331,8 @@ class Player(VoiceProtocol):
if isinstance(event, TrackStartEvent): if isinstance(event, TrackStartEvent):
self._ending_track = self._current self._ending_track = self._current
self._log.debug(f"Dispatched event {data['type']} to player.")
async def _swap_node(self, *, new_node: Node) -> None: async def _swap_node(self, *, new_node: Node) -> None:
if self.current: if self.current:
data: dict = {"position": self.position, "encodedTrack": self.current.track_id} data: dict = {"position": self.position, "encodedTrack": self.current.track_id}
@ -348,6 +351,8 @@ class Player(VoiceProtocol):
data=data, data=data,
) )
self._log.debug(f"Swapped all players to new node {new_node._identifier}.")
async def get_tracks( async def get_tracks(
self, self,
query: str, query: str,
@ -401,6 +406,8 @@ class Player(VoiceProtocol):
data={"encodedTrack": None}, data={"encodedTrack": None},
) )
self._log.debug(f"Player has been stopped.")
async def disconnect(self, *, force: bool = False) -> None: async def disconnect(self, *, force: bool = False) -> None:
"""Disconnects the player from voice.""" """Disconnects the player from voice."""
try: try:
@ -426,14 +433,13 @@ class Player(VoiceProtocol):
guild_id=self._guild.id, guild_id=self._guild.id,
) )
self._log.debug("Player has been destroyed.")
async def play( async def play(
self, track: Track, *, start: int = 0, end: int = 0, ignore_if_playing: bool = False self, track: Track, *, start: int = 0, end: int = 0, ignore_if_playing: bool = False
) -> Track: ) -> Track:
"""Plays a track. If a Spotify track is passed in, it will be handled accordingly.""" """Plays a track. If a Spotify track is passed in, it will be handled accordingly."""
end_time = self._adjust_end_time()
print(f"got end time of {end_time}")
# Make sure we've never searched the track before # Make sure we've never searched the track before
if track.original is None: if track.original is None:
# First lets try using the tracks ISRC, every track has one (hopefully) # First lets try using the tracks ISRC, every track has one (hopefully)
@ -465,7 +471,7 @@ class Player(VoiceProtocol):
data = { data = {
"encodedTrack": search.track_id, "encodedTrack": search.track_id,
"position": str(start), "position": str(start),
"endTime": end_time, "endTime": self._adjust_end_time(),
} }
track.original = search track.original = search
track.track_id = search.track_id track.track_id = search.track_id
@ -474,7 +480,7 @@ class Player(VoiceProtocol):
data = { data = {
"encodedTrack": track.track_id, "encodedTrack": track.track_id,
"position": str(start), "position": str(start),
"endTime": end_time, "endTime": self._adjust_end_time(),
} }
# Lets set the current track before we play it so any # Lets set the current track before we play it so any
@ -515,6 +521,10 @@ class Player(VoiceProtocol):
query=f"noReplace={ignore_if_playing}", query=f"noReplace={ignore_if_playing}",
) )
self._log.debug(
f"Playing {track.title} from uri {track.uri} with a length of {track.length}",
)
return self._current return self._current
async def seek(self, position: float) -> float: async def seek(self, position: float) -> float:
@ -533,6 +543,8 @@ class Player(VoiceProtocol):
guild_id=self._guild.id, guild_id=self._guild.id,
data={"position": position}, data={"position": position},
) )
self._log.debug(f"Seeking to {position}.")
return self.position return self.position
async def set_pause(self, pause: bool) -> bool: async def set_pause(self, pause: bool) -> bool:
@ -544,6 +556,8 @@ class Player(VoiceProtocol):
data={"paused": pause}, data={"paused": pause},
) )
self._paused = pause self._paused = pause
self._log.debug(f"Player has been {'paused' if pause else 'resumed'}.")
return self._paused return self._paused
async def set_volume(self, volume: int) -> int: async def set_volume(self, volume: int) -> int:
@ -555,6 +569,8 @@ class Player(VoiceProtocol):
data={"volume": volume}, data={"volume": volume},
) )
self._volume = volume self._volume = volume
self._log.debug(f"Player volume has been adjusted to {volume}")
return self._volume return self._volume
async def add_filter(self, _filter: Filter, fast_apply: bool = False) -> Filters: async def add_filter(self, _filter: Filter, fast_apply: bool = False) -> Filters:
@ -573,7 +589,10 @@ class Player(VoiceProtocol):
guild_id=self._guild.id, guild_id=self._guild.id,
data={"filters": payload}, data={"filters": payload},
) )
self._log.debug(f"Filter has been applied to player with tag {_filter.tag}")
if fast_apply: if fast_apply:
self._log.debug(f"Fast apply passed, now applying filter instantly.")
await self.seek(self.position) await self.seek(self.position)
return self._filters return self._filters
@ -594,7 +613,9 @@ class Player(VoiceProtocol):
guild_id=self._guild.id, guild_id=self._guild.id,
data={"filters": payload}, data={"filters": payload},
) )
self._log.debug(f"Filter has been removed from player with tag {filter_tag}")
if fast_apply: if fast_apply:
self._log.debug(f"Fast apply passed, now removing filter instantly.")
await self.seek(self.position) await self.seek(self.position)
return self._filters return self._filters
@ -618,6 +639,8 @@ class Player(VoiceProtocol):
guild_id=self._guild.id, guild_id=self._guild.id,
data={"filters": {}}, data={"filters": {}},
) )
self._log.debug(f"All filters have been removed from player.")
if fast_apply: if fast_apply:
self._log.debug(f"Fast apply passed, now removing all filters instantly.")
await self.seek(self.position) await self.seek(self.position)

View File

@ -1,8 +1,10 @@
from __future__ import annotations from __future__ import annotations
import asyncio import asyncio
import logging
import random import random
import re import re
import time
from typing import Any from typing import Any
from typing import Dict from typing import Dict
from typing import List from typing import List
@ -20,6 +22,7 @@ from . import __version__
from . import applemusic from . import applemusic
from . import spotify from . import spotify
from .enums import * from .enums import *
from .enums import LogLevel
from .exceptions import AppleMusicNotEnabled from .exceptions import AppleMusicNotEnabled
from .exceptions import InvalidSpotifyClientAuthorization from .exceptions import InvalidSpotifyClientAuthorization
from .exceptions import LavalinkVersionIncompatible from .exceptions import LavalinkVersionIncompatible
@ -102,6 +105,7 @@ class Node:
spotify_client_secret: Optional[str] = None, spotify_client_secret: Optional[str] = None,
apple_music: bool = False, apple_music: bool = False,
fallback: bool = False, fallback: bool = False,
log_level: LogLevel = LogLevel.INFO,
): ):
self._bot: commands.Bot = bot self._bot: commands.Bot = bot
self._host: str = host self._host: str = host
@ -112,6 +116,7 @@ class Node:
self._heartbeat: int = heartbeat self._heartbeat: int = heartbeat
self._secure: bool = secure self._secure: bool = secure
self._fallback: bool = fallback self._fallback: bool = fallback
self._log_level: LogLevel = log_level
self._websocket_uri: str = f"{'wss' if self._secure else 'ws'}://{self._host}:{self._port}" self._websocket_uri: str = f"{'wss' if self._secure else 'ws'}://{self._host}:{self._port}"
self._rest_uri: str = f"{'https' if self._secure else 'http'}://{self._host}:{self._port}" self._rest_uri: str = f"{'https' if self._secure else 'http'}://{self._host}:{self._port}"
@ -126,6 +131,7 @@ class Node:
self._version: LavalinkVersion = None self._version: LavalinkVersion = None
self._route_planner = RoutePlanner(self) self._route_planner = RoutePlanner(self)
self._log = self._setup_logging(self._log_level)
if not self._bot.user: if not self._bot.user:
raise NodeCreationError("Bot user is not ready yet.") raise NodeCreationError("Bot user is not ready yet.")
@ -202,6 +208,20 @@ class Node:
"""Alias for `Node.latency`, returns the latency of the node""" """Alias for `Node.latency`, returns the latency of the node"""
return self.latency return self.latency
def _setup_logging(self, level: LogLevel) -> logging.Logger:
logger = logging.getLogger()
handler = logging.StreamHandler()
dt_fmt = "%Y-%m-%d %H:%M:%S"
formatter = logging.Formatter(
"[{asctime}] [{levelname:<8}] {name}: {message}",
dt_fmt,
style="{",
)
handler.setFormatter(formatter)
logger.setLevel(level)
logger.addHandler(handler)
return logger
async def _handle_version_check(self, version: str): async def _handle_version_check(self, version: str):
if version.endswith("-SNAPSHOT"): if version.endswith("-SNAPSHOT"):
# we're just gonna assume all snapshot versions correlate with v4 # we're just gonna assume all snapshot versions correlate with v4
@ -328,6 +348,7 @@ class Node:
headers=self._headers, headers=self._headers,
json=data or {}, json=data or {},
) as resp: ) as resp:
self._log.debug(f"Making REST request with method {method} to {uri}")
if resp.status >= 300: if resp.status >= 300:
resp_data: dict = await resp.json() resp_data: dict = await resp.json()
raise NodeRestException( raise NodeRestException(
@ -335,11 +356,20 @@ class Node:
) )
if method == "DELETE" or resp.status == 204: if method == "DELETE" or resp.status == 204:
self._log.debug(
f"REST request with method {method} to {uri} completed sucessfully and returned no data.",
)
return await resp.json(content_type=None) return await resp.json(content_type=None)
if resp.content_type == "text/plain": if resp.content_type == "text/plain":
self._log.debug(
f"REST request with method {method} to {uri} completed sucessfully and returned text with body {await resp.text()}",
)
return await resp.text() return await resp.text()
self._log.debug(
f"REST request with method {method} to {uri} completed sucessfully and returned JSON with body {await resp.json()}",
)
return await resp.json() return await resp.json()
def get_player(self, guild_id: int) -> Optional[Player]: def get_player(self, guild_id: int) -> Optional[Player]:
@ -350,6 +380,8 @@ class Node:
"""Initiates a connection with a Lavalink node and adds it to the node pool.""" """Initiates a connection with a Lavalink node and adds it to the node pool."""
await self._bot.wait_until_ready() await self._bot.wait_until_ready()
start = time.perf_counter()
if not self._session: if not self._session:
self._session = aiohttp.ClientSession() self._session = aiohttp.ClientSession()
@ -363,16 +395,26 @@ class Node:
await self._handle_version_check(version=version) await self._handle_version_check(version=version)
self._log.debug(f"Version check from node successful. Returned version {version}")
self._websocket = await self._session.ws_connect( self._websocket = await self._session.ws_connect(
f"{self._websocket_uri}/v{self._version.major}/websocket", f"{self._websocket_uri}/v{self._version.major}/websocket",
headers=self._headers, headers=self._headers,
heartbeat=self._heartbeat, heartbeat=self._heartbeat,
) )
self._log.debug(
f"Connected to node websocket using {self._websocket_uri}/v{self._version.major}/websocket",
)
if not self._task: if not self._task:
self._task = self._loop.create_task(self._listen()) self._task = self._loop.create_task(self._listen())
self._available = True self._available = True
end = time.perf_counter()
self._log.info(f"Connected to node {self._identifier}. Took {end - start:.3f}s")
return self return self
except (aiohttp.ClientConnectorError, ConnectionRefusedError): except (aiohttp.ClientConnectorError, ConnectionRefusedError):
@ -392,21 +434,34 @@ class Node:
"""Disconnects a connected Lavalink node and removes it from the node pool. """Disconnects a connected Lavalink node and removes it from the node pool.
This also destroys any players connected to the node. This also destroys any players connected to the node.
""" """
start = time.perf_counter()
for player in self.players.copy().values(): for player in self.players.copy().values():
await player.destroy() await player.destroy()
self._log.debug("All players disconnected from node.")
await self._websocket.close() await self._websocket.close()
await self._session.close() await self._session.close()
self._log.debug("Websocket and http session closed.")
if self._spotify_client: if self._spotify_client:
await self._spotify_client.close() await self._spotify_client.close()
self._log.debug("Spotify client session closed.")
if self._apple_music_client: if self._apple_music_client:
await self._apple_music_client.close() await self._apple_music_client.close()
self._log.debug("Apple Music client session closed.")
del self._pool._nodes[self._identifier] del self._pool._nodes[self._identifier]
self.available = False self.available = False
self._task.cancel() self._task.cancel()
end = time.perf_counter()
self._log.info(
f"Successfully disconnected from node {self._identifier} and closed all sessions. Took {end - start:.3f}s",
)
async def build_track(self, identifier: str, ctx: Optional[commands.Context] = None) -> Track: async def build_track(self, identifier: str, ctx: Optional[commands.Context] = None) -> Track:
""" """
Builds a track using a valid track identifier Builds a track using a valid track identifier

View File

@ -231,3 +231,47 @@ class LavalinkVersion(NamedTuple):
major: int major: int
minor: int minor: int
fix: int fix: int
def __eq__(self, other: object) -> bool:
if not isinstance(other, LavalinkVersion):
return False
return (
(self.major == other.major) and (self.minor == other.minor) and (self.fix == other.fix)
)
def __ne__(self, other: object) -> bool:
if not isinstance(other, LavalinkVersion):
return False
return not (self == other)
def __lt__(self, other: object) -> bool:
if not isinstance(other, LavalinkVersion):
return False
if self.major > other.major:
return False
if self.minor > other.minor:
return False
if self.fix > other.fix:
return False
return True
def __gt__(self, other: object) -> bool:
if not isinstance(other, LavalinkVersion):
return False
return not (self < other)
def __le__(self, other: object) -> bool:
if not isinstance(other, LavalinkVersion):
return False
return (self < other) or (self == other)
def __ge__(self, other: object) -> bool:
if not isinstance(other, LavalinkVersion):
return False
return (self > other) or (self == other)