网站正常运行中有时出现异常在所难免,查看系统运行日志分析问题并能够根据错误信息快速解决问题尤为重要,ABP对于系统运行日志这块已经做了很好的处理,默认采用的Log4Net已经足够满足开发过程中的需要了(当然有需要的话也可以更换为其它日志组件)。

  ABP官网地址:https://aspnetboilerplate.com/

一、日志文件

  ABP框架默认使用了Log4Net日志组件,日志记录在txt文件中,也可以替换成其它日志组件诸如Nlog,方便将日志文件信息直接记录到数据库中,具体情形使用具体组件。

  X-Admin&ABP框架开发-系统日志-LMLPHP

  当一个文件达到了在Log4Net配置中设置好的文件大小上限时,在文件名后按照数字倒排后开始继续增加文件。

  X-Admin&ABP框架开发-系统日志-LMLPHP

  当需要查看错误信息时,直接在日期最近的文件中找出错误信息即可,但是这个过程比较繁琐,还需要从日志文件中去查看,并且日志文件中虽然做了分类,哪些是正常信息,哪些是错误信息,但是不太直观,因此,可以考虑直接将日志文件在页面中呈现,对信息进一步加工,方便直接查看。

   X-Admin&ABP框架开发-系统日志-LMLPHP

  参考了AbpZero中的部分代码并根据实际需要进行整合,开始在页面中设计日志展示层。 

二、页面展示日志信息

1、系统日志服务应属于整个系统中相对其他业务模块独立的一部分,因此,首先在应用层中新建一个Logging文件夹并创建一个日志应用层服务接口与其实现。在接口中声明两个方法,直接查看当前最近的日志文件中的日志信息以及从服务器下载所有的日志文件。

/// <summary>
/// 网站运行日志应用层服务
/// </summary>
public interface IWebSiteLogAppService : IApplicationService
{
    /// <summary>
    /// 获取最近的一个日志文件
    /// </summary>
    /// <returns></returns>
    GetLatestWebLogsOutput GetLatestWebLogs();

    /// <summary>
    /// 下载所有的日志文件
    /// </summary>
    /// <returns></returns>
    FileDto DownloadWebLogs();
}

   首先考虑直接获取最近的日志文件信息,直接读取即可,遵循的规则是读取指定文件夹下指定文件后缀名更改日期为最大的文件然后从中读取日志信息,并返回到前端。

public GetLatestWebLogsOutput GetLatestWebLogs()
{
    var directory = new DirectoryInfo(AppConsts.LogFilePath);

    if (!directory.Exists)
    {
        return new GetLatestWebLogsOutput
        {
            LatestWebLogLines = new List<string>()
        };
    }

    var lastLogFile = directory.GetFiles("*.txt", SearchOption.AllDirectories)
        .OrderByDescending(f => f.LastWriteTime)
        .FirstOrDefault();

    if (lastLogFile == null)
    {
        return new GetLatestWebLogsOutput();
    }

    var lines = AppFileHelper.ReadLines(lastLogFile.FullName).Reverse().Take(1000).ToList();
    var logLineCount = 0;
    var lineCount = 0;

    foreach (var line in lines)
    {
        if (line.StartsWith("DEBUG") ||
            line.StartsWith("INFO") ||
            line.StartsWith("WARN") ||
            line.StartsWith("ERROR") ||
            line.StartsWith("FATAL"))
            logLineCount++;

        lineCount++;

        if (logLineCount == 100) break;
    }

    return new GetLatestWebLogsOutput
    {
        LatestWebLogLines = lines.Take(lineCount).Reverse().ToList()
    };
}

  

2、在前端处理日志信息,Mvc层中新增一个控制器,并写一个方法调用日志服务获取最近的日志文件信息,并处理好权限问题及页面左侧菜单的展示。

/// <summary>
/// 系统维护控制器
/// </summary>
[AbpMvcAuthorize]
public class MaintenanceController : SurroundControllerBase
{
    private readonly IWebSiteLogAppService _webSiteLogAppService;

    public MaintenanceController(IWebSiteLogAppService webSiteLogAppService)
    {
        _webSiteLogAppService = webSiteLogAppService;
    }

