基于DDD的.NET开发框架 - ABP日志Logger集成

返回ABP系列

ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称。

ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应用程序的新起点,它旨在成为一个通用的WEB应用程序框架和项目模板。

ABP的官方网站:http://www.aspnetboilerplate.com

ABP官方文档:http://www.aspnetboilerplate.com/Pages/Documents

Github上的开源项目:https://github.com/aspnetboilerplate

一、基本概念

ABP使用Castle日志记录工具,并且可以使用不同的日志类库,比如:Log4Net, NLog, Serilog... 等。对于所有的日志类库,Castle提供了一个通用的接口来实现,我们可以很方便的处理各种特殊的日志库,而且当业务需要的时候,很容易替换日志组件。

Castle是什么:Castle是针对.NET平台的一个开源项目,从数据访问框架ORM到IOC容器,再到WEB层的MVC框架、AOP,基本包括了整个开发过程中的所有东西。ASP.NET Boilerplate的ioc容器就是通过Castle实现的。

Log4Net是.Net中最流行的日志类库之一。ABP模板中自带了经过合适配置的Log4Net。但是,只存在一行log4net的依赖(看下面),因此,你可以将它改为你最喜欢的类库。

获取Logger:

无论你选择了什么日志类库,最终要记录的日志代码都是相同的

一开始,我们要处理一下记录日志的Logger对象。因为ABP强烈推荐使用依赖注入,所以我们可以使用属性注入模式轻松地注入一个Logger对象。如下所示:

using Castle.Core.Logging; //1、导入 Logging 命名空间
public class TaskAppService : ITaskAppService
    {
        //2、使用属性注入获得 logger
        public ILogger Logger { get; set; }
        public TaskAppService()
        {
            //3、如果没有提供Logger,就不能记录日志
            Logger = NullLogger.Instance;
        }

        public void CreateTask(CreateTaskInput input)
        {
            //4、记录日志
            Logger.Info("Creating a new task with description: " + input.Description);

            //TODO: 保存到数据库...
        }
    }

1、导入Castle的ILogger接口的命名空间。

2、定义一个公有的叫做Logger的ILogger对象。这是记录日志的对象。创建TaskAppService对象之后,依赖注入系统会自动注入这个属性。这就是所谓的属性注入模式。

3、将Logger设置为NullLogger.Instance。即使没有这行代码,系统也会工作地很好。但是这是属性注入模式的最佳实践。如果没给Logger设置任何值,那么当我们使用它的时候会因为它是null而抛出“空指针”异常。这个保证了它不为null。因此,如果没有给Logger设置值,那么它是NullLogger。这就是所谓的null对象模式。NullLogger实际上什么都没做,也没有记录任何日志。因此,我们的类要不要一个实际的logger都能工作。

4、最后,我们记录了一个info等级的日志文本。存在多种不同的等级。

通过积累使用Logger:

ASP.NET Boilerplate框架提供了MVC Controllers、Web API Controllers和Application service classes的基类。例如,Web层对应的基类是XXXControllerBase,这些基类中都声明了Logger属性。可以直接使用Logger来记录日志,无需注入。如下:

        public class HomeController : SimpleTaskSystemControllerBase
        {
            public ActionResult Index()
            {
                Logger.Debug("A sample log message...");
                return View();
            }
        }

MVC Controllers:继承XXAbpController基类

Web API Controllers:继承XXAbpApiController基类

Application service classes:继承XXAppServiceBase基类

配置:

如果你在官网上通过ASP.NET Boilerplate templates 来生成了你的工程,Log4Net的所有配置都自动生成了。

默认的配置格式如下:

  • Log level: 日志记录等级,有DEBUG, INFO, WARN, ERROR or FATAL5个。
  • Date and time: 日志记录时间。
  • Thread number: 每行日志写时候的线程号。
  • Logger name: 日志记录器的名字,通常情况就是类名称。
  • Log text: 你写入的日志内容。

