2021-11-09 06:48:47 +00:00
using System.Diagnostics ;
2020-09-20 20:36:04 +00:00
2020-11-16 09:07:57 +00:00
using App.Metrics ;
2020-12-23 01:19:02 +00:00
using Myriad.Builders ;
using Myriad.Rest ;
2021-11-27 02:10:56 +00:00
using Myriad.Rest.Types.Requests ;
using Myriad.Types ;
2020-09-20 20:36:04 +00:00
using NodaTime ;
2020-11-16 09:07:57 +00:00
using Serilog ;
2021-11-27 02:10:56 +00:00
namespace PluralKit.Bot ;
public class ErrorMessageService
2020-09-20 20:36:04 +00:00
{
2021-11-27 02:10:56 +00:00
// globally rate limit errors for now, don't want to spam users when something breaks
private static readonly Duration MinErrorInterval = Duration . FromSeconds ( 10 ) ;
private static readonly Duration IntervalFromStartup = Duration . FromMinutes ( 5 ) ;
private readonly ILogger _logger ;
private readonly IMetrics _metrics ;
private readonly DiscordApiClient _rest ;
public ErrorMessageService ( IMetrics metrics , ILogger logger , DiscordApiClient rest )
2020-09-20 20:36:04 +00:00
{
2021-11-27 02:10:56 +00:00
_metrics = metrics ;
_logger = logger ;
_rest = rest ;
2021-11-09 06:48:47 +00:00
2021-11-27 02:10:56 +00:00
lastErrorTime = SystemClock . Instance . GetCurrentInstant ( ) ;
}
2021-08-27 15:03:47 +00:00
2021-11-27 02:10:56 +00:00
// private readonly ConcurrentDictionary<ulong, Instant> _lastErrorInChannel = new ConcurrentDictionary<ulong, Instant>();
private Instant lastErrorTime { get ; set ; }
2021-08-27 15:03:47 +00:00
2021-11-27 02:10:56 +00:00
public async Task SendErrorMessage ( ulong channelId , string errorId )
{
var now = SystemClock . Instance . GetCurrentInstant ( ) ;
if ( ! ShouldSendErrorMessage ( channelId , now ) )
2020-11-16 09:07:57 +00:00
{
2021-11-27 02:10:56 +00:00
_logger . Warning ( "Rate limited sending error message to {ChannelId} with error code {ErrorId}" ,
channelId , errorId ) ;
_metrics . Measure . Meter . Mark ( BotMetrics . ErrorMessagesSent , "throttled" ) ;
return ;
2020-11-16 09:07:57 +00:00
}
2020-09-20 20:36:04 +00:00
2021-11-27 02:10:56 +00:00
var embed = new EmbedBuilder ( )
. Color ( 0xE74C3C )
. Title ( "Internal error occurred" )
. Description (
"For support, please send the error code above in **#bug-reports-and-errors** on **[the support server *(click to join)*](https://discord.gg/PczBt78)** with a description of what you were doing at the time." )
. Footer ( new Embed . EmbedFooter ( errorId ) )
. Timestamp ( now . ToDateTimeOffset ( ) . ToString ( "O" ) ) ;
try
2020-09-20 20:36:04 +00:00
{
2021-11-27 02:10:56 +00:00
await _rest . CreateMessage ( channelId ,
new MessageRequest { Content = $"> **Error code:** `{errorId}`" , Embed = embed . Build ( ) } ) ;
2020-11-16 09:07:57 +00:00
2021-11-27 02:10:56 +00:00
_logger . Information ( "Sent error message to {ChannelId} with error code {ErrorId}" , channelId , errorId ) ;
_metrics . Measure . Meter . Mark ( BotMetrics . ErrorMessagesSent , "sent" ) ;
}
catch ( Exception e )
2020-11-16 09:07:57 +00:00
{
2021-11-27 02:10:56 +00:00
_logger . Error ( e , "Error sending error message to {ChannelId}" , channelId ) ;
_metrics . Measure . Meter . Mark ( BotMetrics . ErrorMessagesSent , "failed" ) ;
throw ;
}
}
2021-11-09 06:48:47 +00:00
2021-11-27 02:10:56 +00:00
private bool ShouldSendErrorMessage ( ulong channelId , Instant now )
{
// if (_lastErrorInChannel.TryGetValue(channelId, out var lastErrorTime))
2021-11-09 06:48:47 +00:00
2021-11-27 02:10:56 +00:00
var startupTime = Instant . FromDateTimeUtc ( Process . GetCurrentProcess ( ) . StartTime . ToUniversalTime ( ) ) ;
// don't send errors during startup
// mostly because Npgsql throws a bunch of errors when opening connections sometimes???
if ( now - startupTime < IntervalFromStartup )
return false ;
2020-11-16 09:07:57 +00:00
2021-11-27 02:10:56 +00:00
var interval = now - lastErrorTime ;
if ( interval < MinErrorInterval )
return false ;
// _lastErrorInChannel[channelId] = now;
lastErrorTime = now ;
return true ;
2020-09-20 20:36:04 +00:00
}
}