Adding support for exception instances in Services.NzbDrone

This commit is contained in:
kay.one 2012-02-29 18:36:39 -08:00
parent 917ae0f999
commit 98faca17ee
14 changed files with 342 additions and 11 deletions

View File

@ -20,14 +20,13 @@ namespace NzbDrone.Common.Contract
{
var dic = new Dictionary<string, string>
{
{"ExType", Type.NullCheck()},
{"Logger", Logger.NullCheck()},
{"Message", LogMessage.NullCheck()},
{"Str", String.NullCheck()}
{"ExType", Type.NullSafe()},
{"Logger", Logger.NullSafe()},
{"Message", LogMessage.NullSafe()},
{"Str", String.NullSafe()}
};
return dic;
}
}
}

View File

@ -0,0 +1,11 @@
using System.Linq;
using Newtonsoft.Json;
namespace NzbDrone.Common.Contract
{
public class ExceptionReportResponse
{
[JsonProperty("id")]
public int ExceptionId { get; set; }
}
}

View File

@ -13,7 +13,7 @@ namespace NzbDrone.Common.Contract
{
var dic = new Dictionary<string, string>
{
{"Title", Title.NullCheck()},
{"Title", Title.NullSafe()},
};
return dic;

View File

@ -54,6 +54,7 @@
<Reference Include="System.Xml.Linq" />
</ItemGroup>
<ItemGroup>
<Compile Include="Contract\ExceptionReportResponse.cs" />
<Compile Include="StringExtention.cs" />
<Compile Include="HttpProvider.cs" />
<Compile Include="ConfigFileProvider.cs" />

View File

@ -6,15 +6,15 @@ namespace NzbDrone.Common
public static class StringExtention
{
public static object NullCheck(this object target)
public static object NullSafe(this object target)
{
if (target != null) return target;
return "[NULL]";
}
public static string NullCheck(this string target)
public static string NullSafe(this string target)
{
return ((object)target).NullCheck().ToString();
return ((object)target).NullSafe().ToString();
}
}
}

View File

@ -0,0 +1,93 @@
using System;
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;
namespace NzbDrone.Services.Service.Controllers
{
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 JsonResult ReportNew(ExceptionReport exceptionReport)
{
try
{
var exceptionId = GetExceptionDetailId(exceptionReport);
var exceptionInstance = new ExceptionInstance
{
ExceptionDetail = exceptionId,
IsProduction = exceptionReport.IsProduction,
LogMessage = exceptionReport.LogMessage,
Timestamp = DateTime.Now
};
_database.Insert(exceptionInstance);
return new JsonResult { Data = new ExceptionReportResponse { ExceptionId = exceptionId } };
}
catch (Exception e)
{
logger.FatalException("Error has occurred while logging exception", e);
throw;
}
}
private int 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 exeptionDetail = new ExceptionDetail();
exeptionDetail.Hash = reportHash;
exeptionDetail.Logger = exceptionReport.Logger;
exeptionDetail.String = exceptionReport.String;
exeptionDetail.Type = exceptionReport.Type;
exeptionDetail.Version = exceptionReport.Version;
id = Convert.ToInt32(_database.Insert(exeptionDetail));
}
return id;
}
private static string Hash(string input)
{
uint mCrc = 0xffffffff;
byte[] bytes = System.Text.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);
}
}
}

View File

@ -25,7 +25,7 @@ namespace NzbDrone.Services.Service.Controllers
[HttpPost]
public JsonResult ParseError(ParseErrorReport parseErrorReport)
{
logger.Trace(parseErrorReport.NullCheck());
logger.Trace(parseErrorReport.NullSafe());
if (ParseErrorExists(parseErrorReport.Title))
return Json(OK);
@ -63,7 +63,7 @@ namespace NzbDrone.Services.Service.Controllers
}
catch (Exception)
{
logger.Trace(exceptionReport.NullCheck());
logger.Trace(exceptionReport.NullSafe());
throw;
}

View File

