这是《ABP大型项目实战》系列文章的一篇。
 
项目发布到生产环境后难免会有错误。
那么如何进行调试和排错呢?
 
我看到俱乐部里有人是直接登陆生产服务器把数据库下载到开发机器进行调试排错。
这种办法是不适用于大型项目的:
  1. 首先,大型项目(特别是全球都有分公司的大型项目)很有可能24小时都有人在使用。所以尽量避免直接登录生产服务器操作,就算部署,也应该用DevOps、蓝绿部署等办法。
  2. 另外,如果大型项目有采用金丝雀发布和A/B测试,那么把数据库下载到开发机器这种方法是很不适用的。
  3. 即使大型项目没有采用金丝雀发布和A/B测试,也不适合把数据库下载到开发机器进行调试排错。因为数据库有可能很大,网络传输需要时间,特别是连VPN的时候,甚至有可能要从欧洲传到中国,又要从中国回传到欧洲。
  4. 生产环境数据库下载后为了安全还需要脱敏,这也需要时间。
 
还有其他方法,但是这些方法都存在一个问题,就是时光不能倒流,很多时候你是不可能叫客户回来重现一遍操作流程来复现bug的。
 
那么有什么好办法呢?
有的,通过查看日志来调试与排错。
 
ABP在这方面做得不错,内置了审计日志,提供了详细日志基类。嗯,这是两块内容了,一篇文章是讲不完的,所以我分开多篇文章来讲。
先从审计日志开始吧。
 
不得不说,ABP在这方面做得很好,审计日志是透明的,你要关闭审计日志反而要写代码控制。当然,这里不建议关闭审计日志。
 
然而,ABP的审计日志只提供了写,没有提供读的UI,这样的话,要看审计日志就必须要打开数据库查看了。
从前面的描述看到,这种做法在大型项目肯定是行不通的啦。我们必须要提供读取审计日志的UI。这就是这篇文章的主题。
 
在我使用的ABP 3.4版本里面,并没有提供审计日志的读取AppService, 但是提供了审计日志的Entity class。(注意: ABP更新很频繁,所以你目前使用的版本有可能新增甚至删除了部分interface或class)
所以我们第一步是先围绕ABP提供的审计日志Entity class(AuditLog)来建立AppService class和相关读取Mehtod. 以下是ABP 3.4版本的示例代码:
 注意:这是示例代码,直接复制到你的项目里不经任何修改很大概率是编译不通过的,你必须要根据你的项目实际情况进行修改,你最起码要改命名空间吧。
注意:ABP自己已经提供了AuditLog实体类,我们不需要另外再新建实体类了。
IAuditLogAppService.cs
 public interface IAuditLogAppService : IApplicationService
{
/// <summary>
/// 大型项目的审计日志量会十分大,所以最起码要分页
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
Task<PagedResultDto<AuditLogListDto>> GetAuditLogs(GetAuditLogsInput input);
/// <summary>
/// 一定要提供Excel下载功能,一般建议是按照时间段选取
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
Task<FileDto> GetAuditLogsToExcel(GetAuditLogsInput input);
/// <summary>
/// 提供全部审计日志的Excel下载,因为数据量会比较大,需要在服务器先压缩好,再提供给客户端下载。
/// </summary>
/// <returns></returns>
Task<FileDto> GetAuditLogsToExcel(); //List<AuditLogListDto> GetAllAuditLogs(); //错误案例示范,大型项目的审计日志量会十分大,所以最起码要分页
}

  AuditLogListDto.cs

using System;
using Abp.Application.Services.Dto;
using Abp.Auditing;
using Abp.AutoMapper; [AutoMapFrom(typeof(AuditLog))]
public class AuditLogListDto : EntityDto<long>
{
public long? UserId { get; set; } public string UserName { get; set; } public int? ImpersonatorTenantId { get; set; } public long? ImpersonatorUserId { get; set; } public string ServiceName { get; set; } public string MethodName { get; set; } public string Parameters { get; set; } public DateTime ExecutionTime { get; set; } public int ExecutionDuration { get; set; } public string ClientIpAddress { get; set; } public string ClientName { get; set; } public string BrowserInfo { get; set; } public string Exception { get; set; } public string CustomData { get; set; }
}

  AuditLogAppService.cs

