当前位置: 代码网 > it编程>编程语言>Asp.net > .NET Core 实现一个自定义日志记录器

.NET Core 实现一个自定义日志记录器

2025年02月13日 Asp.net 我要评论
引言在应用程序中,日志记录是一个至关重要的功能。不仅有助于调试和监控应用程序,还能帮助我们了解应用程序的运行状态。在这个示例中将展示如何实现一个自定义的日志记录器,先说明一下,这个实现和microso

引言

在应用程序中,日志记录是一个至关重要的功能。不仅有助于调试和监控应用程序,还能帮助我们了解应用程序的运行状态。

在这个示例中将展示如何实现一个自定义的日志记录器,先说明一下,这个实现和microsoft.extensions.loggingserilognlog什么的无关,这里只是将自定义的日志数据存入数据库中,或许你也可以理解为实现的是一个存数据的“repository”,只不过用这个repository来存的是日志。这个实现包含一个抽象包和两个实现包,两个实现分别是用 entityframework core 和 mysqlconnector 。日志记录操作将放在本地队列中异步处理,以确保不影响业务处理。

1. 抽象包

1.1 定义日志记录接口

首先,我们需要定义一个日志记录接口 icustomlogger,它包含两个方法:logreceived 和 logprocessed。logreceived 用于记录接收到的日志,logprocessed 用于更新日志的处理状态。

namespace logging.abstractions;

public interface icustomlogger
{
    /// <summary>
    /// 记录一条日志
    /// </summary>
    void logreceived(customlogentry logentry);

    /// <summary>
    /// 根据id更新这条日志
    /// </summary>
    void logprocessed(string logid, bool issuccess);
} 

定义一个日志结构实体customlogentry,用于存储日志的详细信息:

namespace logging.abstractions;

public class customlogentry
{
    /// <summary>
    /// 日志唯一id,数据库主键
    /// </summary>
    public string id { get; set; } = guid.newguid().tostring();
    public string message { get; set; } = default!;
    public bool issuccess { get; set; }
    public datetime createtime { get; set; } = datetime.utcnow;
    public datetime? updatetime { get; set; } = datetime.utcnow;
}

1.2 定义日志记录抽象类

接下来,定义一个抽象类customlogger,它实现了icustomlogger接口,并提供了日志记录的基本功能,将日志写入操作(插入or更新)放在本地队列中异步处理。使用concurrentqueue来确保线程安全,并开启一个后台任务异步处理这些日志。这个抽象类只负责将日志写入命令放到队列中,实现类负责消费队列中的消息,确定日志应该怎么写?往哪里写?这个示例中后边会有两个实现,一个是基于entityframework core的实现,另一个是mysqlconnector的实现。

封装一下日志写入命令

namespace logging.abstractions;

public class writecommand(writecommandtype commandtype, customlogentry logentry)
{
    public writecommandtype commandtype { get; } = commandtype;
    public customlogentry logentry { get; } = logentry;
}

public enum writecommandtype
{
    /// <summary>
    /// 插入
    /// </summary>
    insert,

    /// <summary>
    /// 更新
    /// </summary>
    update
}

customlogger实现

using system.collections.concurrent;
using microsoft.extensions.logging;

namespace logging.abstractions;

public abstract class customlogger : icustomlogger, idisposable, iasyncdisposable
{
    protected ilogger<customlogger> logger { get; }

    protected concurrentqueue<writecommand> writequeue { get; }

    protected task writetask { get; }
    private readonly cancellationtokensource _cancellationtokensource;
    private readonly cancellationtoken _cancellationtoken;

    protected customlogger(ilogger<customlogger> logger)
    {
        logger = logger;

        writequeue = new concurrentqueue<writecommand>();
        _cancellationtokensource = new cancellationtokensource();
        _cancellationtoken = _cancellationtokensource.token;
        writetask = task.factory.startnew(trywriteasync, _cancellationtoken, taskcreationoptions.longrunning, taskscheduler.default);
    }

    public void logreceived(customlogentry logentry)
    {
        writequeue.enqueue(new writecommand(writecommandtype.insert, logentry));
    }

