diff --git a/poetry.lock b/poetry.lock
index e570c183..df10d2bd 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -232,17 +232,17 @@ test = ["asgi-lifespan (>=1.0.1)", "dnspython (>=2.1.0)", "fastapi (>=0.100)", "
[[package]]
name = "boto3"
-version = "1.34.27"
+version = "1.34.28"
description = "The AWS SDK for Python"
optional = false
python-versions = ">= 3.8"
files = [
- {file = "boto3-1.34.27-py3-none-any.whl", hash = "sha256:3626db4ba9fbb1b58c8fe923da5ed670873b3d881a102956ea19d3b69cd097cc"},
- {file = "boto3-1.34.27.tar.gz", hash = "sha256:ebdd938019f3df2e7b50585353963d4553faf3fbb7b2085c440107fa6caa233b"},
+ {file = "boto3-1.34.28-py3-none-any.whl", hash = "sha256:fb56622ce195c06ae0d15ae9472d44529362a869ad52862a5a28b891530969f9"},
+ {file = "boto3-1.34.28.tar.gz", hash = "sha256:9e0dcca7bb0567f7b4b84d1d26c19b217abfe149d19106af7f120f09142688cf"},
]
[package.dependencies]
-botocore = ">=1.34.27,<1.35.0"
+botocore = ">=1.34.28,<1.35.0"
jmespath = ">=0.7.1,<2.0.0"
s3transfer = ">=0.10.0,<0.11.0"
@@ -251,13 +251,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"]
[[package]]
name = "botocore"
-version = "1.34.27"
+version = "1.34.28"
description = "Low-level, data-driven core of boto 3."
optional = false
python-versions = ">= 3.8"
files = [
- {file = "botocore-1.34.27-py3-none-any.whl", hash = "sha256:1c10f247136ad17b6ef1588c1e043e294dbaebdebe9ce84dc56713029f515c53"},
- {file = "botocore-1.34.27.tar.gz", hash = "sha256:a0e68ba264275b358b8c1cca604161f4d9465cf7847d73e929543a9f30ff22d1"},
+ {file = "botocore-1.34.28-py3-none-any.whl", hash = "sha256:03be8209257ab65f3c8be7377cf8d38bff6a6afbe3d36c72924e48959bb694dc"},
+ {file = "botocore-1.34.28.tar.gz", hash = "sha256:45c99ccc6389ab1a87e996a7cc8797c7e41d5ecd9a5757d567ba3a57cb7655e7"},
]
[package.dependencies]
diff --git a/src/valentina/characters/chargen.py b/src/valentina/characters/chargen.py
index 0aa47ee0..d4dfc573 100644
--- a/src/valentina/characters/chargen.py
+++ b/src/valentina/characters/chargen.py
@@ -527,7 +527,7 @@ async def random_attributes(self, character: Character) -> Character:
Args:
character (Character): The character for which to generate attributes.
"""
- logger.debug(f"CHARGEN: Generate attribute values for {character.name}")
+ logger.debug(f"Generate attribute values for {character.name}")
concept = CharacterConcept[character.concept_name] if character.concept_name else None
char_class = CharClass[character.char_class_name]
@@ -591,7 +591,7 @@ async def random_abilities(self, character: Character) -> Character:
Args:
character (Character): The character for which to generate abilities.
"""
- logger.debug(f"CHARGEN: Generate ability values for {character.name}")
+ logger.debug(f"Generate ability values for {character.name}")
concept = CharacterConcept[character.concept_name] if character.concept_name else None
@@ -656,7 +656,7 @@ async def random_disciplines(self, character: Character) -> Character:
Returns:
Character: The updated character.
"""
- logger.debug(f"CHARGEN: Generate discipline values for {character.name}")
+ logger.debug(f"Generate discipline values for {character.name}")
# TODO: Work with Ghouls which have no clan
try:
@@ -713,7 +713,7 @@ async def random_virtues(self, character: Character) -> Character:
Returns:
Character: The updated character.
"""
- logger.debug(f"CHARGEN: Generate virtue values for {character.name}")
+ logger.debug(f"Generate virtue values for {character.name}")
if not (
virtues := TraitCategory.VIRTUES.get_trait_list(CharClass[character.char_class_name])
@@ -756,7 +756,7 @@ async def random_backgrounds(self, character: Character) -> Character:
Returns:
Character: The updated character.
"""
- logger.debug(f"CHARGEN: Generate background values for {character.name}")
+ logger.debug(f"Generate background values for {character.name}")
char_class = CharClass[character.char_class_name]
@@ -801,7 +801,7 @@ async def random_willpower(self, character: Character) -> Character: # noqa: PL
Returns:
Character: The updated character.
"""
- logger.debug(f"CHARGEN: Generate willpower values for {character.name}")
+ logger.debug(f"Generate willpower values for {character.name}")
if not any(x.name for x in character.traits if x.name == "Self-Control"): # type: ignore [attr-defined]
return character
@@ -852,7 +852,7 @@ async def random_hunter_traits(self, character: Character) -> Character:
if character.char_class != CharClass.HUNTER:
return character
- logger.debug(f"CHARGEN: Generate hunter trait values for {character.name}")
+ logger.debug(f"Generate hunter trait values for {character.name}")
try:
creed = HunterCreed[character.creed_name]
@@ -919,7 +919,7 @@ async def concept_special_abilities(self, character: Character) -> Character: #
if character.char_class != CharClass.MORTAL:
return character
- logger.debug(f"CHARGEN: Assign special abilities for {character.name}")
+ logger.debug(f"Assign special abilities for {character.name}")
# Assign Traits
for ability in character.concept.value.abilities:
@@ -1055,7 +1055,7 @@ async def start(self, restart: bool = False) -> None:
Args:
restart (bool, optional): Whether to restart the wizard. Defaults to False.
"""
- logger.debug("CHARGEN: Starting the character generation wizard.")
+ logger.debug("Starting the character generation wizard.")
# Build the instructional embeds
embed1 = discord.Embed(
@@ -1126,7 +1126,7 @@ async def present_character_choices(self) -> None:
Returns:
None: This method returns nothing.
"""
- logger.debug("CHARGEN: Starting the character selection process")
+ logger.debug("Starting the character selection process")
# Generate 3 characters
characters = [
@@ -1192,7 +1192,7 @@ async def present_character_choices(self) -> None:
campaign_xp, _, _ = self.user.fetch_campaign_xp(self.campaign)
# Delete the previously created characters
- logger.debug("CHARGEN: Rerolling characters and deleting old ones.")
+ logger.debug("Rerolling characters and deleting old ones.")
for character in characters:
await character.delete(link_rule=DeleteRules.DELETE_LINKS)
@@ -1271,7 +1271,7 @@ async def spend_freebie_points(self, character: Character) -> Character:
Returns:
Character: The created character.
"""
- logger.debug(f"CHARGEN: Spending freebie points for {character.name}")
+ logger.debug(f"Spending freebie points for {character.name}")
# Create the character sheet embed
title = f"Spend freebie points on {character.name}\n"
diff --git a/src/valentina/cogs/admin.py b/src/valentina/cogs/admin.py
index 05ca9052..74c7403d 100644
--- a/src/valentina/cogs/admin.py
+++ b/src/valentina/cogs/admin.py
@@ -324,7 +324,9 @@ async def emoji_add(
# Confirm the action
title = f"Add custom emoji :{name}:"
- is_confirmed, msg, confirmation_embed = await confirm_action(ctx, title, hidden=hidden)
+ is_confirmed, msg, confirmation_embed = await confirm_action(
+ ctx, title, hidden=hidden, audit=True
+ )
if not is_confirmed:
return
@@ -332,7 +334,6 @@ async def emoji_add(
await ctx.guild.create_custom_emoji(name=name, image=data)
# Send confirmation
- await ctx.post_to_audit_log(title)
await msg.edit_original_response(embed=confirmation_embed, view=None)
@guild.command(name="emoji_delete")
diff --git a/src/valentina/cogs/campaign.py b/src/valentina/cogs/campaign.py
index 89d03a2f..2f321d09 100644
--- a/src/valentina/cogs/campaign.py
+++ b/src/valentina/cogs/campaign.py
@@ -69,7 +69,7 @@ async def create_campaign(
title = f"Create new campaign: `{name}`"
is_confirmed, interaction, confirmation_embed = await confirm_action(
- ctx, title, hidden=hidden
+ ctx, title, hidden=hidden, audit=True
)
if not is_confirmed:
@@ -83,7 +83,6 @@ async def create_campaign(
guild.campaigns.append(campaign)
await guild.save()
- await ctx.post_to_audit_log(title)
await interaction.edit_original_response(embed=confirmation_embed, view=None)
@campaign.command(name="current_date", description="Set the current date of a campaign")
@@ -103,7 +102,6 @@ async def current_date(
campaign.date_in_game = date
await campaign.save()
- await ctx.post_to_audit_log(f"Set date of campaign `{campaign.name}` to `{date:%Y-%m-%d}`")
await present_embed(
ctx,
title=f"Set date of campaign `{campaign.name}` to `{date:%Y-%m-%d}`",
@@ -133,7 +131,7 @@ async def delete_campaign(
title = f"Delete campaign: {campaign.name}"
is_confirmed, interaction, confirmation_embed = await confirm_action(
- ctx, title, hidden=hidden
+ ctx, title, hidden=hidden, audit=True
)
if not is_confirmed:
@@ -142,7 +140,6 @@ async def delete_campaign(
guild = await Guild.get(ctx.guild.id, fetch_links=True)
await guild.delete_campaign(campaign)
- await ctx.post_to_audit_log(title)
await interaction.edit_original_response(embed=confirmation_embed, view=None)
@campaign.command(name="view", description="View a campaign")
@@ -207,7 +204,7 @@ async def campaign_set_active(
title = f"Set campaign `{campaign.name}` as active"
is_confirmed, interaction, confirmation_embed = await confirm_action(
- ctx, title, hidden=hidden
+ ctx, title, hidden=hidden, audit=True
)
if not is_confirmed:
@@ -217,7 +214,6 @@ async def campaign_set_active(
guild.active_campaign = campaign
await guild.save()
- await ctx.post_to_audit_log(title)
await interaction.edit_original_response(embed=confirmation_embed, view=None)
@campaign.command(name="set_inactive", description="Set a campaign as inactive")
@@ -248,7 +244,7 @@ async def campaign_set_inactive(
title = f"Set campaign `{active_campaign.name}` as inactive"
is_confirmed, interaction, confirmation_embed = await confirm_action(
- ctx, title, hidden=hidden
+ ctx, title, hidden=hidden, audit=True
)
if not is_confirmed:
@@ -257,7 +253,6 @@ async def campaign_set_inactive(
guild.active_campaign = None
await guild.save()
- await ctx.post_to_audit_log(title)
await interaction.edit_original_response(embed=confirmation_embed, view=None)
@campaign.command(name="list", description="List all campaigns")
@@ -294,7 +289,6 @@ async def campaign_list(
])
await present_embed(ctx, title="Campaigns", fields=fields, level="info")
- logger.debug("CAMPAIGN: List all campaigns")
### NPC COMMANDS ####################################################################
@@ -476,7 +470,7 @@ async def delete_npc(
title = f"Delete NPC: `{npc.name}` in `{active_campaign.name}`"
is_confirmed, interaction, confirmation_embed = await confirm_action(
- ctx, title, hidden=hidden
+ ctx, title, hidden=hidden, audit=True
)
if not is_confirmed:
@@ -485,7 +479,6 @@ async def delete_npc(
del active_campaign.npcs[index]
await active_campaign.save()
- await ctx.post_to_audit_log(title)
await interaction.edit_original_response(embed=confirmation_embed, view=None)
### CHAPTER COMMANDS ####################################################################
@@ -641,7 +634,7 @@ async def delete_chapter(
title = f"Delete Chapter `{chapter.number}. {chapter.name}` from `{active_campaign.name}`"
is_confirmed, interaction, confirmation_embed = await confirm_action(
- ctx, title, hidden=hidden
+ ctx, title, hidden=hidden, audit=True
)
if not is_confirmed:
@@ -650,7 +643,6 @@ async def delete_chapter(
del active_campaign.chapters[index]
await active_campaign.save()
- await ctx.post_to_audit_log(title)
await interaction.edit_original_response(embed=confirmation_embed, view=None)
### NOTE COMMANDS ####################################################################
@@ -802,7 +794,7 @@ async def delete_note(
title = f"Delete note: `{note.name}` from `{active_campaign.name}`"
is_confirmed, interaction, confirmation_embed = await confirm_action(
- ctx, title, hidden=hidden
+ ctx, title, hidden=hidden, audit=True
)
if not is_confirmed:
@@ -811,7 +803,6 @@ async def delete_note(
del active_campaign.notes[index]
await active_campaign.save()
- await ctx.post_to_audit_log(title)
await interaction.edit_original_response(embed=confirmation_embed, view=None)
diff --git a/src/valentina/cogs/characters.py b/src/valentina/cogs/characters.py
index dfd73d07..e52774c8 100644
--- a/src/valentina/cogs/characters.py
+++ b/src/valentina/cogs/characters.py
@@ -7,7 +7,6 @@
import inflect
from discord.commands import Option
from discord.ext import commands
-from loguru import logger
from valentina.characters import AddFromSheetWizard, CharGenWizard
from valentina.constants import (
@@ -130,8 +129,7 @@ async def add_character(
wizard = AddFromSheetWizard(ctx, character=character, user=user)
await wizard.begin_chargen()
- await ctx.post_to_audit_log(f"Created player character: `{character.name}`")
- logger.info(f"CHARACTER: Create player character {character.name}")
+ await ctx.post_to_audit_log(f"Create player character: `{character.name}`")
@chars.command(name="create", description="Create a new randomized character")
async def create_character(
@@ -309,7 +307,7 @@ async def transfer_character(
title = f"Transfer `{character.name}` from `{ctx.author.display_name}` to `{new_owner.display_name}`"
is_confirmed, interaction, confirmation_embed = await confirm_action(
- ctx, title, hidden=hidden
+ ctx, title, hidden=hidden, audit=True
)
if not is_confirmed:
return
@@ -324,7 +322,6 @@ async def transfer_character(
character.user_owner = new_user.id
await character.save()
- await ctx.post_to_audit_log(title)
await interaction.edit_original_response(embed=confirmation_embed, view=None)
@chars.command(name="kill", description="Kill a character")
@@ -358,7 +355,7 @@ async def kill_character(
title = f"Kill `{character.name}`"
is_confirmed, interaction, confirmation_embed = await confirm_action(
- ctx, title, hidden=hidden
+ ctx, title, hidden=hidden, audit=True
)
if not is_confirmed:
return
@@ -366,7 +363,6 @@ async def kill_character(
character.is_alive = False
await character.save()
- await ctx.post_to_audit_log(title)
await interaction.edit_original_response(embed=confirmation_embed, view=None)
### IMAGE COMMANDS ####################################################################
@@ -431,14 +427,13 @@ async def add_image(
title = f"Add image to `{character.name}`"
is_confirmed, interaction, confirmation_embed = await confirm_action(
- ctx, title, hidden=hidden, image=image_url
+ ctx, title, hidden=hidden, image=image_url, audit=True
)
if not is_confirmed:
await character.delete_image(image_key)
return
# Update audit log and original response
- await ctx.post_to_audit_log(title)
await interaction.edit_original_response(embed=confirmation_embed, view=None)
@image.command(name="delete", description="Delete an image from a character")
@@ -516,14 +511,13 @@ async def add_trait(
title = f"Add trait: `{name.title()}` at `{value}` dots for {character.name}"
is_confirmed, interaction, confirmation_embed = await confirm_action(
- ctx, title, hidden=hidden
+ ctx, title, hidden=hidden, audit=True
)
if not is_confirmed:
return
await character.add_trait(category, name.title(), value, max_value=max_value)
- await ctx.post_to_audit_log(title)
await interaction.edit_original_response(embed=confirmation_embed, view=None)
@trait.command(name="update", description="Update the value of a trait for a character")
@@ -576,7 +570,7 @@ async def update_trait(
f"Update `{trait.name}` from `{trait.value}` to `{new_value}` for `{character.name}`"
)
is_confirmed, interaction, confirmation_embed = await confirm_action(
- ctx, title, hidden=hidden
+ ctx, title, hidden=hidden, audit=True
)
if not is_confirmed:
@@ -585,7 +579,6 @@ async def update_trait(
trait.value = new_value
await trait.save()
- await ctx.post_to_audit_log(title)
await interaction.edit_original_response(embed=confirmation_embed, view=None)
@trait.command(name="delete", description="Delete a trait from a character")
@@ -627,6 +620,7 @@ async def delete_trait(
title,
description="This is a destructive action that can not be undone.",
hidden=hidden,
+ audit=True,
)
if not is_confirmed:
@@ -637,7 +631,6 @@ async def delete_trait(
await trait.delete()
- await ctx.post_to_audit_log(title)
await interaction.edit_original_response(embed=confirmation_embed, view=None)
### SECTION COMMANDS ####################################################################
@@ -752,7 +745,7 @@ async def delete_custom_section(
title = f"Delete section `{section.title}` from `{character.name}`"
is_confirmed, interaction, confirmation_embed = await confirm_action(
- ctx, title, hidden=hidden
+ ctx, title, hidden=hidden, audit=True
)
if not is_confirmed:
return
@@ -760,7 +753,6 @@ async def delete_custom_section(
character.sheet_sections.pop(section_index)
await character.save()
- await ctx.post_to_audit_log(title)
await interaction.edit_original_response(embed=confirmation_embed, view=None)
### BIO COMMANDS ####################################################################
diff --git a/src/valentina/cogs/developer.py b/src/valentina/cogs/developer.py
index 20d66562..f111e54b 100644
--- a/src/valentina/cogs/developer.py
+++ b/src/valentina/cogs/developer.py
@@ -11,12 +11,12 @@
from beanie import DeleteRules
from discord.commands import Option
from discord.ext import commands
-from loguru import logger
from valentina.characters import RNGCharGen
-from valentina.constants import PREF_MAX_EMBED_CHARACTERS, EmbedColor
+from valentina.constants import PREF_MAX_EMBED_CHARACTERS, EmbedColor, LogLevel
from valentina.models import AWSService, Character, GlobalProperty, Guild, RollProbability, User
from valentina.models.bot import Valentina, ValentinaContext
+from valentina.utils import instantiate_logger
from valentina.utils.autocomplete import (
select_aws_object_from_guild,
select_changelog_version_1,
@@ -110,7 +110,6 @@ async def delete_from_s3_guild(
# Delete the object from S3
# TODO: Search for the url in character data and delete it there too so we don't have dead links
self.aws_svc.delete_object(key)
- logger.info(f"Deleted object with key: {key} from S3")
await interaction.edit_original_response(embed=confirmation_embed, view=None)
@@ -199,7 +198,7 @@ async def delete_developer_characters(
return
for c in dev_characters:
- logger.debug(f"DEVELOPER: Deleting {c.name}")
+ ctx.log_command(f"Delete dev character {c.name}")
await c.delete(link_rule=DeleteRules.DELETE_LINKS)
await interaction.edit_original_response(embed=confirmation_embed, view=None)
@@ -302,7 +301,6 @@ async def clear_probability_cache(
for result in results:
await result.delete()
- logger.info(f"DEVELOPER: {ctx.author.display_name} cleared probability data from the db")
await interaction.edit_original_response(embed=confirmation_embed, view=None)
@server.command(name="reload", description="Reload all cogs")
@@ -328,10 +326,8 @@ async def reload(
for cog in Path(self.bot.parent_dir / "src" / "valentina" / "cogs").glob("*.py"):
if cog.stem[0] != "_":
count += 1
- logger.info(f"COGS: Reloading - {cog.stem}")
self.bot.reload_extension(f"valentina.cogs.{cog.stem}")
- logger.debug("Admin: Reload the bot's cogs")
await interaction.edit_original_response(embed=confirmation_embed, view=None)
@server.command(name="shutdown", description="Shutdown the bot")
@@ -354,7 +350,7 @@ async def shutdown(
return
await interaction.edit_original_response(embed=confirmation_embed, view=None)
- logger.warning(f"DEVELOPER: {ctx.author.display_name} has shut down the bot")
+ ctx.log_command("Shutdown the bot", LogLevel.WARNING)
await self.bot.close()
@@ -370,6 +366,7 @@ async def debug_send_log(
),
) -> None:
"""Send the bot's logs to the user."""
+ ctx.log_command("Send the bot's logs", LogLevel.DEBUG)
log_file = get_config_value("VALENTINA_LOG_FILE")
file = discord.File(log_file)
await ctx.respond(file=file, ephemeral=hidden)
@@ -386,7 +383,7 @@ async def debug_tail_logs(
),
) -> None:
"""Tail the bot's logs."""
- logger.debug("ADMIN: Tail bot logs")
+ ctx.log_command("Tail the bot's logs", LogLevel.DEBUG)
max_lines_from_bottom = 20
log_lines = []
@@ -465,6 +462,31 @@ async def status(
await ctx.respond(embed=embed, ephemeral=hidden)
+ ### LOGGING COMMANDS ################################################################
+
+ @developer.command(name="logging", description="Change log level")
+ @commands.is_owner()
+ async def logging(
+ self,
+ ctx: ValentinaContext,
+ log_level: Option(LogLevel),
+ hidden: Option(
+ bool,
+ description="Make the confirmation only visible to you (default True)",
+ default=True,
+ ),
+ ) -> None:
+ """Change log level."""
+ title = f"Set log level to: `{log_level.value}`"
+ is_confirmed, interaction, confirmation_embed = await confirm_action(
+ ctx, title, hidden=hidden
+ )
+ if not is_confirmed:
+ return
+
+ instantiate_logger(log_level)
+ await interaction.edit_original_response(embed=confirmation_embed, view=None)
+
def setup(bot: Valentina) -> None:
"""Add the cog to the bot."""
diff --git a/src/valentina/cogs/experience.py b/src/valentina/cogs/experience.py
index d00bdb34..ac18df19 100644
--- a/src/valentina/cogs/experience.py
+++ b/src/valentina/cogs/experience.py
@@ -66,7 +66,7 @@ async def xp_add(
title = f"Add `{amount}` xp to `{user.name}`"
description = "View experience with `/user_info`"
is_confirmed, msg, confirmation_embed = await confirm_action(
- ctx, title, description=description, hidden=hidden
+ ctx, title, description=description, hidden=hidden, audit=True
)
if not is_confirmed:
return
@@ -75,7 +75,6 @@ async def xp_add(
await user.add_campaign_xp(active_campaign, amount)
# Send the confirmation message
- await ctx.post_to_audit_log(title)
await msg.edit_original_response(embed=confirmation_embed, view=None)
@xp.command(name="add_cool_point", description="Add a cool point to a user")
@@ -116,7 +115,7 @@ async def cp_add(
title = f"Add `{amount}` cool {p.plural_noun('point', amount)} to `{user.name}`"
description = "View cool points with `/user_info`"
is_confirmed, msg, confirmation_embed = await confirm_action(
- ctx, title, description=description, hidden=hidden
+ ctx, title, description=description, hidden=hidden, audit=True
)
if not is_confirmed:
return
@@ -125,7 +124,6 @@ async def cp_add(
await user.add_campaign_cool_points(active_campaign, amount)
# Send the confirmation message
- await ctx.post_to_audit_log(title)
await msg.edit_original_response(embed=confirmation_embed, view=None)
@xp.command(name="spend", description="Spend experience points")
@@ -183,7 +181,9 @@ async def xp_spend(
new_trait_value = trait.value + 1
title = f"Upgrade `{trait.name}` from `{trait.value}` {p.plural_noun('dot', trait.value)} to `{trait.value + 1}` {p.plural_noun('dot', trait.value + 1)} for `{upgrade_cost}` xp"
- is_confirmed, msg, confirmation_embed = await confirm_action(ctx, title, hidden=hidden)
+ is_confirmed, msg, confirmation_embed = await confirm_action(
+ ctx, title, hidden=hidden, audit=True
+ )
if not is_confirmed:
return
@@ -196,7 +196,6 @@ async def xp_spend(
await trait.save()
# Send the confirmation message
- await ctx.post_to_audit_log(title)
await msg.edit_original_response(embed=confirmation_embed, view=None)
diff --git a/src/valentina/cogs/gameplay.py b/src/valentina/cogs/gameplay.py
index 5fafc5ca..b79c881f 100644
--- a/src/valentina/cogs/gameplay.py
+++ b/src/valentina/cogs/gameplay.py
@@ -6,9 +6,8 @@
import discord
from discord.commands import Option
from discord.ext import commands
-from loguru import logger
-from valentina.constants import DEFAULT_DIFFICULTY, DiceType
+from valentina.constants import DEFAULT_DIFFICULTY, DiceType, LogLevel
from valentina.models import User
from valentina.models.bot import Valentina, ValentinaContext
from valentina.utils import random_num
@@ -84,8 +83,9 @@ async def traits(
pool = trait_one.value + trait_two.value
- logger.debug(
- f"ROLL TRAITS: {trait_one.name} ({trait_one.id}) + {trait_two.name} ({trait_two.id}) = {pool}"
+ ctx.log_command(
+ f"{trait_one.name} ({trait_one.id}) + {trait_two.name} ({trait_two.id})",
+ LogLevel.DEBUG,
)
await perform_roll(
@@ -148,6 +148,11 @@ async def roll_macro(
msg = "Macro traits not found on character"
raise commands.BadArgument(msg)
+ ctx.log_command(
+ f"Macro: {macro.name}: {trait_one.name} ({trait_one.id}) + {trait_two.name} ({trait_two.id})",
+ LogLevel.DEBUG,
+ )
+
pool = trait_one.value + trait_two.value
await perform_roll(
diff --git a/src/valentina/cogs/github.py b/src/valentina/cogs/github.py
index 8ed5ea2b..3dfa254f 100644
--- a/src/valentina/cogs/github.py
+++ b/src/valentina/cogs/github.py
@@ -8,7 +8,7 @@
from github.Repository import Repository
from loguru import logger
-from valentina.constants import GithubIssueLabels
+from valentina.constants import GithubIssueLabels, LogLevel
from valentina.models.bot import Valentina, ValentinaContext
from valentina.utils import errors
from valentina.utils.helpers import get_config_value
@@ -51,6 +51,7 @@ async def fetch_github_repo(self) -> Repository:
@issues.command(name="list", description="List open issues")
async def issue_list(self, ctx: ValentinaContext) -> None:
"""List open Github issues."""
+ ctx.log_command("github issues list", LogLevel.DEBUG)
repo = await self.fetch_github_repo()
open_issues = repo.get_issues(state="open")
for issue in open_issues:
@@ -77,6 +78,7 @@ async def issue_list(self, ctx: ValentinaContext) -> None:
@issues.command(name="get", description="Get details for a specific issue")
async def issue_get(self, ctx: ValentinaContext, issue_number: int) -> None:
"""Get details for a specific Github issue."""
+ ctx.log_command(f"github issues get {issue_number}", LogLevel.DEBUG)
repo = await self.fetch_github_repo()
issue = repo.get_issue(number=issue_number)
await present_embed(
@@ -103,6 +105,7 @@ async def issue_add(
),
) -> None:
"""Add a new Github issue."""
+ ctx.log_command(f"github issues add {title} {description} {type_of_issue}", LogLevel.DEBUG)
repo = await self.fetch_github_repo()
issue = repo.create_issue(title=title, body=description, labels=[type_of_issue])
await present_embed(
diff --git a/src/valentina/cogs/macros.py b/src/valentina/cogs/macros.py
index d185eb84..ac902019 100644
--- a/src/valentina/cogs/macros.py
+++ b/src/valentina/cogs/macros.py
@@ -161,7 +161,7 @@ async def delete_macro(
title = f"Delete macro `{macro.name}`"
is_confirmed, interaction, confirmation_embed = await confirm_action(
- ctx, title, hidden=hidden, footer="This action is irreversible."
+ ctx, title, hidden=hidden, footer="This action is irreversible.", audit=True
)
if not is_confirmed:
@@ -170,7 +170,6 @@ async def delete_macro(
del user.macros[index]
await user.save()
- await ctx.post_to_audit_log(title)
await interaction.edit_original_response(embed=confirmation_embed, view=None)
diff --git a/src/valentina/cogs/misc.py b/src/valentina/cogs/misc.py
index 59733e32..34b020cb 100644
--- a/src/valentina/cogs/misc.py
+++ b/src/valentina/cogs/misc.py
@@ -355,7 +355,7 @@ async def upload_thumbnail(
"""Add a roll result thumbnail to the bot."""
title = f"Add roll result image for {roll_type.title()}\n{url}"
is_confirmed, interaction, confirmation_embed = await confirm_action(
- ctx, title, hidden=hidden, image=url
+ ctx, title, hidden=hidden, image=url, audit=True
)
if not is_confirmed:
@@ -364,7 +364,6 @@ async def upload_thumbnail(
guild = await Guild.get(ctx.guild.id, fetch_links=True)
await guild.add_roll_result_thumbnail(ctx, RollResultType[roll_type.upper()], url)
- await ctx.post_to_audit_log(title)
await interaction.edit_original_response(embed=confirmation_embed, view=None)
diff --git a/src/valentina/cogs/storyteller.py b/src/valentina/cogs/storyteller.py
index e38e3cfa..9e676759 100644
--- a/src/valentina/cogs/storyteller.py
+++ b/src/valentina/cogs/storyteller.py
@@ -139,7 +139,7 @@ async def create_story_char(
wizard = AddFromSheetWizard(ctx, character=character, user=user)
await wizard.begin_chargen()
- await ctx.post_to_audit_log(f"Created storyteller character: `{character.name}`")
+ await ctx.post_to_audit_log(f"Create storyteller character: `{character.name}`")
logger.info(f"CHARACTER: Create storyteller character {character.name}")
@character.command(name="create_rng", description="Create a random new npc character")
@@ -238,7 +238,7 @@ async def create_rng_char(
color=EmbedColor.SUCCESS.value,
),
)
- await ctx.post_to_audit_log(f"Storyteller character created: `{character.full_name}`")
+ await ctx.post_to_audit_log(f"Create storyteller character: `{character.full_name}`")
@character.command(name="list", description="List all storyteller characters")
async def list_characters(
@@ -310,7 +310,7 @@ async def update_storyteller_character(
f"Update `{trait.name}` for `{character.name}` from `{trait.value}` to `{new_value}`"
)
is_confirmed, interaction, confirmation_embed = await confirm_action(
- ctx, title, hidden=hidden
+ ctx, title, hidden=hidden, audit=True
)
if not is_confirmed:
@@ -320,7 +320,6 @@ async def update_storyteller_character(
trait.value = new_value
await trait.save()
- await ctx.post_to_audit_log(title)
await interaction.edit_original_response(embed=confirmation_embed, view=None)
@character.command(name="sheet", description="View a character sheet")
@@ -361,7 +360,7 @@ async def delete_storyteller_character(
"""Delete a storyteller character."""
title = f"Delete storyteller character `{character.full_name}`"
is_confirmed, interaction, confirmation_embed = await confirm_action(
- ctx, title, hidden=hidden
+ ctx, title, hidden=hidden, audit=True
)
if not is_confirmed:
@@ -369,7 +368,6 @@ async def delete_storyteller_character(
await character.delete(link_rule=DeleteRules.DELETE_LINKS)
- await ctx.post_to_audit_log(title)
await interaction.edit_original_response(embed=confirmation_embed, view=None)
@character.command(name="add_trait", description="Add a trait to a storyteller character")
@@ -408,7 +406,7 @@ async def add_trait(
"""Add a custom trait to a character."""
title = f"Create custom trait: `{name.title()}` at `{value}` dots for {character.full_name}"
is_confirmed, interaction, confirmation_embed = await confirm_action(
- ctx, title, hidden=hidden
+ ctx, title, hidden=hidden, audit=True
)
if not is_confirmed:
@@ -416,7 +414,6 @@ async def add_trait(
await character.add_trait(category, name.title(), value, max_value=max_value)
- await ctx.post_to_audit_log(title)
await interaction.edit_original_response(embed=confirmation_embed, view=None)
@character.command(name="image_add", description="Add an image to a storyteller character")
@@ -484,7 +481,7 @@ async def add_image(
title = f"Add image to `{character.name}`"
is_confirmed, interaction, confirmation_embed = await confirm_action(
- ctx, title, hidden=hidden, image=image_url
+ ctx, title, hidden=hidden, image=image_url, audit=True
)
if not is_confirmed:
@@ -492,7 +489,6 @@ async def add_image(
return
# Update audit log and original response
- await ctx.post_to_audit_log(title)
await interaction.edit_original_response(embed=confirmation_embed, view=None)
@character.command(
@@ -570,7 +566,7 @@ async def transfer_character(
title = f"Transfer `{character.name}` from `{old_owner.name}` to `{new_owner.name}`"
is_confirmed, interaction, confirmation_embed = await confirm_action(
- ctx, title, hidden=hidden
+ ctx, title, hidden=hidden, audit=True
)
if not is_confirmed:
return
@@ -582,7 +578,6 @@ async def transfer_character(
character.user_owner = new_owner.id
await character.save()
- await ctx.post_to_audit_log(title)
await interaction.edit_original_response(embed=confirmation_embed, view=None)
@player.command(name="update", description="Update a player character")
@@ -625,7 +620,7 @@ async def update_player_character(
f"Update `{trait.name}` from `{trait.value}` to `{new_value}` for `{character.name}`"
)
is_confirmed, interaction, confirmation_embed = await confirm_action(
- ctx, title, hidden=hidden
+ ctx, title, hidden=hidden, audit=True
)
if not is_confirmed:
@@ -634,7 +629,6 @@ async def update_player_character(
trait.value = new_value
await trait.save()
- await ctx.post_to_audit_log(title)
await interaction.edit_original_response(embed=confirmation_embed, view=None)
### ROLL COMMANDS ####################################################################
diff --git a/src/valentina/constants.py b/src/valentina/constants.py
index 3ed4c458..b05b6805 100644
--- a/src/valentina/constants.py
+++ b/src/valentina/constants.py
@@ -29,6 +29,16 @@
### ENUMS ###
+class LogLevel(str, Enum):
+ """Enum for logging levels."""
+
+ TRACE = "TRACE"
+ DEBUG = "DEBUG"
+ INFO = "INFO"
+ WARNING = "WARNING"
+ ERROR = "ERROR"
+
+
class ChannelPermission(Enum):
"""Enum for permissions when creating a character. Default is UNRESTRICTED."""
diff --git a/src/valentina/main.py b/src/valentina/main.py
index 01bb73fb..0a4394fa 100644
--- a/src/valentina/main.py
+++ b/src/valentina/main.py
@@ -1,7 +1,5 @@
"""Main file which instantiates the bot and runs it."""
-import logging
-import sys
from pathlib import Path
from time import sleep
from typing import Optional
@@ -11,7 +9,7 @@
from loguru import logger
from valentina.models.bot import Valentina
-from valentina.utils import InterceptHandler
+from valentina.utils import instantiate_logger
from valentina.utils.database import test_db_connection
from valentina.utils.helpers import get_config_value
@@ -29,39 +27,6 @@ def version_callback(value: bool) -> None:
raise typer.Exit()
-http_log_level = get_config_value("VALENTINA_LOG_LEVEL_HTTP", "INFO")
-aws_log_level = get_config_value("VALENTINA_LOG_LEVEL_AWS", "INFO")
-
-# Instantiate Logging
-logging.getLogger("discord.http").setLevel(level=http_log_level.upper())
-logging.getLogger("discord.gateway").setLevel(level=http_log_level.upper())
-logging.getLogger("discord.webhook").setLevel(level=http_log_level.upper())
-logging.getLogger("discord.client").setLevel(level=http_log_level.upper())
-logging.getLogger("faker").setLevel(level="INFO")
-for service in ["urllib3", "boto3", "botocore", "s3transfer"]:
- logging.getLogger(service).setLevel(level=aws_log_level.upper())
-
-logger.remove()
-logger.add(
- sys.stderr,
- level=get_config_value("VALENTINA_LOG_LEVEL", "INFO").upper(),
- colorize=True,
- format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {name}: {message}",
- enqueue=True,
-)
-logger.add(
- get_config_value("VALENTINA_LOG_FILE", "valentina.log"),
- level=get_config_value("VALENTINA_LOG_LEVEL", "INFO").upper(),
- rotation="1 week",
- retention="2 weeks",
- compression="zip",
- enqueue=True,
-)
-
-# Intercept standard discord.py logs and redirect to Loguru
-logging.basicConfig(handlers=[InterceptHandler()], level=0, force=True)
-
-
@app.command()
def main(
version: Optional[bool] = typer.Option( # noqa: ARG001
@@ -69,6 +34,9 @@ def main(
),
) -> None:
"""Run Valentina."""
+ # Instantiate the logger
+ instantiate_logger()
+
# Ensure the database is available before starting the bot
while not test_db_connection():
logger.error("DB: Connection failed. Retrying in 30 seconds...")
diff --git a/src/valentina/models/bot.py b/src/valentina/models/bot.py
index e3933e76..9a88d691 100644
--- a/src/valentina/models/bot.py
+++ b/src/valentina/models/bot.py
@@ -17,6 +17,7 @@
from valentina.constants import (
ChannelPermission,
EmbedColor,
+ LogLevel,
PermissionManageCampaign,
PermissionsGrantXP,
PermissionsKillCharacter,
@@ -40,6 +41,18 @@
class ValentinaContext(discord.ApplicationContext):
"""A custom application context for Valentina."""
+ # valentina.models.bot: Reload all cogs (@natenate in #general)
+
+ def log_command(self, msg: str, level: LogLevel = LogLevel.INFO) -> None: # pragma: no cover
+ """Log the command to the console and log file."""
+ author = f"@{self.author.display_name}" if hasattr(self, "author") else None
+ command = f"'/{self.command.qualified_name}'" if hasattr(self, "command") else None
+ channel = f"#{self.channel.name}" if hasattr(self, "channel") else None
+
+ command_info = [author, command, channel]
+
+ logger.log(level.value, f"{msg} [{', '.join([x for x in command_info if x])}]")
+
def _message_to_embed(self, message: str) -> discord.Embed: # pragma: no cover
"""Convert a string message to a discord embed.
@@ -132,6 +145,12 @@ async def post_to_audit_log(self, message: str | discord.Embed) -> None: # prag
guild = await Guild.get(self.guild.id)
audit_log_channel = guild.fetch_audit_log_channel(self.guild)
+ if isinstance(message, str):
+ self.log_command(message, LogLevel.INFO)
+
+ if isinstance(message, discord.Embed):
+ self.log_command(f"{message.title} {message.description}", LogLevel.INFO)
+
if audit_log_channel:
embed = self._message_to_embed(message) if isinstance(message, str) else message
diff --git a/src/valentina/models/character.py b/src/valentina/models/character.py
index 8379993c..ba45a3c9 100644
--- a/src/valentina/models/character.py
+++ b/src/valentina/models/character.py
@@ -175,6 +175,7 @@ async def add_image(self, extension: str, data: bytes) -> str:
key = f"{key_prefix}/{image_name}"
# Upload the image to S3
+ logger.debug(f"S3: Uploading {key} to {self.name}")
aws_svc.upload_image(data=data, key=key)
# Add the image to the character's data
@@ -209,7 +210,7 @@ async def delete_image(self, key: str) -> None:
# Delete the image from Amazon S3
aws_svc.delete_object(key)
- logger.info(f"S3: Deleted {key} from {self.name}")
+ logger.info(f"S3: Delete {key} from {self.name}")
async def add_trait(
self,
diff --git a/src/valentina/utils/__init__.py b/src/valentina/utils/__init__.py
index 9754004b..ebd1e412 100644
--- a/src/valentina/utils/__init__.py
+++ b/src/valentina/utils/__init__.py
@@ -2,6 +2,6 @@
from .console import console
from .helpers import random_num
-from .logging import InterceptHandler
+from .logging import InterceptHandler, instantiate_logger
-__all__ = ["Context", "InterceptHandler", "console", "random_num"]
+__all__ = ["Context", "InterceptHandler", "console", "instantiate_logger", "random_num"]
diff --git a/src/valentina/utils/logging.py b/src/valentina/utils/logging.py
index f1eee950..c5082c45 100644
--- a/src/valentina/utils/logging.py
+++ b/src/valentina/utils/logging.py
@@ -5,6 +5,58 @@
from loguru import logger
+from valentina.constants import LogLevel
+from valentina.utils.helpers import get_config_value
+
+
+def instantiate_logger(log_level: LogLevel | None = None) -> None: # pragma: no cover
+ """Instantiate the Loguru logger for Valentina.
+
+ Configure the logger with the specified verbosity level, log file path,
+ and whether to log to a file.
+
+ Args:
+ log_level (LogLevel): The verbosity level for the logger.
+
+ Returns:
+ None
+ """
+ if log_level:
+ log_level_name = log_level.value
+ else:
+ log_level_name = LogLevel(get_config_value("VALENTINA_LOG_LEVEL", "INFO").upper())
+
+ http_log_level = get_config_value("VALENTINA_LOG_LEVEL_HTTP", "INFO")
+ aws_log_level = get_config_value("VALENTINA_LOG_LEVEL_AWS", "INFO")
+
+ # Configure Loguru
+ logger.remove()
+ logger.add(
+ sys.stderr,
+ level=log_level_name,
+ colorize=True,
+ format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {name}: {message}",
+ enqueue=True,
+ )
+ logger.add(
+ get_config_value("VALENTINA_LOG_FILE", "valentina.log"),
+ level=log_level_name,
+ rotation="10 MB",
+ retention=3,
+ compression="zip",
+ enqueue=True,
+ )
+
+ # Intercept standard discord.py logs and redirect to Loguru
+ logging.getLogger("discord.http").setLevel(level=http_log_level.upper())
+ logging.getLogger("discord.gateway").setLevel(level=http_log_level.upper())
+ logging.getLogger("discord.webhook").setLevel(level=http_log_level.upper())
+ logging.getLogger("discord.client").setLevel(level=http_log_level.upper())
+ logging.getLogger("faker").setLevel(level="INFO")
+ for service in ["urllib3", "boto3", "botocore", "s3transfer"]:
+ logging.getLogger(service).setLevel(level=aws_log_level.upper())
+ logging.basicConfig(handlers=[InterceptHandler()], level=0, force=True)
+
class InterceptHandler(logging.Handler):
"""Intercepts standard logging and redirects to Loguru.
diff --git a/src/valentina/views/actions.py b/src/valentina/views/actions.py
index 4b148b79..6c796bc9 100644
--- a/src/valentina/views/actions.py
+++ b/src/valentina/views/actions.py
@@ -2,7 +2,7 @@
import discord
-from valentina.constants import EmbedColor, Emoji
+from valentina.constants import EmbedColor, Emoji, LogLevel
from valentina.models.bot import ValentinaContext
from valentina.views import ConfirmCancelButtons, present_embed
@@ -15,6 +15,7 @@ async def confirm_action(
image: str | None = None,
thumbnail: str | None = None,
footer: str | None = None,
+ audit: bool = False,
) -> tuple[bool, discord.Interaction, discord.Embed]:
"""Prompt the user for confirmation.
@@ -26,6 +27,7 @@ async def confirm_action(
image (str, optional): The image URL for the confirmation embed. Defaults to None.
thumbnail (str, optional): The thumbnail URL for the confirmation embed. Defaults to None.
footer: str | None = None,
+ audit (bool): Whether to log the command in the audit log.
Returns:
tuple(bool, discord.InteractionMessage): A tuple containing the user's response and success response coroutine.
@@ -65,4 +67,9 @@ async def confirm_action(
if footer is not None:
response_embed.set_footer(text=footer)
+ if audit:
+ await ctx.post_to_audit_log(title.rstrip("?"))
+ else:
+ ctx.log_command(title.rstrip("?"), LogLevel.DEBUG)
+
return (True, msg, response_embed)
diff --git a/src/valentina/views/thumbnail_review.py b/src/valentina/views/thumbnail_review.py
index c4f3a4b3..9f06f67b 100644
--- a/src/valentina/views/thumbnail_review.py
+++ b/src/valentina/views/thumbnail_review.py
@@ -44,9 +44,7 @@ async def confirm_callback(self, button: Button, interaction: discord.Interactio
await self.guild.save()
# Log to audit log
- await self.ctx.post_to_audit_log(
- f"Deleted thumbnail id `{self.index}`\n{self.thumbnail.url}"
- )
+ await self.ctx.post_to_audit_log(f"Delete thumbnail `{self.index}`\n{self.thumbnail.url}")
# Respond to user
await interaction.response.edit_message(
@@ -94,7 +92,7 @@ async def select_callback(self, select, interaction: discord.Interaction) -> Non
# Log to audit log
await self.ctx.post_to_audit_log(
- f"Thumbnail id `{self.index}` categorized to {new_cat} \n{self.thumbnail.url}",
+ f"Thumbnail `{self.index}` categorized to {new_cat} \n{self.thumbnail.url}",
)
# Respond to user