    /// <summary>
    /// 首页
    /// </summary>
    /// <returns></returns>
    public IActionResult Index()
    {
        return View();
    }

    /// <summary>
    /// 获取最近日志信息
    /// </summary>
    /// <returns></returns>
    public JsonResult GetLatestWebLogs()
    {
        var getLatestWebLogsOutput = _webSiteLogAppService.GetLatestWebLogs();
        return Json(getLatestWebLogsOutput);
    }
}

   增加一个视图文件并开始编写前端代码获取日志文件,利用abp前端封装好的ajax请求快速的获取日志文件,然后通过layui中提供的徽章进行加工处理,如此一来,通过颜色快速区分哪些是错误信息,哪些信息权重更大,更值得关注,此处引用了一个lodash.js,该js中提供了许多的辅助方法。

function getFormattedLogs(logLines) {
    var resultHtml = '';
    $.each(logLines, function (index, logLine) {
        resultHtml += '<span>' + _.escape(logLine)
            .replace('DEBUG', '<span class="layui-badge layui-bg-gray">DEBUG</span>')
            .replace('INFO', '<span class="layui-badge layui-bg-green">INFO</span>')
            .replace('WARN', '<span class="layui-badge layui-bg-orange">WARN</span>')
            .replace('ERROR', '<span class="layui-badge">ERROR</span>')
            .replace('FATAL', '<span class="layui-badge">FATAL</span>') + '</span><br/>';
    });
    return resultHtml;
}

   通过刷新按钮获取最近的日志信息。

  

三、下载日志文件

   也可以直接下载日志文件去分析,当然,从使用频率讲,这个功能的权重远低于直接页面查看,但是细想一下,如果说一个异常发生,没有及时去页面中查看,那么就得去成堆的日志中翻找,反而凸显其作用了。

public FileDto DownloadWebLogs()
{
    var logFiles = GetAllLogFiles();

    var zipFileDto = new FileDto("WebSiteLogs.zip", MimeTypeNames.ApplicationZip);

    using (var outputZipFileStream = new MemoryStream())
    {
        using (var zipStream = new ZipArchive(outputZipFileStream, ZipArchiveMode.Create))
        {
            foreach (var logFile in logFiles)
            {
                var entry = zipStream.CreateEntry(logFile.Name);
                using (var entryStream = entry.Open())
                {
                    using (var fs = new FileStream(logFile.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 0x1000, FileOptions.SequentialScan))
                    {
                        fs.CopyTo(entryStream);
                        entryStream.Flush();
                    }
                }
            }
        }

        _tempFileCacheManager.SetFile(zipFileDto.FileToken, outputZipFileStream.ToArray());
    }

    return zipFileDto;
}

private List<FileInfo> GetAllLogFiles()
{
    var directory = new DirectoryInfo(AppConsts.LogFilePath);
    return directory.GetFiles("*.*", SearchOption.TopDirectoryOnly).ToList();
}

   将日志文件全部读取出来,然后打包存储在缓存中,前端点击下载按钮时后台返回压缩包的标识信息供前端直接下载,此处在控制器中加入一个文件管理的控制器,来作为系统中大部分文件下载的渠道。

var waitIndex = parent.layer.load(2);
abp.ajax({
    type:"Get",
    url: "@Url.Action("DownloadWebLogs", "Maintenance")",
    abpHandleError: false
}).done(function (file) {
    location.href = '@Url.Action("DownloadTempFile", "File")' + abp.utils.formatString("?fileToken={0}&fileType={1}&fileName={2}", file.fileToken, file.fileType, file.fileName);
}).fail(function (jqXHR) {
    parent.layer.msg(jqXHR.message, { icon: 5 });
}).always(function () {
    parent.layer.close(waitIndex);
});

  点击日志下载,浏览器开始执行下载任务。   

  

  至此,系统日志的页面查看就完成了,对于加入诸如查询等更加丰富的功能,可以再进行扩展,也可以考虑直接使用已有的组件更方便的呈现的日志信息而无需手动实现,诸如LogDashBoard等,可以很快速的接入到系统中。 

  代码地址:https://gitee.com/530521314/Partner.Surround.git

2019-08-03,望技术有成后能回来看见自己的脚步
08-04 04:23