    public void logprocessed(string messageid, bool issuccess)
    {
        var logentry = getbyid(messageid);
        if (logentry == null)
        {
            return;
        }

        logentry.issuccess = issuccess;
        logentry.updatetime = datetime.utcnow;
        writequeue.enqueue(new writecommand(writecommandtype.update, logentry));
    }

    private async task trywriteasync()
    {
        try
        {
            while (!_cancellationtoken.iscancellationrequested)
            {
                if (writequeue.isempty)
                {
                    await task.delay(1000, _cancellationtoken);
                    continue;
                }

                if (writequeue.trydequeue(out var writecommand))
                {
                    await writeasync(writecommand);
                }
            }

            while (writequeue.trydequeue(out var remainingcommand))
            {
                await writeasync(remainingcommand);
            }
        }
        catch (operationcanceledexception)
        {
            // 任务被取消,正常退出
        }
        catch (exception e)
        {
            logger.logerror(e, "处理待写入日志队列异常");
        }
    }

    protected abstract customlogentry? getbyid(string messageid);

    protected abstract task writeasync(writecommand writecommand);

    public void dispose()
    {
        dispose(true);
        gc.suppressfinalize(this);
    }

    public async valuetask disposeasync()
    {
        await disposeasynccore();
        dispose(false);
        gc.suppressfinalize(this);
    }

    protected virtual void dispose(bool disposing)
    {
        if (disposing)
        {
            _cancellationtokensource.cancel();
            try
            {
                writetask.wait();
            }
            catch (aggregateexception ex)
            {
                foreach (var innerexception in ex.innerexceptions)
                {
                    logger.logerror(innerexception, "释放资源异常");
                }
            }
            finally
            {
                _cancellationtokensource.dispose();
            }
        }
    }

    protected virtual async task disposeasynccore()
    {
        _cancellationtokensource.cancel();
        try
        {
            await writetask;
        }
        catch (exception e)
        {
            logger.logerror(e, "释放资源异常");
        }
        finally
        {
            _cancellationtokensource.dispose();
        }
    }
}

1.3 表结构迁移

为了方便表结构迁移,我们可以使用fluentmigrator.runner.mysql,在项目中引入:

<project sdk="microsoft.net.sdk">
	<propertygroup>
		<targetframework>net8.0</targetframework>
		<implicitusings>enable</implicitusings>
		<nullable>enable</nullable>
	</propertygroup>

	<itemgroup>
		<packagereference include="fluentmigrator.runner.mysql" version="6.2.0" />
	</itemgroup>
</project>

新建一个createlogentriestable,放在migrations目录下

[migration(20241216)]
public class createlogentriestable : migration
{
    public override void up()
    {
        create.table("logentries")
            .withcolumn("id").asstring(36).primarykey()
            .withcolumn("message").ascustom(text)
            .withcolumn("issuccess").asboolean().notnullable()
            .withcolumn("createtime").asdatetime().notnullable()
            .withcolumn("updatetime").asdatetime();
    }

    public override void down()
    {
        delete.table("logentries");
    }
}

添加服务注册

using fluentmigrator.runner;
using logging.abstractions;
using logging.abstractions.migrations;

namespace microsoft.extensions.dependencyinjection;

public static class customloggerextensions
{
    /// <summary>
    /// 添加自定义日志服务表结构迁移
    /// </summary>
    /// <param name="services"></param>
    /// <param name="connectionstring">数据库连接字符串</param>
    /// <returns></returns>
    public static iservicecollection addcustomloggermigration(this iservicecollection services, string connectionstring)
    {
        services.addfluentmigratorcore()
            .configurerunner(
                rb => rb.addmysql5()
                    .withglobalconnectionstring(connectionstring)
                    .scanin(typeof(createlogentriestable).assembly)
                    .for.migrations()
            )
            .addlogging(lb =>
            {
                lb.addfluentmigratorconsole();
            });

        using var serviceprovider = services.buildserviceprovider();
        using var scope = serviceprovider.createscope();
        var runner = scope.serviceprovider.getrequiredservice<imigrationrunner>();
        runner.migrateup();

        return services;
    }
}

2. entityframework core 的实现

