2021-08-27 15:03:47 +00:00
using System ;
2020-11-16 09:07:57 +00:00
using System.Collections.Concurrent ;
2021-11-09 06:48:47 +00:00
using System.Diagnostics ;
2020-09-20 20:36:04 +00:00
using System.Threading.Tasks ;
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 ;
2020-09-20 20:36:04 +00:00
using NodaTime ;
2020-11-16 09:07:57 +00:00
using Serilog ;
2020-09-20 20:36:04 +00:00
namespace PluralKit.Bot
{
public class ErrorMessageService
{
2021-11-09 06:48:47 +00:00
// globally rate limit errors for now, don't want to spam users when something breaks
2020-09-20 20:36:04 +00:00
private static readonly Duration MinErrorInterval = Duration . FromSeconds ( 10 ) ;
2021-11-09 06:48:47 +00:00
private static readonly Duration IntervalFromStartup = Duration . FromMinutes ( 5 ) ;
// private readonly ConcurrentDictionary<ulong, Instant> _lastErrorInChannel = new ConcurrentDictionary<ulong, Instant>();
private Instant lastErrorTime { get ; set ; }
2021-08-27 15:03:47 +00:00
2020-11-16 09:07:57 +00:00
private readonly IMetrics _metrics ;
private readonly ILogger _logger ;
2020-12-23 01:19:02 +00:00
private readonly DiscordApiClient _rest ;
2021-08-27 15:03:47 +00:00
2020-12-23 01:19:02 +00:00
public ErrorMessageService ( IMetrics metrics , ILogger logger , DiscordApiClient rest )
2020-11-16 09:07:57 +00:00
{
_metrics = metrics ;
_logger = logger ;
2020-12-23 01:19:02 +00:00
_rest = rest ;
2021-11-09 06:48:47 +00:00
lastErrorTime = SystemClock . Instance . GetCurrentInstant ( ) ;
2020-11-16 09:07:57 +00:00
}
2020-09-20 20:36:04 +00:00
2020-12-23 01:19:02 +00:00
public async Task SendErrorMessage ( ulong channelId , string errorId )
2020-09-20 20:36:04 +00:00
{
2020-11-16 09:07:57 +00:00
var now = SystemClock . Instance . GetCurrentInstant ( ) ;
2020-12-23 01:19:02 +00:00
if ( ! ShouldSendErrorMessage ( channelId , now ) )
2020-09-20 20:36:04 +00:00
{
2020-12-23 01:19:02 +00:00
_logger . Warning ( "Rate limited sending error message to {ChannelId} with error code {ErrorId}" , channelId , errorId ) ;
2020-11-16 09:07:57 +00:00
_metrics . Measure . Meter . Mark ( BotMetrics . ErrorMessagesSent , "throttled" ) ;
return ;
2020-09-20 20:36:04 +00:00
}
2020-11-16 09:07:57 +00:00
2020-12-23 01:19:02 +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 ( errorId ) )
. Timestamp ( now . ToDateTimeOffset ( ) . ToString ( "O" ) ) ;
2020-11-16 09:07:57 +00:00
try
{
2020-12-23 01:19:02 +00:00
await _rest . CreateMessage ( channelId , new ( )
{
Content = $"> **Error code:** `{errorId}`" ,
Embed = embed . Build ( )
} ) ;
2021-08-27 15:03:47 +00:00
2020-12-23 01:19:02 +00:00
_logger . Information ( "Sent error message to {ChannelId} with error code {ErrorId}" , channelId , errorId ) ;
2020-11-16 09:07:57 +00:00
_metrics . Measure . Meter . Mark ( BotMetrics . ErrorMessagesSent , "sent" ) ;
}
catch ( Exception e )
{
2020-12-23 01:19:02 +00:00
_logger . Error ( e , "Error sending error message to {ChannelId}" , channelId ) ;
2020-11-16 09:07:57 +00:00
_metrics . Measure . Meter . Mark ( BotMetrics . ErrorMessagesSent , "failed" ) ;
throw ;
}
}
2020-12-23 01:19:02 +00:00
private bool ShouldSendErrorMessage ( ulong channelId , Instant now )
2020-11-16 09:07:57 +00:00
{
2021-11-09 06:48:47 +00:00
// if (_lastErrorInChannel.TryGetValue(channelId, out var lastErrorTime))
var startupTime = Instant . FromDateTimeUtc ( Process . GetCurrentProcess ( ) . StartTime ) ;
// don't send errors during startup
// mostly because Npgsql throws a bunch of errors when opening connections sometimes???
if ( ( now - startupTime ) < IntervalFromStartup )
return false ;
var interval = now - lastErrorTime ;
if ( interval < MinErrorInterval )
return false ;
2020-11-16 09:07:57 +00:00
2021-11-09 06:48:47 +00:00
// _lastErrorInChannel[channelId] = now;
lastErrorTime = now ;
2020-11-16 09:07:57 +00:00
return true ;
2020-09-20 20:36:04 +00:00
}
}
}