@ -0,0 +1,38 @@
using System;
using System.Data;
using System.Linq;
using Migrator.Framework;
namespace NzbDrone.Services.Service.Migrations
{
[Migration(20120229)]
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("LogMessage", DbType.String, 3000, ColumnProperty.NotNull),
MigrationsHelper.TimestampColumn,
MigrationsHelper.ProductionColumn);
Database.AddTable("Exceptions", new Column("Id", DbType.Int64, ColumnProperty.PrimaryKeyWithIdentity),
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);
Database.ExecuteNonQuery("ALTER TABLE ExceptionReports ALTER COLUMN String NTEXT");
Database.ExecuteNonQuery("ALTER TABLE Exceptions ALTER COLUMN String NTEXT");
}
public override void Down()
{
throw new NotImplementedException();
}
}
}

View File

@ -209,8 +209,12 @@
<ItemGroup>
<Compile Include="App_Start\Logging.cs" />
<Compile Include="App_Start\NinjectMVC3.cs" />
<Compile Include="Controllers\ExceptionController.cs" />
<Compile Include="Helpers\HtmlIncludeExtentions.cs" />
<Compile Include="Migrations\Migration20120226.cs" />
<Compile Include="Migrations\Migration20120229.cs" />
<Compile Include="Repository\Reporting\ExceptionDetail.cs" />
<Compile Include="Repository\Reporting\ExceptionInstance.cs" />
<Compile Include="Services.PetaPoco.cs" />
<Compile Include="Datastore\Connection.cs" />
<Compile Include="Controllers\DailySeriesController.cs" />

View File

@ -0,0 +1,17 @@
using System;
using System.Linq;
using Services.PetaPoco;
namespace NzbDrone.Services.Service.Repository.Reporting
{
[TableName("Exceptions")]
public class ExceptionDetail
{
public int Id { 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; }
}
}

View File

@ -0,0 +1,16 @@
using System;
using System.Linq;
using Services.PetaPoco;
namespace NzbDrone.Services.Service.Repository.Reporting
{
[TableName("ExceptionInstances")]
public class ExceptionInstance
{
public long Id { get; set; }
public int ExceptionDetail { get; set; }
public string LogMessage { get; set; }
public DateTime Timestamp { get; set; }
public bool IsProduction { get; set; }
}
}

View File

@ -0,0 +1,133 @@
using System;
using System.Linq;
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
{
[TestFixture]
public class ExceptionControllerFixture : ServicesTestBase
{
ExceptionController Controller
{
get
{
return Mocker.Resolve<ExceptionController>();
}
}
private static ExceptionReport CreateExceptionReport()
{
return new ExceptionReport
{
IsProduction = true,
Version = "1.1.2.323456",
UGuid = Guid.NewGuid(),
Logger = "NzbDrone.Logger.Name",
LogMessage = @"Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message",
String = @"Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message",
Type = typeof(InvalidOperationException).Name
};
}
[Test]
public void ReportNew_should_save_instance()
{
var exceptionReport = CreateExceptionReport();
WithRealDb();
Controller.ReportNew(exceptionReport);
var exceptionInstance = Db.Fetch<ExceptionInstance>();
exceptionInstance.Should().HaveCount(1);
exceptionInstance.Single().Id.Should().BeGreaterThan(0);
exceptionInstance.Single().ExceptionDetail.Should().BeGreaterThan(0);
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_return_exception_id()
{
var exceptionReport = CreateExceptionReport();
WithRealDb();
var response = Controller.ReportNew(exceptionReport);
response.Data.Should().BeOfType<ExceptionReportResponse>();
((ExceptionReportResponse)response.Data).ExceptionId.Should().BeGreaterThan(0);
}
[Test]
public void Reporting_exception_more_than_once_should_create_single_detail_with_multiple_instances()
{
var exceptionReport = CreateExceptionReport();
WithRealDb();
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);
}
}
}

View File

@ -65,6 +65,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="ExceptionControllerFixture.cs" />
<Compile Include="ReportingControllerFixture.cs" />
<Compile Include="Framework\ServicesTestBase.cs" />
<Compile Include="Framework\TestDbHelper.cs" />

View File

@ -48,6 +48,24 @@ namespace NzbDrone.Services.Tests
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message",
String = @"Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message
Long message Long message Long messageLong messageLong messageLong messageLong messageLong messageLong messageLong messageLong message