配置文件:log4net.config 一般都在项目的web目录下面。

<?xml version="1.0" encoding="utf-8" ?>
<log4net>
  <appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender" >
    <file value="Logs/Logs.txt" />
    <appendToFile value="true" />
    <rollingStyle value="Size" />
    <maxSizeRollBackups value="10" />
    <maximumFileSize value="10000KB" />
    <staticLogFileName value="true" />
    <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%-5level %date [%-5.5thread] %-40.40logger - %message%newline" />
    </layout>
  </appender>
  <root>
    <appender-ref ref="RollingFileAppender" />
    <level value="DEBUG" />
  </root>
  <logger name="NHibernate">
    <level value="WARN" />
  </logger>
</log4net>

Log4Net是一个非常强大和易用的日志库组件,你可以写各种日志,比如写到txt文件,写入到数据库等等。你能设置最小的日志等级,就像上面这个针对NHibernate的配置。不同的记录器写不同的日志。

最后,在工程的Global.asax 文件中,来定义Log4Net的配置文件:

public class MvcApplication : AbpWebApplication
{
    protected override void Application_Start(object sender, EventArgs e)
    {
        IocManager.Instance.IocContainer.AddFacility<LoggingFacility>(f => f.UseLog4Net().WithConfig("log4net.config"));
        base.Application_Start(sender, e);
    }
}

客户端:

ABP为客户端定义了一个javascript日志记录API。默认会将日志记录到浏览器的控制台。记录日志的javascript代码样例如下:

abp.log.warn(‘a sample log message...‘);

二、ABP底层代码分析

LogSeverity:枚举类型,定义了5个日志级别:Info,Debug,Warn,Error, Fatal.

namespace Abp.Logging
{
    /// <summary>
    /// Indicates severity for log.
    /// </summary>
    public enum LogSeverity
    {
        /// <summary>
        /// Debug.
        /// </summary>
        Debug,

        /// <summary>
        /// Info.
        /// </summary>
        Info,

        /// <summary>
        /// Warn.
        /// </summary>
        Warn,

        /// <summary>
        /// Error.
        /// </summary>
        Error,

        /// <summary>
        /// Fatal.
        /// </summary>
        Fatal
    }
}

IHasLogSeverity:封装了LogSeverity。UserFriendlyException,AbpValidationException实现了这个接口。Loghelper在对exeption做log的时候可以方便的通过实现了IHasLogSeverity的exeption的实例获取到logSeverity。然后根据logSeverity的级别log.

namespace Abp.Logging
{
    /// <summary>
    /// Interface to define a <see cref="Severity"/> property (see <see cref="LogSeverity"/>).
    /// </summary>
    public interface IHasLogSeverity
    {
        /// <summary>
        /// Log severity.
        /// </summary>
        LogSeverity Severity { get; set; }
    }
}

Loghelper: 静态类。调用logger实例(实现Castle的Ilogger接口)完成log操作。

using System;
using System.Linq;
using Abp.Collections.Extensions;
using Abp.Dependency;
using Abp.Runtime.Validation;
using Castle.Core.Logging;

namespace Abp.Logging
{
    /// <summary>
    /// This class can be used to write logs from somewhere where it‘s a hard to get a reference to the <see cref="ILogger"/>.
    /// Normally, use <see cref="ILogger"/> with property injection wherever it‘s possible.
    /// </summary>
    public static class LogHelper
    {
        /// <summary>
        /// A reference to the logger.
        /// </summary>
        public static ILogger Logger { get; private set; }

        static LogHelper()
        {
            Logger = IocManager.Instance.IsRegistered(typeof(ILoggerFactory))
                ? IocManager.Instance.Resolve<ILoggerFactory>().Create(typeof(LogHelper))
                : NullLogger.Instance;
        }

        public static void LogException(Exception ex)
        {
            LogException(Logger, ex);
        }

