diff --git a/NzbDrone.Common/HashUtil.cs b/NzbDrone.Common/HashUtil.cs new file mode 100644 index 000000000..53571eae0 --- /dev/null +++ b/NzbDrone.Common/HashUtil.cs @@ -0,0 +1,89 @@ +using System; +using System.Text; +using System.Threading; + +namespace NzbDrone.Common +{ + public static class HashUtil + { + //This should never be changed. very bad things will happen! + private static readonly DateTime Epoch = new DateTime(2010, 1, 1); + + private static readonly object _lock = new object(); + + public static string CalculateCrc(string input) + { + uint mCrc = 0xffffffff; + byte[] bytes = Encoding.UTF8.GetBytes(input); + foreach (byte myByte in bytes) + { + mCrc ^= ((uint)(myByte) << 24); + for (var i = 0; i < 8; i++) + { + if ((Convert.ToUInt32(mCrc) & 0x80000000) == 0x80000000) + { + mCrc = (mCrc << 1) ^ 0x04C11DB7; + } + else + { + mCrc <<= 1; + } + } + } + return String.Format("{0:x8}", mCrc); + } + + public static string GenerateUserId() + { + return GenerateId("u"); + } + + public static string GenerateAppId() + { + return GenerateId("a"); + } + + public static string GenerateApiToken() + { + return Guid.NewGuid().ToString().Replace("-", ""); + } + + public static string GenerateSecurityToken(int length) + { + var byteSize = (length / 4) * 3; + + var linkBytes = new byte[byteSize]; + var rngCrypto = new System.Security.Cryptography.RNGCryptoServiceProvider(); + rngCrypto.GetBytes(linkBytes); + var base64String = Convert.ToBase64String(linkBytes); + + return base64String; + } + + private static string GenerateId(string prefix) + { + lock (_lock) + { + Thread.Sleep(1); + var tick = (DateTime.Now - Epoch).Ticks; + return prefix + "." + ToBase(tick); + } + } + + private static string ToBase(long input) + { + const string BASE_CHARS = "0123456789abcdefghijklmnopqrstuvwxyz"; + int targetBase = BASE_CHARS.Length; + + var result = new StringBuilder(); + do + { + result.Append(BASE_CHARS[(int)(input % targetBase)]); + input /= targetBase; + } while (input > 0); + + return result.ToString(); + } + + } +} \ No newline at end of file diff --git a/NzbDrone.Common/Instrumentation/LogEventExtensions.cs b/NzbDrone.Common/Instrumentation/LogEventExtensions.cs new file mode 100644 index 000000000..80c911509 --- /dev/null +++ b/NzbDrone.Common/Instrumentation/LogEventExtensions.cs @@ -0,0 +1,40 @@ +using System; +using NLog; +using NzbDrone.Common.Serializer; + +namespace NzbDrone.Common.Instrumentation +{ + public static class LogEventExtensions + { + public static string GetHash(this LogEventInfo logEvent) + { + var stackString = Json.Serialize(logEvent.StackTrace); + var hashSeed = String.Concat(logEvent.LoggerName, logEvent.Exception.GetType().ToString(), stackString, logEvent.Level); + return HashUtil.CalculateCrc(hashSeed); + } + + + public static string GetFormattedMessage(this LogEventInfo logEvent) + { + var message = logEvent.FormattedMessage; + + if (logEvent.Exception != null) + { + if (logEvent.Exception != null) + { + if (String.IsNullOrWhiteSpace(message)) + { + message = logEvent.Exception.Message; + } + else + { + message += ": " + logEvent.Exception.Message; + } + } + + } + + return message; + } + } +} \ No newline at end of file diff --git a/NzbDrone.Common/Instrumentation/LogglyTarget.cs b/NzbDrone.Common/Instrumentation/LogglyTarget.cs new file mode 100644 index 000000000..047940c89 --- /dev/null +++ b/NzbDrone.Common/Instrumentation/LogglyTarget.cs @@ -0,0 +1,70 @@ +using System.Collections.Generic; +using NLog; +using NLog.Config; +using NLog.Layouts; +using NLog.Targets; +using NzbDrone.Common.Serializer; +using Logger = Loggly.Logger; + +namespace NzbDrone.Common.Instrumentation +{ + public class LogglyTarget : TargetWithLayout + { + private readonly IEnvironmentProvider _environmentProvider; + private Logger _logger; + + public void Register(LogLevel minLevel) + { + Layout = new SimpleLayout("${callsite:className=false:fileName=false:includeSourcePath=false:methodName=true}"); + + var rule = new LoggingRule("*", minLevel, this); + + LogManager.Configuration.AddTarget("LogglyLogger", this); + LogManager.Configuration.LoggingRules.Add(rule); + LogManager.ConfigurationReloaded += (sender, args) => Register(minLevel); + LogManager.ReconfigExistingLoggers(); + } + + public LogglyTarget(IEnvironmentProvider environmentProvider) + { + _environmentProvider = environmentProvider; + } + + protected override void InitializeTarget() + { + string apiKey = string.Empty; + + if (EnvironmentProvider.IsProduction) + { + apiKey = "4c4ecb69-d1b9-4e2a-b54b-b0c4cc143a95"; + } + else + { + apiKey = "d344a321-b107-45c4-a548-77138f446510"; + } + + _logger = new Logger(apiKey); + } + + + protected override void Write(NLog.LogEventInfo logEvent) + { + var dictionary = new Dictionary(); + + if (logEvent.Exception != null) + { + dictionary.Add("ex", logEvent.Exception.ToString()); + dictionary.Add("extyp", logEvent.Exception.GetType().Name); + dictionary.Add("hash", logEvent.GetHash()); + } + + dictionary.Add("logger", logEvent.LoggerName); + dictionary.Add("method", Layout.Render(logEvent)); + dictionary.Add("level", logEvent.Level.Name); + dictionary.Add("message", logEvent.GetFormattedMessage()); + dictionary.Add("ver", _environmentProvider.Version.ToString()); + + _logger.Log(Json.Serialize(dictionary)); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Common/NzbDrone.Common.csproj b/NzbDrone.Common/NzbDrone.Common.csproj index a7c516c8f..236910802 100644 --- a/NzbDrone.Common/NzbDrone.Common.csproj +++ b/NzbDrone.Common/NzbDrone.Common.csproj @@ -58,6 +58,10 @@ ..\packages\DotNetZip.1.9.1.8\lib\net20\Ionic.Zip.dll + + False + ..\packages\loggly-csharp.2.2\lib\Loggly.dll + False @@ -101,8 +105,11 @@ + + + diff --git a/NzbDrone.Common/packages.config b/NzbDrone.Common/packages.config index 501d8b269..90227afa3 100644 --- a/NzbDrone.Common/packages.config +++ b/NzbDrone.Common/packages.config @@ -1,6 +1,7 @@  + \ No newline at end of file diff --git a/NzbDrone.Core/Datastore/DbFactory.cs b/NzbDrone.Core/Datastore/DbFactory.cs index 50af669b1..d45977514 100644 --- a/NzbDrone.Core/Datastore/DbFactory.cs +++ b/NzbDrone.Core/Datastore/DbFactory.cs @@ -56,7 +56,6 @@ namespace NzbDrone.Core.Datastore connectionBuilder.CacheSize = (int)-10.Megabytes(); connectionBuilder.DateTimeKind = DateTimeKind.Utc; connectionBuilder.JournalMode = SQLiteJournalModeEnum.Wal; - connectionBuilder.Pooling = true; return connectionBuilder.ConnectionString; } diff --git a/NzbDrone/AppMain.cs b/NzbDrone/AppMain.cs index 77b3b7a3b..c7361978e 100644 --- a/NzbDrone/AppMain.cs +++ b/NzbDrone/AppMain.cs @@ -2,6 +2,8 @@ using System.Diagnostics; using System.Reflection; using NLog; +using NzbDrone.Common; +using NzbDrone.Common.Instrumentation; namespace NzbDrone { @@ -14,6 +16,8 @@ namespace NzbDrone { try { + new LogglyTarget(new EnvironmentProvider()).Register(LogLevel.Warn); + logger.Info("Starting NzbDrone Console. Version {0}", Assembly.GetExecutingAssembly().GetName().Version); AppDomain.CurrentDomain.UnhandledException += ((s, e) => AppDomainException(e.ExceptionObject as Exception));