2.1 数据库上下文

新建logging.entityframeworkcore项目,添加对logging.abstractions项目的引用,并在项目中安装pomelo.entityframeworkcore.mysqlmicrosoft.extensions.objectpool

<project sdk="microsoft.net.sdk">
  <propertygroup>
    <targetframework>net8.0</targetframework>
    <implicitusings>enable</implicitusings>
    <nullable>enable</nullable>
  </propertygroup>

  <itemgroup>
    <packagereference include="microsoft.extensions.objectpool" version="8.0.11" />
    <packagereference include="pomelo.entityframeworkcore.mysql" version="8.0.2" />
  </itemgroup>

  <itemgroup>
    <projectreference include="..\logging.abstractions\logging.abstractions.csproj" />
  </itemgroup>

</project>

创建customloggerdbcontext类,用于管理日志实体

using logging.abstractions;
using microsoft.entityframeworkcore;

namespace logging.entityframeworkcore;

public class customloggerdbcontext(dbcontextoptions<customloggerdbcontext> options) : dbcontext(options)
{
    public virtual dbset<customlogentry> logentries { get; set; }
}

使用 objectpool 管理 dbcontext:提高性能,减少 dbcontext 的创建和销毁开销。

创建customloggerdbcontextpoolpolicy

using microsoft.entityframeworkcore;
using microsoft.extensions.objectpool;

namespace logging.entityframeworkcore;

/// <summary>
/// dbcontext 池策略
/// </summary>
/// <param name="options"></param>
public class customloggerdbcontextpoolpolicy(dbcontextoptions<customloggerdbcontext> options) : ipooledobjectpolicy<customloggerdbcontext>
{
    /// <summary>
    /// 创建 dbcontext
    /// </summary>
    /// <returns></returns>
    public customloggerdbcontext create()
    {
        return new customloggerdbcontext(options);
    }

    /// <summary>
    /// 回收 dbcontext
    /// </summary>
    /// <param name="context"></param>
    /// <returns></returns>
    public bool return(customloggerdbcontext context)
    {
        // 重置 dbcontext 状态
        context.changetracker.clear();
        return true; 
    }
} 

2.2 实现日志写入

创建一个efcorecustomlogger,继承自customlogger,实现日志写入的具体逻辑

using logging.abstractions;
using microsoft.extensions.logging;
using microsoft.extensions.objectpool;

namespace logging.entityframeworkcore;

/// <summary>
/// efcore自定义日志记录器
/// </summary>
public class efcorecustomlogger(objectpool<customloggerdbcontext> contextpool, ilogger<efcorecustomlogger> logger) : customlogger(logger)
{
    /// <summary>
    /// 根据id查询日志
    /// </summary>
    /// <param name="logid"></param>
    /// <returns></returns>
    protected override customlogentry? getbyid(string logid)
    {
        var dbcontext = contextpool.get();
        try
        {
            return dbcontext.logentries.find(logid);
        }
        finally
        {
            contextpool.return(dbcontext);
        }
    }

    /// <summary>
    /// 写入日志
    /// </summary>
    /// <param name="writecommand"></param>
    /// <returns></returns>
    /// <exception cref="argumentoutofrangeexception"></exception>
    protected override async task writeasync(writecommand writecommand)
    {
        var dbcontext = contextpool.get();

        try
        {
            switch (writecommand.commandtype)
            {
                case writecommandtype.insert:
                    if (writecommand.logentry != null)
                    {
                        await dbcontext.logentries.addasync(writecommand.logentry);
                    }

                    break;
                case writecommandtype.update:
                {
                    if (writecommand.logentry != null)
                    {
                        dbcontext.logentries.update(writecommand.logentry);
                    }

                    break;
                }
                default:
                    throw new argumentoutofrangeexception();
            }

            await dbcontext.savechangesasync();
        }
        finally
        {
            contextpool.return(dbcontext);
        }
    }
}

添加服务注册

using logging.abstractions;
using microsoft.entityframeworkcore;
using microsoft.extensions.dependencyinjection;
using microsoft.extensions.objectpool;

namespace logging.entityframeworkcore;

