Elmah有一个内置的查看器暴露在~/Elmah.axd
和Elmah MVC有一个内置的查看器暴露在~/Elmah
,两者都有一些预设的配置选项。
正如dove 的回答概述的那样,如果您需要扩展内置配置,您可以将 Elmah 视图包装在您自己的控制器操作方法中,并根据需要授权访问。
一个可能的限制是,从 Elmah 通过 渲染视图Elmah.ErrorLogPageFactory()
对于具有主布局页面的 MVC 应用程序不是超级友好。
*公平地说,这个限制也适用于任何开箱即用的实现。
自己动手
但是,由于您正在编写自己的自定义代码来路由和处理错误日志视图,因此编写视图组件并没有太多额外的工作,而不仅仅是包装提供的视图。到目前为止,这种方法不仅对谁可以查看,而且对他们可以查看的内容提供了最大的控制和粒度。
这是一个自定义Controller、Actions和Views的快速而肮脏的实现,它公开了存储在Elmah.ErrorLog.GetDefault().GetErrors(0, 10, errors)
.
Controllers/ElmahController.cs
:
public class ElmahController : Controller
{
[Authorize(Roles = "Admin")]
public ActionResult Index(int pageNum = 1, int pageSize = 7)
{
var vm = new ElmahViewModel()
{
PageNum = pageNum,
PageSize = pageSize
};
ErrorLog log = Elmah.ErrorLog.GetDefault(System.Web.HttpContext.Current);
vm.TotalSize = log.GetErrors(vm.PageIndex, vm.PageSize, vm.Errors);
return View(vm);
}
[Authorize(Roles = "Admin")]
public ActionResult Details(string id)
{
ErrorLog log = Elmah.ErrorLog.GetDefault(System.Web.HttpContext.Current);
ErrorLogEntry errDetail = log.GetError(id);
return View(errDetail);
}
}
public class ElmahViewModel
{
public List<ErrorLogEntry> Errors { get; set; } = new List<ErrorLogEntry>();
public int TotalSize { get; set; }
public int PageSize { get; set; }
public int PageNum { get; set; }
public int PageIndex => Math.Max(0, PageNum - 1);
public int TotalPages => (int)Math.Ceiling((double)TotalSize / PageSize);
}
这增加了两个动作。一个显示带有一些可选分页输入的错误列表,另一个将接收错误 ID 并仅返回该错误。然后我们也可以添加以下两个视图:
Views/Elmah/List.cshtml
:
@model CSHN.Controllers.ElmahViewModel
@{
ViewBag.Title = "ELMAH (Error Logging Modules and Handlers)";
}
<table class="table table-condensed table-bordered table-striped table-accent">
<thead>
<tr>
<th>
Host Name
</th>
<th class="text-center" style="width: 85px">
Status Code
</th>
<th>
@Html.DisplayNameFor(model => model.Errors.First().Error.Type)
</th>
<th style="min-width: 250px;">
@Html.DisplayNameFor(model => model.Errors.First().Error.Message)
</th>
<th>
@Html.DisplayNameFor(model => model.Errors.First().Error.User)
</th>
<th class="text-center" style="width: 160px">
@Html.DisplayNameFor(model => model.Errors.First().Error.Time)
</th>
<th class="filter-false text-center" data-sorter="false" style="width: 75px">
Actions
</th>
</tr>
</thead>
<tbody>
@if (Model.Errors.Any())
{
foreach (var item in Model.Errors)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Error.HostName)
</td>
<td class="text-center" style="width: 85px">
@Html.DisplayFor(modelItem => item.Error.StatusCode)
</td>
<td>
@Html.DisplayFor(modelItem => item.Error.Type)
</td>
<td>
@Html.DisplayFor(modelItem => item.Error.Message)
</td>
<td>
@Html.DisplayFor(modelItem => item.Error.User)
</td>
<td>
@Html.DisplayFor(modelItem => item.Error.Time)
</td>
<td class="disable-user-select hidden-print text-center" style="width: 75px">
<a href="@Url.Action("Details", "Elmah", new { id = item.Id})"
class="btn btn-xs btn-primary btn-muted">
<i class='fa fa-eye fa-fw'></i> Open
</a>
</td>
</tr>
}
}
else
{
<tr class="warning">
<td colspan="7">There haven't been any errors since the last AppPool Restart.</td>
</tr>
}
</tbody>
@* We need a paginator if we have more records than currently returned *@
@if (Model.TotalSize > Model.PageSize)
{
<tfoot>
<tr>
<th colspan="7" class="form-inline form-inline-xs">
<a href="@Url.Action("Index", new {pageNum = Model.PageNum - 1, pageSize = Model.PageSize})"
class="btn btn-default btn-sm prev @(Model.PageNum == 1?"disabled":"")">
<span class="fa fa-backward fa-fw"></span>
</a>
<span class="pagedisplay">
Page @Model.PageNum of @Model.TotalPages
</span>
<a href="@Url.Action("Index", new {pageNum = Model.PageNum + 1, pageSize = Model.PageSize})"
class="btn btn-default btn-sm next @(Model.PageNum == Model.TotalPages?"disabled":"")">
<span class="fa fa-forward fa-fw"></span>
</a>
</th>
</tr>
</tfoot>
}
</table>
<style>
.table-accent thead tr {
background: #0b6cce;
color: white;
}
.pagedisplay {
margin: 0 10px;
}
</style>
Views/Elmah/Details.cshtml
:
@model Elmah.ErrorLogEntry
@{
ViewBag.Title = $"Error Details on {Model.Error.Time}";
}
<a href="@Url.Action("Index", "Elmah")"
class="btn btn-sm btn-default ">
<i class='fa fa-th-list fa-fw'></i> Back to All Errors
</a>
<div class="form-horizontal">
<h4 class="table-header"> General </h4>
<table class="table table-condensed table-bordered table-striped table-fixed table-accent">
<thead>
<tr>
<th>Name</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td>Message</td>
<td>@Model.Error.Message</td>
</tr>
<tr>
<td>Type</td>
<td>@Model.Error.Type</td>
</tr>
<tr>
<td>Time</td>
<td>@Model.Error.Time</td>
</tr>
<tr>
<td>Detail</td>
<td><pre class="code-block">@Model.Error.Detail</pre></td>
</tr>
</tbody>
</table>
<h4 class="table-header"> Cookies </h4>
<table class="table table-condensed table-bordered table-striped table-fixed table-accent">
<thead>
<tr>
<th >Name</th>
<th>Value</th>
</tr>
</thead>
@foreach (var cookieKey in Model.Error.Cookies.AllKeys)
{
<tr>
<td>@cookieKey</td>
<td>@Model.Error.Cookies[cookieKey]</td>
</tr>
}
</table>
<h4 class="table-header"> Server Variables </h4>
<table class="table table-condensed table-bordered table-striped table-fixed table-accent">
<thead>
<tr>
<th >Name</th>
<th>Value</th>
</tr>
</thead>
@foreach (var servKey in Model.Error.ServerVariables.AllKeys)
{
if (!string.IsNullOrWhiteSpace(Model.Error.ServerVariables[servKey]))
{
<tr>
<td>@servKey</td>
<td>@Html.Raw(Html.Encode(Model.Error.ServerVariables[servKey]).Replace(";", ";<br />"))</td>
</tr>
}
}
</table>
</div>
<style>
.table-header {
background: #16168c;
color: white;
margin-bottom: 0;
padding: 5px;
}
.table-fixed {
table-layout: fixed;
}
.table-fixed td,
.table-fixed th {
word-break: break-all;
}
.table-accent thead tr {
background: #0b6cce;
color: white;
}
.table-accent thead tr th:first-child {
width: 250px;
}
.table-accent td:first-child {
font-weight: bold;
}
.code-block {
overflow-x: scroll;
white-space: pre;
background: #ffffcc;
}
</style>
这种方法的另一个好处是我们不需要set allowRemoteAccess="yes"
,它可以通过暴露 SessionId 暴露一些安全问题,比如会话劫持。
如果这个实现不够健壮,你可以随时扩展和定制它,直到你满意为止。如果您想保留本地选项可用,您仍然可以公开它并通过将其隐藏在机器上为管理员提供友好链接HttpRequest.IsLocal