        public static void LogException(ILogger logger, Exception ex)
        {
            var severity = (ex is IHasLogSeverity)
                    ? (ex as IHasLogSeverity).Severity
                    : LogSeverity.Error;

            logger.Log(severity, ex.Message, ex);

            LogValidationErrors(logger, ex);
        }

        private static void LogValidationErrors(ILogger logger, Exception exception)
        {
            //Try to find inner validation exception
            if (exception is AggregateException && exception.InnerException != null)
            {
                var aggException = exception as AggregateException;
                if (aggException.InnerException is AbpValidationException)
                {
                    exception = aggException.InnerException;
                }
            }

            if (!(exception is AbpValidationException))
            {
                return;
            }

            var validationException = exception as AbpValidationException;
            if (validationException.ValidationErrors.IsNullOrEmpty())
            {
                return;
            }

            logger.Log(validationException.Severity, "There are " + validationException.ValidationErrors.Count + " validation errors:");
            foreach (var validationResult in validationException.ValidationErrors)
            {
                var memberNames = "";
                if (validationResult.MemberNames != null && validationResult.MemberNames.Any())
                {
                    memberNames = " (" + string.Join(", ", validationResult.MemberNames) + ")";
                }

                logger.Log(validationException.Severity, validationResult.ErrorMessage + memberNames);
            }
        }
    }
}

LoggerExtensions: 扩展了Castle的Ilogger接口的方法,封装更便捷的方法供Loghelper调用。

using System;
using Castle.Core.Logging;

namespace Abp.Logging
{
    /// <summary>
    /// Extensions for <see cref="ILogger"/>.
    /// </summary>
    public static class LoggerExtensions
    {
        public static void Log(this ILogger logger, LogSeverity severity, string message)
        {
            switch (severity)
            {
                case LogSeverity.Fatal:
                    logger.Fatal(message);
                    break;
                case LogSeverity.Error:
                    logger.Error(message);
                    break;
                case LogSeverity.Warn:
                    logger.Warn(message);
                    break;
                case LogSeverity.Info:
                    logger.Info(message);
                    break;
                case LogSeverity.Debug:
                    logger.Debug(message);
                    break;
                default:
                    throw new AbpException("Unknown LogSeverity value: " + severity);
            }
        }

        public static void Log(this ILogger logger, LogSeverity severity, string message, Exception exception)
        {
            switch (severity)
            {
                case LogSeverity.Fatal:
                    logger.Fatal(message, exception);
                    break;
                case LogSeverity.Error:
                    logger.Error(message, exception);
                    break;
                case LogSeverity.Warn:
                    logger.Warn(message, exception);
                    break;
                case LogSeverity.Info:
                    logger.Info(message, exception);
                    break;
                case LogSeverity.Debug:
                    logger.Debug(message, exception);
                    break;
                default:
                    throw new AbpException("Unknown LogSeverity value: " + severity);
            }
        }

        public static void Log(this ILogger logger, LogSeverity severity, Func<string> messageFactory)
        {
            switch (severity)
            {
                case LogSeverity.Fatal:
                    logger.Fatal(messageFactory);
                    break;
                case LogSeverity.Error:
                    logger.Error(messageFactory);
                    break;
                case LogSeverity.Warn:
                    logger.Warn(messageFactory);
                    break;
                case LogSeverity.Info:
                    logger.Info(messageFactory);
                    break;
                case LogSeverity.Debug:
                    logger.Debug(messageFactory);
                    break;
                default:
                    throw new AbpException("Unknown LogSeverity value: " + severity);
            }
        }
    }
}

在具体的web项目的application_start方法中注入logger实例。以下是注入log4net代码

public class MvcApplication : AbpWebApplication
{
    protected override void Application_Start(object sender, EventArgs e)
    {
        IocManager.Instance.IocContainer.AddFacility<LoggingFacility>(f => f.UseLog4Net().WithConfig("log4net.config"));
        base.Application_Start(sender, e);
    }
}
时间: 2024-10-14 12:08:30

