mirror of
https://github.com/lidarr/Lidarr
synced 2024-12-24 16:51:58 +00:00
Finished Exception Controller
This commit is contained in:
parent
98faca17ee
commit
9cebfab2f4
14 changed files with 255 additions and 39 deletions
|
@ -5,7 +5,7 @@ namespace NzbDrone.Common.Contract
|
|||
{
|
||||
public class ExceptionReportResponse
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public int ExceptionId { get; set; }
|
||||
[JsonProperty("h")]
|
||||
public string ExceptionHash { get; set; }
|
||||
}
|
||||
}
|
26
NzbDrone.Common/Contract/ExistingExceptionReport.cs
Normal file
26
NzbDrone.Common/Contract/ExistingExceptionReport.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NzbDrone.Common.Contract
|
||||
{
|
||||
public class ExistingExceptionReport : ReportBase
|
||||
{
|
||||
|
||||
[JsonProperty("h")]
|
||||
public string Hash { get; set; }
|
||||
|
||||
[JsonProperty("lm")]
|
||||
public string LogMessage { get; set; }
|
||||
|
||||
protected override Dictionary<string, string> GetString()
|
||||
{
|
||||
var dic = new Dictionary<string, string>
|
||||
{
|
||||
{"Message", LogMessage.NullSafe()}
|
||||
};
|
||||
|
||||
return dic;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -55,6 +55,7 @@
|
|||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Contract\ExceptionReportResponse.cs" />
|
||||
<Compile Include="Contract\ExistingExceptionReport.cs" />
|
||||
<Compile Include="StringExtention.cs" />
|
||||
<Compile Include="HttpProvider.cs" />
|
||||
<Compile Include="ConfigFileProvider.cs" />
|
||||
|
|
|
@ -15,7 +15,7 @@ public static class Logging
|
|||
|
||||
public static void PreStart()
|
||||
{
|
||||
string logPath = string.Format("C:\\NLog\\{0}\\{1}\\${{shortdate}}.log", HostingEnvironment.SiteName, new EnviromentProvider().Version);
|
||||
string logPath = string.Format("C:\\NLog\\{0}\\{1}\\${{shortdate}}-${{logger}}.log", HostingEnvironment.SiteName, new EnviromentProvider().Version);
|
||||
string error = string.Format("C:\\NLog\\{0}\\{1}\\${{shortdate}}_Error.log", HostingEnvironment.SiteName, new EnviromentProvider().Version);
|
||||
|
||||
LogConfiguration.RegisterUdpLogger();
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
using System.Linq;
|
||||
using System.Web.Mvc;
|
||||
using NLog;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Common.Contract;
|
||||
using NzbDrone.Services.Service.Repository.Reporting;
|
||||
using Services.PetaPoco;
|
||||
|
@ -14,23 +13,53 @@ public class ExceptionController : Controller
|
|||
private readonly IDatabase _database;
|
||||
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
|
||||
|
||||
private const string OK = "OK";
|
||||
|
||||
public ExceptionController(IDatabase database)
|
||||
{
|
||||
_database = database;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public EmptyResult ReportExisting(ExistingExceptionReport existingExceptionReport)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ExceptionHashExists(existingExceptionReport.Hash))
|
||||
{
|
||||
|
||||
var exceptionInstance = new ExceptionInstance
|
||||
{
|
||||
ExceptionHash = existingExceptionReport.Hash,
|
||||
IsProduction = existingExceptionReport.IsProduction,
|
||||
LogMessage = existingExceptionReport.LogMessage,
|
||||
Timestamp = DateTime.Now
|
||||
};
|
||||
|
||||
_database.Insert(exceptionInstance);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Warn("Invalid exception hash '{0}'", existingExceptionReport.Hash);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.FatalException("Error has occurred while saving exception", e);
|
||||
throw;
|
||||
}
|
||||
|
||||
return new EmptyResult();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public JsonResult ReportNew(ExceptionReport exceptionReport)
|
||||
{
|
||||
try
|
||||
{
|
||||
var exceptionId = GetExceptionDetailId(exceptionReport);
|
||||
var exceptionHash = GetExceptionDetailId(exceptionReport);
|
||||
|
||||
var exceptionInstance = new ExceptionInstance
|
||||
{
|
||||
ExceptionDetail = exceptionId,
|
||||
ExceptionHash = exceptionHash,
|
||||
IsProduction = exceptionReport.IsProduction,
|
||||
LogMessage = exceptionReport.LogMessage,
|
||||
Timestamp = DateTime.Now
|
||||
|
@ -38,22 +67,20 @@ public JsonResult ReportNew(ExceptionReport exceptionReport)
|
|||
|
||||
_database.Insert(exceptionInstance);
|
||||
|
||||
return new JsonResult { Data = new ExceptionReportResponse { ExceptionId = exceptionId } };
|
||||
return new JsonResult { Data = new ExceptionReportResponse { ExceptionHash = exceptionHash } };
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.FatalException("Error has occurred while logging exception", e);
|
||||
logger.FatalException("Error has occurred while saving exception", e);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private int GetExceptionDetailId(ExceptionReport exceptionReport)
|
||||
|
||||
private string GetExceptionDetailId(ExceptionReport exceptionReport)
|
||||
{
|
||||
var reportHash = Hash(exceptionReport.Version + exceptionReport.String + exceptionReport.Logger);
|
||||
var id = _database.FirstOrDefault<int>("SELECT Id FROM Exceptions WHERE Hash =@0", reportHash);
|
||||
|
||||
if (id == 0)
|
||||
var reportHash = Hash(String.Concat(exceptionReport.Version, exceptionReport.String, exceptionReport.Logger));
|
||||
|
||||
if (!ExceptionHashExists(reportHash))
|
||||
{
|
||||
var exeptionDetail = new ExceptionDetail();
|
||||
exeptionDetail.Hash = reportHash;
|
||||
|
@ -62,10 +89,15 @@ private int GetExceptionDetailId(ExceptionReport exceptionReport)
|
|||
exeptionDetail.Type = exceptionReport.Type;
|
||||
exeptionDetail.Version = exceptionReport.Version;
|
||||
|
||||
id = Convert.ToInt32(_database.Insert(exeptionDetail));
|
||||
_database.Insert(exeptionDetail);
|
||||
}
|
||||
|
||||
return id;
|
||||
return reportHash;
|
||||
}
|
||||
|
||||
private bool ExceptionHashExists(string reportHash)
|
||||
{
|
||||
return _database.Exists<ExceptionDetail>(reportHash);
|
||||
}
|
||||
|
||||
private static string Hash(string input)
|
||||
|
|
|
@ -10,24 +10,25 @@ public class Migration20120229 : Migration
|
|||
{
|
||||
public override void Up()
|
||||
{
|
||||
|
||||
Database.AddTable("ExceptionInstances", new Column("Id", DbType.Int64, ColumnProperty.PrimaryKeyWithIdentity),
|
||||
new Column("ExceptionDetail", DbType.Int16, ColumnProperty.NotNull),
|
||||
new Column("ExceptionHash", DbType.String, ColumnProperty.NotNull),
|
||||
new Column("LogMessage", DbType.String, 3000, ColumnProperty.NotNull),
|
||||
MigrationsHelper.TimestampColumn,
|
||||
MigrationsHelper.ProductionColumn);
|
||||
|
||||
Database.AddTable("Exceptions", new Column("Id", DbType.Int64, ColumnProperty.PrimaryKeyWithIdentity),
|
||||
Database.AddTable("Exceptions", new Column("Hash", DbType.String, ColumnProperty.Unique),
|
||||
new Column("Logger", DbType.String, ColumnProperty.NotNull),
|
||||
new Column("Type", DbType.String, ColumnProperty.NotNull),
|
||||
new Column("String", DbType.String, ColumnProperty.NotNull),
|
||||
new Column("Hash", DbType.String, ColumnProperty.NotNull),
|
||||
MigrationsHelper.VersionColumn);
|
||||
|
||||
var indexName = MigrationsHelper.GetIndexName("Exceptions", "Hash");
|
||||
Database.AddIndex(indexName, "Exceptions", "Hash");
|
||||
|
||||
//Database.AddForeignKey("FK_Exceptions_ExceptionInstances", "Exceptions", "Hash", "ExceptionInstances", "ExceptionHash", ForeignKeyConstraint.Cascade);
|
||||
|
||||
Database.ExecuteNonQuery("ALTER TABLE ExceptionReports ALTER COLUMN String NTEXT");
|
||||
Database.ExecuteNonQuery("ALTER TABLE Exceptions ALTER COLUMN String NTEXT");
|
||||
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
|
|
|
@ -20,7 +20,7 @@ public static void Run(string connetionString)
|
|||
|
||||
try
|
||||
{
|
||||
var migrator = new Migrator.Migrator("sqlserver", connetionString, Assembly.GetAssembly(typeof(MigrationsHelper)), true, new MigrationLogger());
|
||||
var migrator = new Migrator.Migrator("sqlserver", connetionString, Assembly.GetAssembly(typeof(MigrationsHelper)), true);
|
||||
migrator.MigrateToLastVersion();
|
||||
logger.Info("Database migration completed");
|
||||
}
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Services.PetaPoco;
|
||||
|
||||
namespace NzbDrone.Services.Service.Repository.Reporting
|
||||
{
|
||||
[TableName("Exceptions")]
|
||||
[PrimaryKey("Hash", autoIncrement = false)]
|
||||
public class ExceptionDetail
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Hash { get; set; }
|
||||
public string Logger { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string String { get; set; }
|
||||
public string Hash { get; set; }
|
||||
public string Version { get; set; }
|
||||
}
|
||||
}
|
|
@ -8,7 +8,7 @@ namespace NzbDrone.Services.Service.Repository.Reporting
|
|||
public class ExceptionInstance
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public int ExceptionDetail { get; set; }
|
||||
public string ExceptionHash { get; set; }
|
||||
public string LogMessage { get; set; }
|
||||
public DateTime Timestamp { get; set; }
|
||||
public bool IsProduction { get; set; }
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Contract;
|
||||
using NzbDrone.Services.Service.Repository.Reporting;
|
||||
using NzbDrone.Services.Tests.Framework;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Services.Tests.ExceptionController
|
||||
{
|
||||
[TestFixture]
|
||||
public class ReportExistingFixture : ServicesTestBase
|
||||
{
|
||||
|
||||
Service.Controllers.ExceptionController Controller
|
||||
{
|
||||
get
|
||||
{
|
||||
return Mocker.Resolve<Service.Controllers.ExceptionController>();
|
||||
}
|
||||
}
|
||||
|
||||
private static ExistingExceptionReport CreateExceptionReport()
|
||||
{
|
||||
return new ExistingExceptionReport
|
||||
{
|
||||
IsProduction = true,
|
||||
Version = "1.1.2.323456",
|
||||
UGuid = Guid.NewGuid(),
|
||||
LogMessage = @"Log message",
|
||||
Hash = "ABC123"
|
||||
};
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_log_warn_if_hash_doesnt_exist()
|
||||
{
|
||||
WithRealDb();
|
||||
|
||||
Controller.ReportExisting(CreateExceptionReport());
|
||||
|
||||
Db.Fetch<ExceptionDetail>().Should().BeEmpty();
|
||||
Db.Fetch<ExceptionInstance>().Should().BeEmpty();
|
||||
|
||||
ExceptionVerification.ExpectedWarns(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_save_instance_if_hash_is_valid()
|
||||
{
|
||||
WithRealDb();
|
||||
|
||||
var existing = CreateExceptionReport();
|
||||
|
||||
Db.Insert(Builder<ExceptionDetail>.CreateNew().With(c => c.Hash = existing.Hash).Build());
|
||||
|
||||
Controller.ReportExisting(CreateExceptionReport());
|
||||
|
||||
Db.Fetch<ExceptionDetail>().Should().HaveCount(1);
|
||||
Db.Fetch<ExceptionInstance>().Should().HaveCount(1);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -3,21 +3,20 @@
|
|||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Contract;
|
||||
using NzbDrone.Services.Service.Controllers;
|
||||
using NzbDrone.Services.Service.Repository.Reporting;
|
||||
using NzbDrone.Services.Tests.Framework;
|
||||
|
||||
namespace NzbDrone.Services.Tests
|
||||
namespace NzbDrone.Services.Tests.ExceptionController
|
||||
{
|
||||
[TestFixture]
|
||||
public class ExceptionControllerFixture : ServicesTestBase
|
||||
public class ReportNewFixture : ServicesTestBase
|
||||
{
|
||||
|
||||
ExceptionController Controller
|
||||
Service.Controllers.ExceptionController Controller
|
||||
{
|
||||
get
|
||||
{
|
||||
return Mocker.Resolve<ExceptionController>();
|
||||
return Mocker.Resolve<Service.Controllers.ExceptionController>();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,12 +88,30 @@ public void ReportNew_should_save_instance()
|
|||
var exceptionInstance = Db.Fetch<ExceptionInstance>();
|
||||
exceptionInstance.Should().HaveCount(1);
|
||||
exceptionInstance.Single().Id.Should().BeGreaterThan(0);
|
||||
exceptionInstance.Single().ExceptionDetail.Should().BeGreaterThan(0);
|
||||
exceptionInstance.Single().ExceptionHash.Should().NotBeBlank();
|
||||
exceptionInstance.Single().IsProduction.Should().Be(exceptionReport.IsProduction);
|
||||
exceptionInstance.Single().Timestamp.Should().BeWithin(TimeSpan.FromSeconds(4)).Before(DateTime.Now);
|
||||
exceptionInstance.Single().LogMessage.Should().Be(exceptionReport.LogMessage);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReportNew_should_save_detail()
|
||||
{
|
||||
var exceptionReport = CreateExceptionReport();
|
||||
|
||||
WithRealDb();
|
||||
|
||||
Controller.ReportNew(exceptionReport);
|
||||
|
||||
var exceptionDetails = Db.Fetch<ExceptionDetail>();
|
||||
exceptionDetails.Should().HaveCount(1);
|
||||
exceptionDetails.Single().Hash.Should().NotBeBlank();
|
||||
exceptionDetails.Single().Logger.Should().Be(exceptionReport.Logger);
|
||||
exceptionDetails.Single().Type.Should().Be(exceptionReport.Type);
|
||||
exceptionDetails.Single().String.Should().Be(exceptionReport.String);
|
||||
exceptionDetails.Single().Version.Should().Be(exceptionReport.Version);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReportNew_should_return_exception_id()
|
||||
{
|
||||
|
@ -105,7 +122,7 @@ public void ReportNew_should_return_exception_id()
|
|||
var response = Controller.ReportNew(exceptionReport);
|
||||
|
||||
response.Data.Should().BeOfType<ExceptionReportResponse>();
|
||||
((ExceptionReportResponse)response.Data).ExceptionId.Should().BeGreaterThan(0);
|
||||
((ExceptionReportResponse)response.Data).ExceptionHash.Should().NotBeBlank();
|
||||
}
|
||||
|
||||
|
||||
|
@ -119,15 +136,77 @@ public void Reporting_exception_more_than_once_should_create_single_detail_with_
|
|||
var response1 = Controller.ReportNew(exceptionReport);
|
||||
var response2 = Controller.ReportNew(exceptionReport);
|
||||
var response3 = Controller.ReportNew(exceptionReport);
|
||||
|
||||
|
||||
var detail = Db.Fetch<ExceptionDetail>();
|
||||
var instances = Db.Fetch<ExceptionInstance>();
|
||||
|
||||
detail.Should().HaveCount(1);
|
||||
instances.Should().HaveCount(3);
|
||||
|
||||
instances.Should().OnlyContain(c => c.ExceptionDetail == detail.Single().Id);
|
||||
instances.Should().OnlyContain(c => c.ExceptionHash == detail.Single().Hash);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Reporting_exception_with_diffrent_version_should_create_new_detail()
|
||||
{
|
||||
var exceptionReport1 = CreateExceptionReport();
|
||||
exceptionReport1.Version = "0.1.1";
|
||||
|
||||
var exceptionReport2 = CreateExceptionReport();
|
||||
exceptionReport2.Version = "0.2.1";
|
||||
|
||||
WithRealDb();
|
||||
|
||||
Controller.ReportNew(exceptionReport1);
|
||||
Controller.ReportNew(exceptionReport2);
|
||||
|
||||
var detail = Db.Fetch<ExceptionDetail>();
|
||||
var instances = Db.Fetch<ExceptionInstance>();
|
||||
|
||||
detail.Should().HaveCount(2);
|
||||
instances.Should().HaveCount(2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Reporting_exception_with_diffrent_strting_should_create_new_detail()
|
||||
{
|
||||
var exceptionReport1 = CreateExceptionReport();
|
||||
exceptionReport1.String = "Error1";
|
||||
|
||||
var exceptionReport2 = CreateExceptionReport();
|
||||
exceptionReport2.String = "Error2";
|
||||
|
||||
WithRealDb();
|
||||
|
||||
Controller.ReportNew(exceptionReport1);
|
||||
Controller.ReportNew(exceptionReport2);
|
||||
|
||||
var detail = Db.Fetch<ExceptionDetail>();
|
||||
var instances = Db.Fetch<ExceptionInstance>();
|
||||
|
||||
detail.Should().HaveCount(2);
|
||||
instances.Should().HaveCount(2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Reporting_exception_with_diffrent_logger_should_create_new_detail()
|
||||
{
|
||||
var exceptionReport1 = CreateExceptionReport();
|
||||
exceptionReport1.Logger = "logger1";
|
||||
|
||||
var exceptionReport2 = CreateExceptionReport();
|
||||
exceptionReport2.Logger = "logger2";
|
||||
|
||||
WithRealDb();
|
||||
|
||||
Controller.ReportNew(exceptionReport1);
|
||||
Controller.ReportNew(exceptionReport2);
|
||||
|
||||
var detail = Db.Fetch<ExceptionDetail>();
|
||||
var instances = Db.Fetch<ExceptionInstance>();
|
||||
|
||||
detail.Should().HaveCount(2);
|
||||
instances.Should().HaveCount(2);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@
|
|||
using System.Linq;
|
||||
using System.Text;
|
||||
using Migrator.Providers.SqlServer;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Services.Service.Migrations;
|
||||
using NzbDrone.Test.Common;
|
||||
using NzbDrone.Test.Common.AutoMoq;
|
||||
|
@ -60,5 +61,11 @@ protected void WithRealDb()
|
|||
Mocker.SetConstant(Db);
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void ServiceTearDown()
|
||||
{
|
||||
ExceptionVerification.IgnoreWarns();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,6 +50,9 @@
|
|||
<Reference Include="Moq">
|
||||
<HintPath>..\..\packages\Moq.4.0.10827\lib\NET40\Moq.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NLog, Version=2.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\NLog.2.0.0.2000\lib\net40\NLog.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="nunit.framework, Version=2.6.0.12051, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\packages\NUnit.2.6.0.12054\lib\nunit.framework.dll</HintPath>
|
||||
|
@ -65,7 +68,8 @@
|
|||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="ExceptionControllerFixture.cs" />
|
||||
<Compile Include="ExceptionController\ReportExistingFixture.cs" />
|
||||
<Compile Include="ExceptionController\ReportNewFixture.cs" />
|
||||
<Compile Include="ReportingControllerFixture.cs" />
|
||||
<Compile Include="Framework\ServicesTestBase.cs" />
|
||||
<Compile Include="Framework\TestDbHelper.cs" />
|
||||
|
|
|
@ -3,5 +3,6 @@
|
|||
<package id="FluentAssertions" version="1.7.0" />
|
||||
<package id="Moq" version="4.0.10827" />
|
||||
<package id="NBuilder" version="3.0.1.1" />
|
||||
<package id="NLog" version="2.0.0.2000" />
|
||||
<package id="NUnit" version="2.6.0.12054" />
|
||||
</packages>
|
Loading…
Reference in a new issue