Refactor Sentry scope enrichment

This commit is contained in:
Ske 2020-05-02 16:00:43 +02:00
parent f927450352
commit f97ceac1fb
7 changed files with 111 additions and 56 deletions

View File

@ -47,6 +47,8 @@ namespace PluralKit.Bot
_client.DebugLogger.LogMessageReceived += FrameworkLog; _client.DebugLogger.LogMessageReceived += FrameworkLog;
// HandleEvent takes a type parameter, automatically inferred by the event type // HandleEvent takes a type parameter, automatically inferred by the event type
// It will then look up an IEventHandler<TypeOfEvent> in the DI container and call that object's handler method
// For registering new ones, see Modules.cs
_client.MessageCreated += HandleEvent; _client.MessageCreated += HandleEvent;
_client.MessageDeleted += HandleEvent; _client.MessageDeleted += HandleEvent;
_client.MessageUpdated += HandleEvent; _client.MessageUpdated += HandleEvent;
@ -69,8 +71,14 @@ namespace PluralKit.Bot
async Task HandleEventInner() async Task HandleEventInner()
{ {
var serviceScope = _services.BeginLifetimeScope(); var serviceScope = _services.BeginLifetimeScope();
// Find an event handler that can handle the type of event (<T>) we're given
var handler = serviceScope.Resolve<IEventHandler<T>>(); var handler = serviceScope.Resolve<IEventHandler<T>>();
// Also, find a Sentry enricher for the event type (if one is present), and ask it to put some event data in the Sentry scope
var sentryEnricher = serviceScope.ResolveOptional<ISentryEnricher<T>>();
sentryEnricher?.Enrich(serviceScope.Resolve<Scope>(), evt);
try try
{ {
await handler.Handle(evt); await handler.Handle(evt);

View File

@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using App.Metrics; using App.Metrics;
@ -129,21 +128,6 @@ namespace PluralKit.Bot
{ {
_metrics.Measure.Meter.Mark(BotMetrics.MessagesReceived); _metrics.Measure.Meter.Mark(BotMetrics.MessagesReceived);
_lastMessageCache.AddMessage(evt.Channel.Id, evt.Message.Id); _lastMessageCache.AddMessage(evt.Channel.Id, evt.Message.Id);
// Add message info as Sentry breadcrumb
_sentryScope.AddBreadcrumb(evt.Message.Content, "event.message", data: new Dictionary<string, string>
{
{"user", evt.Author.Id.ToString()},
{"channel", evt.Channel.Id.ToString()},
{"guild", evt.Channel.GuildId.ToString()},
{"message", evt.Message.Id.ToString()},
});
_sentryScope.SetTag("shard", evt.Client.ShardId.ToString());
// Also report information about the bot's permissions in the channel
// We get a lot of permission errors so this'll be useful for determining problems
var perms = evt.Channel.BotPermissions();
_sentryScope.AddBreadcrumb(perms.ToPermissionString(), "permissions");
} }
} }
} }

View File

@ -12,37 +12,19 @@ namespace PluralKit.Bot
public class MessageDeleted: IEventHandler<MessageDeleteEventArgs>, IEventHandler<MessageBulkDeleteEventArgs> public class MessageDeleted: IEventHandler<MessageDeleteEventArgs>, IEventHandler<MessageBulkDeleteEventArgs>
{ {
private readonly ProxyService _proxy; private readonly ProxyService _proxy;
private readonly Scope _sentryScope;
public MessageDeleted(Scope sentryScope, ProxyService proxy) public MessageDeleted(ProxyService proxy)
{ {
_sentryScope = sentryScope;
_proxy = proxy; _proxy = proxy;
} }
public Task Handle(MessageDeleteEventArgs evt) public Task Handle(MessageDeleteEventArgs evt)
{ {
_sentryScope.AddBreadcrumb("", "event.messageDelete", data: new Dictionary<string, string>()
{
{"channel", evt.Channel.Id.ToString()},
{"guild", evt.Channel.GuildId.ToString()},
{"message", evt.Message.Id.ToString()},
});
_sentryScope.SetTag("shard", evt.Client.ShardId.ToString());
return _proxy.HandleMessageDeletedAsync(evt); return _proxy.HandleMessageDeletedAsync(evt);
} }
public Task Handle(MessageBulkDeleteEventArgs evt) public Task Handle(MessageBulkDeleteEventArgs evt)
{ {
_sentryScope.AddBreadcrumb("", "event.messageDelete", data: new Dictionary<string, string>()
{
{"channel", evt.Channel.Id.ToString()},
{"guild", evt.Channel.Id.ToString()},
{"messages", string.Join(",", evt.Messages.Select(m => m.Id))},
});
_sentryScope.SetTag("shard", evt.Client.ShardId.ToString());
return _proxy.HandleMessageBulkDeleteAsync(evt); return _proxy.HandleMessageBulkDeleteAsync(evt);
} }
} }

View File

@ -35,14 +35,6 @@ namespace PluralKit.Bot
// Also, if this is in DMs don't bother either // Also, if this is in DMs don't bother either
if (evt.Channel.Guild == null) return; if (evt.Channel.Guild == null) return;
_sentryScope.AddBreadcrumb(evt.Message.Content ?? "<unknown>", "event.messageEdit", data: new Dictionary<string, string>()
{
{"channel", evt.Channel.Id.ToString()},
{"guild", evt.Channel.GuildId.ToString()},
{"message", evt.Message.Id.ToString()}
});
_sentryScope.SetTag("shard", evt.Client.ShardId.ToString());
// If this isn't the last message in the channel, don't do anything // If this isn't the last message in the channel, don't do anything
if (_lastMessageCache.GetLastMessage(evt.Channel.Id) != evt.Message.Id) return; if (_lastMessageCache.GetLastMessage(evt.Channel.Id) != evt.Message.Id) return;

