From 76a7d4f866c8e0e38826cbf185dd587d5f73790f Mon Sep 17 00:00:00 2001
From: Keivan Beigi <me@keivan.io>
Date: Thu, 5 Jan 2017 14:00:25 -0800
Subject: [PATCH] Use line number instead of message for sentry fingerprint

---
 .../Instrumentation/NzbDroneLogger.cs         |  5 ++-
 .../Sentry/PopulateStackTraceRenderer.cs      | 20 +++++++++
 .../Instrumentation/Sentry/SentryTarget.cs    | 45 ++++++++++++++-----
 src/NzbDrone.Common/NzbDrone.Common.csproj    |  1 +
 4 files changed, 58 insertions(+), 13 deletions(-)
 create mode 100644 src/NzbDrone.Common/Instrumentation/Sentry/PopulateStackTraceRenderer.cs

diff --git a/src/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs b/src/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs
index 7a05776ae..836938de5 100644
--- a/src/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs
+++ b/src/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs
@@ -33,6 +33,8 @@ namespace NzbDrone.Common.Instrumentation
 
             GlobalExceptionHandlers.Register();
 
+            ConfigurationItemFactory.Default.LayoutRenderers.RegisterDefinition("populatestacktrace", typeof(PopulateStackTraceRenderer));
+
             var appFolderInfo = new AppFolderInfo(startupContext);
 
             if (Debugger.IsAttached)
@@ -106,10 +108,11 @@ namespace NzbDrone.Common.Instrumentation
             var target = new SentryTarget(dsn)
             {
                 Name = "sentryTarget",
-                Layout = "${message}"
+                Layout = "${message}${populatestacktrace}"
             };
 
             var loggingRule = new LoggingRule("*", updateClient ? LogLevel.Trace : LogLevel.Error, target);
+
             LogManager.Configuration.AddTarget("sentryTarget", target);
             LogManager.Configuration.LoggingRules.Add(loggingRule);
         }
diff --git a/src/NzbDrone.Common/Instrumentation/Sentry/PopulateStackTraceRenderer.cs b/src/NzbDrone.Common/Instrumentation/Sentry/PopulateStackTraceRenderer.cs
new file mode 100644
index 000000000..bc21f11e3
--- /dev/null
+++ b/src/NzbDrone.Common/Instrumentation/Sentry/PopulateStackTraceRenderer.cs
@@ -0,0 +1,20 @@
+using System.Text;
+using NLog;
+using NLog.Config;
+using NLog.Internal;
+using NLog.LayoutRenderers;
+
+namespace NzbDrone.Common.Instrumentation.Sentry
+{
+    [ThreadAgnostic]
+    [LayoutRenderer("populatestacktrace")]
+    public class PopulateStackTraceRenderer : LayoutRenderer, IUsesStackTrace
+    {
+        StackTraceUsage IUsesStackTrace.StackTraceUsage => StackTraceUsage.WithSource;
+
+        protected override void Append(StringBuilder builder, LogEventInfo logEvent)
+        {
+            // This is only used to populate the stacktrace.  doesn't actually render anything.
+        }
+    }
+}
diff --git a/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs b/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs
index 4be22f0a9..e35e6cf89 100644
--- a/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs
+++ b/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs
@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Linq;
 using System.Threading;
 using NLog;
@@ -54,6 +55,35 @@ namespace NzbDrone.Common.Instrumentation.Sentry
             _client.Tags.Add("version", BuildInfo.Version.ToString());
         }
 
+
+        private List<string> GetFingerPrint(LogEventInfo logEvent)
+        {
+            var fingerPrint = new List<string>
+            {
+                logEvent.Level.Ordinal.ToString(),
+            };
+
+            var lineNumber = "";
+
+            if (logEvent.StackTrace != null)
+            {
+                var stackFrame = logEvent.StackTrace.GetFrame(logEvent.UserStackFrameNumber);
+                if (stackFrame != null)
+                {
+                    lineNumber = $"#{stackFrame.GetFileLineNumber()}";
+                }
+            }
+
+            fingerPrint.Add(logEvent.LoggerName + lineNumber);
+
+            if (logEvent.Exception != null)
+            {
+                fingerPrint.Add(logEvent.Exception.GetType().Name);
+            }
+
+            return fingerPrint;
+        }
+
         protected override void Write(LogEventInfo logEvent)
         {
             try
@@ -64,7 +94,6 @@ namespace NzbDrone.Common.Instrumentation.Sentry
                     return;
                 }
 
-
                 var extras = logEvent.Properties.ToDictionary(x => x.Key.ToString(), x => x.Value.ToString());
                 _client.Logger = logEvent.LoggerName;
 
@@ -73,19 +102,11 @@ namespace NzbDrone.Common.Instrumentation.Sentry
                 {
                     Level = LoggingLevelMap[logEvent.Level],
                     Message = sentryMessage,
-                    Extra = extras,
-                    Fingerprint =
-                    {
-                        logEvent.Level.ToString(),
-                        logEvent.LoggerName,
-                        logEvent.Message
-                    }
+                    Extra = extras
                 };
 
-                if (logEvent.Exception != null)
-                {
-                    sentryEvent.Fingerprint.Add(logEvent.Exception.GetType().FullName);
-                }
+                var fingerPrint = GetFingerPrint(logEvent);
+                fingerPrint.ForEach(c => sentryEvent.Fingerprint.Add(c));
 
                 sentryEvent.Tags.Add("os_name", Environment.GetEnvironmentVariable("OS_NAME"));
                 sentryEvent.Tags.Add("os_version", Environment.GetEnvironmentVariable("OS_VERSION"));
diff --git a/src/NzbDrone.Common/NzbDrone.Common.csproj b/src/NzbDrone.Common/NzbDrone.Common.csproj
index c8f98dd84..dcc5d5a2c 100644
--- a/src/NzbDrone.Common/NzbDrone.Common.csproj
+++ b/src/NzbDrone.Common/NzbDrone.Common.csproj
@@ -182,6 +182,7 @@
     <Compile Include="Instrumentation\LogEventExtensions.cs" />
     <Compile Include="Instrumentation\NzbDroneFileTarget.cs" />
     <Compile Include="Instrumentation\NzbDroneLogger.cs" />
+    <Compile Include="Instrumentation\Sentry\PopulateStackTraceRenderer.cs" />
     <Compile Include="Instrumentation\Sentry\SentryTarget.cs" />
     <Compile Include="Instrumentation\VersionLayoutRenderer.cs" />
     <Compile Include="Extensions\LevenstheinExtensions.cs" />