X-Admin&ABP框架開發-系統日誌

微笑刺客D發表於2019-08-03

  網站正常執行中有時出現異常在所難免,檢視系統執行日誌分析問題並能夠根據錯誤資訊快速解決問題尤為重要,ABP對於系統執行日誌這塊已經做了很好的處理,預設採用的Log4Net已經足夠滿足開發過程中的需要了(當然有需要的話也可以更換為其它日誌元件)。

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

 

一、日誌檔案

  ABP框架預設使用了Log4Net日誌元件,日誌記錄在txt檔案中,也可以替換成其它日誌元件諸如Nlog,方便將日誌檔案資訊直接記錄到資料庫中,具體情形使用具體元件。

  

  當一個檔案達到了在Log4Net配置中設定好的檔案大小上限時,在檔名後按照數字倒排後開始繼續增加檔案。

  

  當需要檢視錯誤資訊時,直接在日期最近的檔案中找出錯誤資訊即可,但是這個過程比較繁瑣,還需要從日誌檔案中去檢視,並且日誌檔案中雖然做了分類,哪些是正常資訊,哪些是錯誤資訊,但是不太直觀,因此,可以考慮直接將日誌檔案在頁面中呈現,對資訊進一步加工,方便直接檢視。

   

  參考了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,望技術有成後能回來看見自己的腳步

相關文章