[DisableAuditing] //屏蔽这个AppService的审计功能
[AbpAuthorize(AppPermissions.Pages_Administration_AuditLogs)]
public class AuditLogAppService : DemoAppServiceBase, IAuditLogAppService
{
private readonly IRepository<AuditLog, long> _auditLogRepository;
private readonly IRepository<User, long> _userRepository;
private readonly IAuditLogListExcelExporter _auditLogListExcelExporter;
private readonly INamespaceStripper _namespaceStripper; public AuditLogAppService(
IRepository<AuditLog, long> auditLogRepository,
IRepository<User, long> userRepository,
IAuditLogListExcelExporter auditLogListExcelExporter,
INamespaceStripper namespaceStripper)
{
_auditLogRepository = auditLogRepository;
_userRepository = userRepository;
_auditLogListExcelExporter = auditLogListExcelExporter;
_namespaceStripper = namespaceStripper;
} // 下面视具体业务情况实现接口的方法
}

  以下是使用EF来查询Auditlog关联User表数据的示例代码:

 IQueryable<AuditLogAndUser> query = from auditLog in _auditLogRepository.GetAll()
join user in _userRepository.GetAll() on auditLog.UserId equals user.Id into userJoin
from joinedUser in userJoin.DefaultIfEmpty()
where auditLog.ExecutionTime >= input.StartDate && auditLog.ExecutionTime <= input.EndDate
select new AuditLogAndUser { AuditLog = auditLog, User = joinedUser }; query = query
//.WhereIf(!input.UserName.IsNullOrWhiteSpace(), item => item.User.UserName.Contains(input.UserName))// 以前的写法,不支持多个用户名查询
.WhereIf(usernamelist != null, item => usernamelist.Contains(item.User.UserName))
//.WhereIf(!input.RealUserName.IsNullOrWhiteSpace(), item => item.User.Name.Contains(input.RealUserName))// 以前的写法,不支持多个用户名查询
.WhereIf(realusernamelist != null, item => realusernamelist.Contains(item.User.Name))
.WhereIf(!input.ServiceName.IsNullOrWhiteSpace(), item => item.AuditLog.ServiceName.Contains(input.ServiceName))
.WhereIf(!input.MethodName.IsNullOrWhiteSpace(), item => item.AuditLog.MethodName.Contains(input.MethodName))
.WhereIf(!input.BrowserInfo.IsNullOrWhiteSpace(), item => item.AuditLog.BrowserInfo.Contains(input.BrowserInfo))
.WhereIf(input.MinExecutionDuration.HasValue && input.MinExecutionDuration > , item => item.AuditLog.ExecutionDuration >= input.MinExecutionDuration.Value)
.WhereIf(input.MaxExecutionDuration.HasValue && input.MaxExecutionDuration < int.MaxValue, item => item.AuditLog.ExecutionDuration <= input.MaxExecutionDuration.Value)
.WhereIf(input.HasException == true, item => item.AuditLog.Exception != null && item.AuditLog.Exception != "")
.WhereIf(input.HasException == false, item => item.AuditLog.Exception == null || item.AuditLog.Exception == "");

这里可以看到,既有大量数据又有多表关联查询哦,并且纯是使用EF去做,实践证明EF性能并不差,顺便推广一下Edi的另一篇文章《Entity Framework 的一些性能建议

然而还是有同学强烈要求提供SQL版本,好吧,以下是最简单的sql版本:

select * from AbpAuditLogs left join AbpUsers on (AbpAuditLogs.UserId = AbpUsers.Id)
where AbpAuditLogs .ExecutionTime >= '2019/2/18' and AbpAuditLogs.ExecutionTime <= '2019/2/19'