public static class efcorecustomloggerextensions
{
    public static iservicecollection addefcorecustomlogger(this iservicecollection services, string connectionstring)
    {
        if (string.isnullorempty(connectionstring))
        {
            throw new argumentnullexception(nameof(connectionstring));
        }

        services.addcustomloggermigration(connectionstring);

        services.addsingleton<objectpoolprovider, defaultobjectpoolprovider>();
        services.addsingleton(serviceprovider =>
        {
            var options = new dbcontextoptionsbuilder<customloggerdbcontext>()
                .usemysql(connectionstring, serverversion.autodetect(connectionstring))
                .options;
            var poolprovider = serviceprovider.getrequiredservice<objectpoolprovider>();
            return poolprovider.create(new customloggerdbcontextpoolpolicy(options));
        });

        services.addsingleton<icustomlogger, efcorecustomlogger>();

        return services;
    }
}

3. mysqlconnector 的实现

mysqlconnector 的实现比较简单,利用原生sql操作数据库完成日志的插入和更新。

新建logging.mysqlconnector项目,添加对logging.abstractions项目的引用,并安装mysqlconnector

<project sdk="microsoft.net.sdk">
	<propertygroup>
		<targetframework>net8.0</targetframework>
		<implicitusings>enable</implicitusings>
		<nullable>enable</nullable>
	</propertygroup>

	<itemgroup>
		<packagereference include="mysqlconnector" version="2.4.0" />
	</itemgroup>

	<itemgroup>
		<projectreference include="..\logging.abstractions\logging.abstractions.csproj" />
	</itemgroup>

</project>

3.1 sql脚本

为了方便维护,我们把需要用到的sql脚本放在一个consts类中

namespace logging.mysqlconnector;

public class consts
{
    /// <summary>
    /// 插入日志
    /// </summary>
    public const string insertsql = """
                                    insert into `logentries` (`id`, `tranceid`, `biztype`, `body`, `component`, `msgtype`, `status`, `createtime`, `updatetime`, `remark`)
                                    values (@id, @tranceid, @biztype, @body, @component, @msgtype, @status, @createtime, @updatetime, @remark);
                                    """;

    /// <summary>
    /// 更新日志
    /// </summary>
    public const string updatesql = """
                                    update `logentries` set `status` = @status, `updatetime` = @updatetime
                                    where `id` = @id;
                                    """;

    /// <summary>
    /// 根据id查询日志
    /// </summary>
    public const string querybyidsql = """
                                        select `id`, `tranceid`, `biztype`, `body`, `component`, `msgtype`, `status`, `createtime`, `updatetime`, `remark`
                                        from `logentries`
                                        where `id` = @id;
                                        """;
}

3.2 实现日志写入

创建mysqlconnectorcustomlogger类,实现日志写入的具体逻辑

using logging.abstractions;
using microsoft.extensions.logging;
using mysqlconnector;

namespace logging.mysqlconnector;

/// <summary>
/// 使用 mysqlconnector 实现记录日志
/// </summary>
public class mysqlconnectorcustomlogger : customlogger
{

    /// <summary>
    /// 数据库连接字符串
    /// </summary>
    private readonly string _connectionstring;

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="connectionstring">mysql连接字符串</param>
    /// <param name="logger"></param>
    public mysqlconnectorcustomlogger(
        string connectionstring, 
        ilogger<mysqlconnectorcustomlogger> logger)
        : base(logger)
    {
        _connectionstring = connectionstring;
    }

    /// <summary> 
    /// 根据id查询日志
    /// </summary>
    /// <param name="messageid"></param>
    /// <returns></returns>
    protected override customlogentry? getbyid(string messageid)
    {
        using var connection = new mysqlconnection(_connectionstring);
        connection.open();

        using var command = new mysqlcommand(consts.querybyidsql, connection);
        command.parameters.addwithvalue("@id", messageid);

        using var reader = command.executereader();
        if (!reader.read())
        {
            return null;
        }

        return new customlogentry
        {
            id = reader.getstring(0),
            message = reader.getstring(1),
            issuccess = reader.getboolean(2),
            createtime = reader.getdatetime(3),
            updatetime = reader.getdatetime(4)
        };
    }