基于DDD的.NET开发框架 - ABP日志Logger集成的相关文章

基于DDD的.NET开发框架 - ABP模块设计

一.摘要 研究过orchard和nopcommerce的都应该知道模块概念,ABP的模块也和他们是一回事.实现原理也都一样:应用程序一般都是先定义模块接口,然后把模块编译的dll放到固定的目录中(ABP只能放到bin下),应用程序主程序通过加载那些实现了插件接口的dll来实现插件的使用. ABP 框架提供了创建和组装模块的基础,一个模块能够依赖于另一个模块.在通常情况 下,一个程序集就可以看成是一个模块.在 ABP 框架中,一个模块通过一个类来定义,而这 个类要继承自 AbpModule. no

基于DDD的.NET开发框架 - ABP的Entity设计思想

返回ABP系列 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应用程序的新起点,它旨在成为一个通用的WEB应用程序框架和项目模板. ABP的官方网站:http://www.aspnetboilerplate.com ABP官方文档:http://www.aspnetboilerplate.com/Pages/Documents Github上的开源项目:http

基于DDD的.NET开发框架 - ABP Session实现

返回ABP系列 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应用程序的新起点,它旨在成为一个通用的WEB应用程序框架和项目模板. ABP的官方网站:http://www.aspnetboilerplate.com ABP官方文档:http://www.aspnetboilerplate.com/Pages/Documents Github上的开源项目:http

基于DDD的.NET开发框架 - ABP依赖注入

返回ABP系列 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应用程序的新起点,它旨在成为一个通用的WEB应用程序框架和项目模板. ABP的官方网站:http://www.aspnetboilerplate.com ABP官方文档:http://www.aspnetboilerplate.com/Pages/Documents Github上的开源项目:http

基于DDD的.NET开发框架 - ABP工作单元(Unit of Work)

返回ABP系列 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应用程序的新起点,它旨在成为一个通用的WEB应用程序框架和项目模板. ABP的官方网站:http://www.aspnetboilerplate.com ABP官方文档:http://www.aspnetboilerplate.com/Pages/Documents Github上的开源项目:http

基于DDD的.NET开发框架 - ABP缓存Caching实现

返回ABP系列 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应用程序的新起点,它旨在成为一个通用的WEB应用程序框架和项目模板. ABP的官方网站:http://www.aspnetboilerplate.com ABP官方文档:http://www.aspnetboilerplate.com/Pages/Documents Github上的开源项目:http

基于DDD的.NET开发框架 - ABP启动配置

返回ABP系列 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应用程序的新起点,它旨在成为一个通用的WEB应用程序框架和项目模板. ABP的官方网站:http://www.aspnetboilerplate.com ABP官方文档:http://www.aspnetboilerplate.com/Pages/Documents Github上的开源项目:http

ABP 基于DDD的.NET开发框架 学习(三)创建模块:任务管理

1.图标i获取:https://material.io/icons/查看2.js中创建如var _tenantService = abp.services.app.tenant;需清除页面的缓存,按ctrl+f5刷新页面------创建一个模块步骤:3.创建导航菜单节点在MPLH.WorkProject.Web下的WorkProjectNavigationProvider中配置4.xxx.WorkProject.Web中的PermissionNames中配置权限名5.控制器继承WorkProje

基于DDD的.NET开发框架-DDD经典分层

DDD核心思想是由业务问题来控制解决方案的形式从以数据库为中心过渡到领域模型为中心 下面这个图是我在<领域驱动设计与模式实战>书中拍下来的,他完全诠释DDD的经典分层. 程序代码中也是响应的引用关系 各层概念: 表现层(Presentation Layer):图中的用户界面层包括用户接口层,用户输入和数据展示. 应用层(Application Layer):应用层定义系统的业务功能,并指挥领域层中的领域对象实现这些功能. 领域层(Domain Layer):核心层,实现所有业务逻辑. 基础设施