Add job event listener for missed jobs and errors; integrate Sentry for error tracking
This commit is contained in:
@ -11,6 +11,8 @@ from typing import TYPE_CHECKING, Any
|
|||||||
|
|
||||||
import discord
|
import discord
|
||||||
import sentry_sdk
|
import sentry_sdk
|
||||||
|
from apscheduler import events
|
||||||
|
from apscheduler.events import EVENT_JOB_ERROR, EVENT_JOB_MISSED, JobExecutionEvent
|
||||||
from apscheduler.job import Job
|
from apscheduler.job import Job
|
||||||
from discord.abc import PrivateChannel
|
from discord.abc import PrivateChannel
|
||||||
from discord_webhook import DiscordWebhook
|
from discord_webhook import DiscordWebhook
|
||||||
@ -41,6 +43,27 @@ scheduler: settings.AsyncIOScheduler = get_scheduler()
|
|||||||
msg_to_cleanup: list[discord.InteractionMessage] = []
|
msg_to_cleanup: list[discord.InteractionMessage] = []
|
||||||
|
|
||||||
|
|
||||||
|
def my_listener(event: JobExecutionEvent) -> None:
|
||||||
|
"""Listener for job events.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
event: The event that occurred.
|
||||||
|
"""
|
||||||
|
if event.code == events.EVENT_JOB_MISSED:
|
||||||
|
scheduled_time: str = event.scheduled_run_time.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
msg: str = f"Job {event.job_id} was missed! Was scheduled at {scheduled_time}"
|
||||||
|
send_webhook(message=msg)
|
||||||
|
|
||||||
|
if event.exception:
|
||||||
|
with sentry_sdk.push_scope() as scope:
|
||||||
|
scope.set_extra("job_id", event.job_id)
|
||||||
|
scope.set_extra("scheduled_run_time", event.scheduled_run_time.isoformat() if event.scheduled_run_time else "None")
|
||||||
|
scope.set_extra("event_code", event.code)
|
||||||
|
sentry_sdk.capture_exception(event.exception)
|
||||||
|
|
||||||
|
send_webhook(f"discord-reminder-bot failed to send message to Discord\n{event}")
|
||||||
|
|
||||||
|
|
||||||
class RemindBotClient(discord.Client):
|
class RemindBotClient(discord.Client):
|
||||||
"""Custom client class for the bot."""
|
"""Custom client class for the bot."""
|
||||||
|
|
||||||
@ -124,6 +147,7 @@ class RemindBotClient(discord.Client):
|
|||||||
async def setup_hook(self) -> None:
|
async def setup_hook(self) -> None:
|
||||||
"""Setup the bot."""
|
"""Setup the bot."""
|
||||||
scheduler.start()
|
scheduler.start()
|
||||||
|
scheduler.add_listener(my_listener, EVENT_JOB_MISSED | EVENT_JOB_ERROR)
|
||||||
jobs: list[Job] = scheduler.get_jobs()
|
jobs: list[Job] = scheduler.get_jobs()
|
||||||
if not jobs:
|
if not jobs:
|
||||||
logger.info("No jobs available.")
|
logger.info("No jobs available.")
|
||||||
|
@ -2,6 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
import sentry_sdk
|
||||||
from apscheduler.triggers.date import DateTrigger
|
from apscheduler.triggers.date import DateTrigger
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
@ -24,8 +25,16 @@ def calculate(job: Job) -> str | None:
|
|||||||
|
|
||||||
# Check if the job is paused
|
# Check if the job is paused
|
||||||
if trigger_time is None:
|
if trigger_time is None:
|
||||||
logger.error(f"Couldn't calculate time for job: {job.id}")
|
with sentry_sdk.push_scope() as scope:
|
||||||
logger.error(f"State: {job.__getstate__() if hasattr(job, '__getstate__') else 'No state'}")
|
scope.set_tag("job_id", job.id)
|
||||||
|
scope.set_extra("job_state", job.__getstate__() if hasattr(job, "__getstate__") else "No state")
|
||||||
|
sentry_sdk.capture_exception(Exception("Couldn't calculate time for job"))
|
||||||
|
|
||||||
|
msg: str = f"Couldn't calculate time for job: {job.id}"
|
||||||
|
if hasattr(job, "__getstate__"):
|
||||||
|
msg += f"State: {job.__getstate__()}"
|
||||||
|
|
||||||
|
logger.error(msg)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return f"<t:{int(trigger_time.timestamp())}:R>"
|
return f"<t:{int(trigger_time.timestamp())}:R>"
|
||||||
|
@ -11,7 +11,7 @@ from apscheduler.triggers.interval import IntervalTrigger
|
|||||||
from discord.ui import Button, Select
|
from discord.ui import Button, Select
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from discord_reminder_bot.misc import DateTrigger, calc_time, calculate
|
from discord_reminder_bot.misc import calc_time, calculate
|
||||||
from discord_reminder_bot.parser import parse_time
|
from discord_reminder_bot.parser import parse_time
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -309,33 +309,33 @@ class JobManagementView(discord.ui.View):
|
|||||||
|
|
||||||
job_msg: str | int = job_kwargs.get("message", "No message found")
|
job_msg: str | int = job_kwargs.get("message", "No message found")
|
||||||
msg: str = f"**Job '{job_msg}' has been deleted.**\n"
|
msg: str = f"**Job '{job_msg}' has been deleted.**\n"
|
||||||
msg += f"**Job ID**: {self.job.id}\n"
|
msg += f"**Job ID**: {self.job.id}"
|
||||||
|
|
||||||
# The time the job was supposed to run
|
# The time the job was supposed to run
|
||||||
if hasattr(self.job, "next_run_time"):
|
if hasattr(self.job, "next_run_time"):
|
||||||
if self.job.next_run_time:
|
if self.job.next_run_time:
|
||||||
msg += f"**Next run time**: {self.job.next_run_time} ({calculate(self.job)})\n"
|
msg += f"\n**Next run time**: {self.job.next_run_time} ({calculate(self.job)})"
|
||||||
else:
|
else:
|
||||||
msg += "**Next run time**: Paused\n"
|
msg += "\n**Next run time**: Paused"
|
||||||
msg += f"**Trigger**: {self.job.trigger}\n"
|
msg += f"\n**Trigger**: {self.job.trigger}"
|
||||||
else:
|
else:
|
||||||
msg += "**Next run time**: Pending\n"
|
msg += "\n**Next run time**: Pending\n"
|
||||||
|
|
||||||
# The Discord user who created the job
|
# The Discord user who created the job
|
||||||
if job_kwargs.get("author_id"):
|
if job_kwargs.get("author_id"):
|
||||||
msg += f"**Created by**: <@{job_kwargs.get('author_id')}>\n"
|
msg += f"\n**Created by**: <@{job_kwargs.get('author_id')}>"
|
||||||
|
|
||||||
# The Discord channel to send the message to
|
# The Discord channel to send the message to
|
||||||
if job_kwargs.get("channel_id"):
|
if job_kwargs.get("channel_id"):
|
||||||
msg += f"**Channel**: <#{job_kwargs.get('channel_id')}>\n"
|
msg += f"\n**Channel**: <#{job_kwargs.get('channel_id')}>"
|
||||||
|
|
||||||
# The Discord user to send the message to
|
# The Discord user to send the message to
|
||||||
if job_kwargs.get("user_id"):
|
if job_kwargs.get("user_id"):
|
||||||
msg += f"**User**: <@{job_kwargs.get('user_id')}>\n"
|
msg += f"\n**User**: <@{job_kwargs.get('user_id')}>"
|
||||||
|
|
||||||
# The Discord guild to send the message to
|
# The Discord guild to send the message to
|
||||||
if job_kwargs.get("guild_id"):
|
if job_kwargs.get("guild_id"):
|
||||||
msg += f"**Guild**: {job_kwargs.get('guild_id')}\n"
|
msg += f"\n**Guild**: {job_kwargs.get('guild_id')}"
|
||||||
|
|
||||||
logger.debug(f"Deletion message: {msg}")
|
logger.debug(f"Deletion message: {msg}")
|
||||||
|
|
||||||
@ -390,11 +390,15 @@ class JobManagementView(discord.ui.View):
|
|||||||
job_author: int = job_kwargs.get("author_id", 0)
|
job_author: int = job_kwargs.get("author_id", 0)
|
||||||
msg: str = f"Job '{job_msg}' has been {status} by <@{interaction.user.id}>. Job was created by <@{job_author}>."
|
msg: str = f"Job '{job_msg}' has been {status} by <@{interaction.user.id}>. Job was created by <@{job_author}>."
|
||||||
|
|
||||||
|
# The time the job was supposed to run
|
||||||
if hasattr(self.job, "next_run_time"):
|
if hasattr(self.job, "next_run_time"):
|
||||||
trigger_time: datetime.datetime | None = (
|
if self.job.next_run_time:
|
||||||
self.job.trigger.run_date if isinstance(self.job.trigger, DateTrigger) else self.job.next_run_time
|
msg += f"\n**Next run time**: {self.job.next_run_time} ({calculate(self.job)})"
|
||||||
)
|
else:
|
||||||
msg += f"\nNext run time: {trigger_time} {calculate(self.job)}"
|
msg += "\n**Next run time**: Paused"
|
||||||
|
msg += f"\n**Trigger**: {self.job.trigger}"
|
||||||
|
else:
|
||||||
|
msg += "\n**Next run time**: Pending"
|
||||||
|
|
||||||
await interaction.followup.send(msg)
|
await interaction.followup.send(msg)
|
||||||
|
|
||||||
@ -416,5 +420,5 @@ class JobManagementView(discord.ui.View):
|
|||||||
bool: Whether the interaction is valid.
|
bool: Whether the interaction is valid.
|
||||||
"""
|
"""
|
||||||
logger.info(f"Checking interaction for job: {self.job.id}")
|
logger.info(f"Checking interaction for job: {self.job.id}")
|
||||||
self.update_buttons()
|
# self.update_buttons()
|
||||||
return True
|
return True
|
||||||
|
Reference in New Issue
Block a user