View File

@ -10,25 +10,14 @@ namespace PluralKit.Bot
public class ReactionAdded: IEventHandler<MessageReactionAddEventArgs> public class ReactionAdded: IEventHandler<MessageReactionAddEventArgs>
{ {
private readonly ProxyService _proxy; private readonly ProxyService _proxy;
private readonly Scope _sentryScope;
public ReactionAdded(ProxyService proxy, Scope sentryScope) public ReactionAdded(ProxyService proxy)
{ {
_proxy = proxy; _proxy = proxy;
_sentryScope = sentryScope;
} }
public Task Handle(MessageReactionAddEventArgs evt) public Task Handle(MessageReactionAddEventArgs evt)
{ {
_sentryScope.AddBreadcrumb("", "event.reaction", data: new Dictionary<string, string>()
{
{"user", evt.User.Id.ToString()},
{"channel", (evt.Channel?.Id ?? 0).ToString()},
{"guild", (evt.Channel?.GuildId ?? 0).ToString()},
{"message", evt.Message.Id.ToString()},
{"reaction", evt.Emoji.Name}
});
_sentryScope.SetTag("shard", evt.Client.ShardId.ToString());
return _proxy.HandleReactionAddedAsync(evt); return _proxy.HandleReactionAddedAsync(evt);
} }
} }

View File

@ -69,6 +69,13 @@ namespace PluralKit.Bot
// Sentry stuff // Sentry stuff
builder.Register(_ => new Scope(null)).AsSelf().InstancePerLifetimeScope(); builder.Register(_ => new Scope(null)).AsSelf().InstancePerLifetimeScope();
builder.RegisterType<SentryEnricher>()
.As<ISentryEnricher<MessageCreateEventArgs>>()
.As<ISentryEnricher<MessageDeleteEventArgs>>()
.As<ISentryEnricher<MessageUpdateEventArgs>>()
.As<ISentryEnricher<MessageBulkDeleteEventArgs>>()
.As<ISentryEnricher<MessageReactionAddEventArgs>>()
.SingleInstance();
// Utils // Utils
builder.Register(c => new HttpClient builder.Register(c => new HttpClient

View File

@ -0,0 +1,93 @@
using System.Collections.Generic;
using System.Linq;
using DSharpPlus;
using DSharpPlus.EventArgs;
using Sentry;
namespace PluralKit.Bot
{
public interface ISentryEnricher<T> where T: DiscordEventArgs
{
void Enrich(Scope scope, T evt);
}
public class SentryEnricher:
ISentryEnricher<MessageCreateEventArgs>,
ISentryEnricher<MessageDeleteEventArgs>,
ISentryEnricher<MessageUpdateEventArgs>,
ISentryEnricher<MessageBulkDeleteEventArgs>,
ISentryEnricher<MessageReactionAddEventArgs>
{
// TODO: should this class take the Scope by dependency injection instead?
// Would allow us to create a centralized "chain of handlers" where this class could just be registered as an entry in
public void Enrich(Scope scope, MessageCreateEventArgs evt)
{
scope.AddBreadcrumb(evt.Message.Content, "event.message", data: new Dictionary<string, string>
{
{"user", evt.Author.Id.ToString()},
{"channel", evt.Channel.Id.ToString()},
{"guild", evt.Channel.GuildId.ToString()},
{"message", evt.Message.Id.ToString()},
});
scope.SetTag("shard", evt.Client.ShardId.ToString());
// Also report information about the bot's permissions in the channel
// We get a lot of permission errors so this'll be useful for determining problems
var perms = evt.Channel.BotPermissions();
scope.AddBreadcrumb(perms.ToPermissionString(), "permissions");
}
public void Enrich(Scope scope, MessageDeleteEventArgs evt)
{
scope.AddBreadcrumb("", "event.messageDelete",
data: new Dictionary<string, string>()
{
{"channel", evt.Channel.Id.ToString()},
{"guild", evt.Channel.GuildId.ToString()},
{"message", evt.Message.Id.ToString()},
});
scope.SetTag("shard", evt.Client.ShardId.ToString());
}
public void Enrich(Scope scope, MessageUpdateEventArgs evt)
{
scope.AddBreadcrumb(evt.Message.Content ?? "<unknown>", "event.messageEdit",
data: new Dictionary<string, string>()
{
{"channel", evt.Channel.Id.ToString()},
{"guild", evt.Channel.GuildId.ToString()},
{"message", evt.Message.Id.ToString()}
});
scope.SetTag("shard", evt.Client.ShardId.ToString());
}
public void Enrich(Scope scope, MessageBulkDeleteEventArgs evt)
{
scope.AddBreadcrumb("", "event.messageDelete",
data: new Dictionary<string, string>()
{
{"channel", evt.Channel.Id.ToString()},
{"guild", evt.Channel.Id.ToString()},
{"messages", string.Join(",", evt.Messages.Select(m => m.Id))},
});
scope.SetTag("shard", evt.Client.ShardId.ToString());
}
public void Enrich(Scope scope, MessageReactionAddEventArgs evt)
{
scope.AddBreadcrumb("", "event.reaction",
data: new Dictionary<string, string>()
{
{"user", evt.User.Id.ToString()},
{"channel", (evt.Channel?.Id ?? 0).ToString()},
{"guild", (evt.Channel?.GuildId ?? 0).ToString()},
{"message", evt.Message.Id.ToString()},
{"reaction", evt.Emoji.Name}
});
scope.SetTag("shard", evt.Client.ShardId.ToString());
}
}
}