这里还有个小技巧可以节省时间:

  1. 先手动建立一个AuditLog类

        public class AuditLog
    {
    /// <summary>
    /// TenantId.
    /// </summary>
    public virtual int? TenantId { get; set; } /// <summary>
    /// UserId.
    /// </summary>
    public virtual long? UserId { get; set; } /// <summary>
    /// Service (class/interface) name.
    /// </summary>
    public virtual string ServiceName { get; set; } /// <summary>
    /// Executed method name.
    /// </summary>
    public virtual string MethodName { get; set; } /// <summary>
    /// Calling parameters.
    /// </summary>
    public virtual string Parameters { get; set; } /// <summary>
    /// Return values.
    /// </summary>
    public virtual string ReturnValue { get; set; } /// <summary>
    /// Start time of the method execution.
    /// </summary>
    public virtual DateTime ExecutionTime { get; set; } /// <summary>
    /// Total duration of the method call as milliseconds.
    /// </summary>
    public virtual int ExecutionDuration { get; set; } /// <summary>
    /// IP address of the client.
    /// </summary>
    public virtual string ClientIpAddress { get; set; } /// <summary>
    /// Name (generally computer name) of the client.
    /// </summary>
    public virtual string ClientName { get; set; } /// <summary>
    /// Browser information if this method is called in a web request.
    /// </summary>
    public virtual string BrowserInfo { get; set; } /// <summary>
    /// Exception object, if an exception occured during execution of the method.
    /// </summary>
    public virtual string Exception { get; set; } /// <summary>
    /// <see cref="AuditInfo.ImpersonatorUserId"/>.
    /// </summary>
    public virtual long? ImpersonatorUserId { get; set; } /// <summary>
    /// <see cref="AuditInfo.ImpersonatorTenantId"/>.
    /// </summary>
    public virtual int? ImpersonatorTenantId { get; set; } /// <summary>
    /// <see cref="AuditInfo.CustomData"/>.
    /// </summary>
    public virtual string CustomData { get; set; } }

      

  2. 选中这个类然后鼠标右键52abp代码生成器
  3. 生成后再把AppService接口和类改成上面的代码
  4. 删除第一步建立的AuditLog类。把引用修正为“Abp.Auditing”

这个小技巧大概可以节省你三十分钟时间吧。

 
第二步就是UI层面,因为审计日志数量会很大,查阅基本要靠搜索,所以要选一个查阅功能强大的Grid组件,根据我个人经验,推荐使用primeng的table组件。具体代码因为没有什么技术难点,我就不贴了。
关于其他成熟框架组件库和如何在angular中使用多个成熟控件框架,请参考我的另外一篇文章《如何用ABP框架快速完成项目(6) - 用ABP一个人快速完成项目(2) - 使用多个成熟控件框架
对了,前端一定要完成导出Excel格式功能哦,你会发觉这个功能实在是太赞了!
 
现在终于可以不用登陆生产服务器就可以查看审计日志了。
但是这只是开始!
因为一个大型项目里,审计日志的增长速度是惊人的,如果审计日志表和主数据库放在一起,是十分不科学的。
那么我们如何解决这个问题呢?敬请期待下一篇文章《ABP大型项目实战(2) - 调试与排错 - 日志 - 单独存储审计日志》
 
Q&A:
  1. 如何禁用具体某个接口的审计功能?
    答:在类头加上如下属性

    [DisableAuditing] //屏蔽这个AppService的审计功能
    [AbpAuthorize(AppPermissions.Pages_Administration_AuditLogs)]
    public class AuditLogAppService : GHITAssetAppServiceBase, IAuditLogAppService

ABP大型项目实战(2) - 调试与排错 - 日志 - 查看审计日志的更多相关文章

  1. ABP大型项目实战(1) - 目录

    前面我写了<如何用ABP框架快速完成项目>系列文章,讲述了如何用ABP快速完成项目.   然后我收到很多反馈,其中一个被经常问到的问题就是,“看了你的课程,发现ABP的优势是快速开发,那么 ...

  2. Java 18套JAVA企业级大型项目实战分布式架构高并发高可用微服务电商项目实战架构

    Java 开发环境:idea https://www.jianshu.com/p/7a824fea1ce7 从无到有构建大型电商微服务架构三个阶段SpringBoot+SpringCloud+Solr ...

  3. GraphQL + React Apollo + React Hook 大型项目实战(32 个视频)

    GraphQL + React Apollo + React Hook 大型项目实战(32 个视频) GraphQL + React Apollo + React Hook 大型项目实战 #1 介绍「 ...

  4. Spark大型项目实战:电商用户行为分析大数据平台

    本项目主要讲解了一套应用于互联网电商企业中,使用Java.Spark等技术开发的大数据统计分析平台,对电商网站的各种用户行为(访问行为.页面跳转行为.购物行为.广告点击行为等)进行复杂的分析.用统计分 ...

  5. 项目实战8.2-Linux下Tomcat开启查看GC信息

    本文收录在Linux运维企业架构实战系列 转自https://www.cnblogs.com/along21/ 一.开启GC日志 1.在Tomcat 的安装路径下,找到bin/catalina.sh  ...

  6. ABP开发框架前后端开发系列---(7)系统审计日志和登录日志的管理

    我们了解ABP框架内部自动记录审计日志和登录日志的,但是这些信息只是在相关的内部接口里面进行记录,并没有一个管理界面供我们了解,但是其系统数据库记录了这些数据信息,我们可以为它们设计一个查看和导出这些 ...

  7. JAVA架构之单点登录 任务调度 权限管理 性能优化大型项目实战

    单点登录SSO(Single Sign On)说得简单点就是在一个多系统共存的环境下,用户在一处登录后,就不用在其他系统中登录,也就是用户的一次登录能得到其他所有系统的信任.单点登录在大型网站里使用得 ...

  8. 企业项目实战 .Net Core + Vue/Angular 分库分表日志系统 | 简单的分库分表设计

    前言 项目涉及到了一些设计模式,如果你看的不是很明白,没有关系坚持下来,写完之后去思考去品,你就会有一种突拨开云雾的感觉,所以请不要在半途感觉自己看不懂选择放弃,如果我哪里写的详细,或者需要修正请联系 ...

  9. 企业项目实战 .Net Core + Vue/Angular 分库分表日志系统 | 前言

    介绍 大家好我是初久,一名从业4年的.Net开发攻城狮,从今天开始我会和大家一起对企业开发中常用的技术进行分享,一方面督促自己学习,一方面也希望大家可以给我指点出更好的方案,我们一起进步. 项目背景 ...