    /// <summary>
    /// 处理日志
    /// </summary>
    /// <param name="writecommand"></param>
    /// <returns></returns>
    /// <exception cref="argumentoutofrangeexception"></exception>
    protected override async task writeasync(writecommand writecommand)
    {
        await using var connection = new mysqlconnection(_connectionstring);
        await connection.openasync();

        switch (writecommand.commandtype)
        {
            case writecommandtype.insert:
                {
                    if (writecommand.logentry != null)
                    {
                        await using var command = new mysqlcommand(consts.insertsql, connection);
                        command.parameters.addwithvalue("@id", writecommand.logentry.id);
                        command.parameters.addwithvalue("@message", writecommand.logentry.message);
                        command.parameters.addwithvalue("@issuccess", writecommand.logentry.issuccess);
                        command.parameters.addwithvalue("@createtime", writecommand.logentry.createtime);
                        command.parameters.addwithvalue("@updatetime", writecommand.logentry.updatetime);
                        await command.executenonqueryasync();
                    }

                    break;
                }
            case writecommandtype.update:
                {
                    if (writecommand.logentry != null)
                    {
                        await using var command = new mysqlcommand(consts.updatesql, connection);
                        command.parameters.addwithvalue("@id", writecommand.logentry.id);
                        command.parameters.addwithvalue("@issuccess", writecommand.logentry.issuccess);
                        command.parameters.addwithvalue("@updatetime", writecommand.logentry.updatetime);
                        await command.executenonqueryasync();
                    }

                    break;
                }
            default:
                throw new argumentoutofrangeexception();
        }
    }
}

添加服务注册

using logging.abstractions;
using logging.mysqlconnector;
using microsoft.extensions.logging;

namespace microsoft.extensions.dependencyinjection;

/// <summary>
/// mysqlconnector 日志记录器扩展
/// </summary>
public static class mysqlconnectorcustomloggerextensions
{
    /// <summary>
    /// 添加 mysqlconnector 日志记录器
    /// </summary>
    /// <param name="services"></param>
    /// <param name="connectionstring"></param>
    /// <returns></returns>
    public static iservicecollection addmysqlconnectorcustomlogger(this iservicecollection services, string connectionstring)
    {
        if (string.isnullorempty(connectionstring))
        {
            throw new argumentnullexception(nameof(connectionstring));
        }

        services.addsingleton<icustomlogger>(s =>
        {
            var logger = s.getrequiredservice<ilogger<mysqlconnectorcustomlogger>>();
            return new mysqlconnectorcustomlogger(connectionstring, logger);
        });
        services.addcustomloggermigration(connectionstring);

        return services;
    }
}

4. 使用示例

下边是一个entityframework core的实现使用示例,mysqlconnector的使用方式相同。

新建webapi项目,添加logging.ntityframeworkcore

var builder = webapplication.createbuilder(args);

// add services to the container.

builder.services.addcontrollers();
// learn more about configuring swagger/openapi at https://aka.ms/aspnetcore/swashbuckle
builder.services.addendpointsapiexplorer();
builder.services.addswaggergen();

// 添加entityframeworkcore日志记录器
var connectionstring = builder.configuration.getconnectionstring("mysql");
builder.services.addefcorecustomlogger(connectionstring!);

var app = builder.build();

// configure the http request pipeline.
if (app.environment.isdevelopment())
{
    app.useswagger();
    app.useswaggerui();
}

app.useauthorization();

app.mapcontrollers();

app.run();

在控制器中使用

namespace entityframeworkcoretest.controllers;

[apicontroller]
[route("[controller]")]
public class testcontroller(icustomlogger customlogger) : controllerbase
{
    [httppost("insertlog")]
    public iactionresult post(customlogentry model)
    {
        customlogger.logreceived(model);

        return ok(); 
    }

    [httpput("updatelog")]
    public iactionresult put(string messageid, messagestatus status)
    {
        customlogger.logprocessed(messageid, status);

        return ok();
    }
} 

以上就是.net core 实现一个自定义日志记录器的详细内容,更多关于.net core日志记录的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com