随机推荐

  1. 支持异步写入的日志类,支持Framework2.0

    因为工作需要需要在XP上运行一个C#编写的Winform插件,我就用Framework2.0,因为存在接口交互所以想保留交易过程的入参出参. 考虑到插件本身实施的因素,就没有使用Log4.NLog等成 ...

  2. C#组件系列——又一款日志组件:Elmah的学习和分享

    前言:好久没动笔了,都有点生疏,12月都要接近尾声,可是这月连一篇的产出都没有,不能坏了“规矩”,今天还是来写一篇.最近个把月确实很忙,不过每天早上还是会抽空来园子里逛逛.一如既往,园子里每年这个时候 ...

  3. 别的C#基础笔记

    1.方法名称             *  规范:每一个单词的首字母大写 2.方法的返回值 *  void:没有返回值.不能使用return来返回具体的值 ,但是可以使用return终止当前方法    ...

  4. [python爬虫]Requests-BeautifulSoup-Re库方案--robots协议与Requests库实战

    [根据北京理工大学嵩天老师“Python网络爬虫与信息提取”慕课课程编写 慕课链接:https://www.icourse163.org/learn/BIT-1001870001?tid=100223 ...

  5. Android Studio获取开发版SHA1值和发布版SHA1值的史上最详细方法

    前言: 今天我想把百度地图的定位集成到项目中来,想写个小小的案例,实现一下,但在集成百度地图时首先要申请秘钥,申请秘钥要用到SHA1值,所以今天就来总结一下怎样去获取这个值吧,希望对大家有帮助. 正常 ...

  6. centos服务器如何监控访问ip,并将非法ip通过防火墙禁用

    centos服务器如何监控访问ip,并将非法ip通过防火墙禁用 上周给朋友帮忙,上架了一款小游戏(年年有鱼),项目刚一上线,就遇到了ddos攻击,阿里云连续给出了6次ddos预警提示,服务器一度处于黑 ...

  7. Nodejs 操作 Sql Server

    Nodejs 操作 Sql Server Intro 最近项目需要爬取一些数据,数据有加密,前端的js又被混淆了,ajax请求被 hook 了,有些复杂,最后打算使用 puppeteer 来爬取数据. ...

  8. 章节九、4-ChromDriver介绍

    一.首先下载Chrom浏览器驱动,将驱动解压到存放火狐浏览器驱动文件路径中(请观看前面的章节) 1.进入该网址下载匹配本地浏览器版本的驱动 http://chromedriver.storage.go ...

  9. Java 图片爬虫,java打包jar文件

    目录 1. Java 图片爬虫,制作 .jar 文件 spider.java 制作 jar 文件 添加执行权限 1. Java 图片爬虫,制作 .jar 文件 spider.java spider.j ...

  10. Linux中ftp的常用命令

    转自:https://www.jb51.net/article/103904.htm FTP命令 ftp> ascii # 设定以ASCII方式传送文件(缺省值) ftp> bell # ...