程序中的日志

程序中的日志

日志实际上只是一种按照时间顺序存储记录的数据表或文件
它记录了什么时间发生了什么事情。而对分布式数据系统,在许多方面,这是要解决的问题的真正核心

日志概念和分类

应用程序中的日志

tomcat 日志

数据库中的日志

日志记录了发生了什么,而每个表或者索引都是更改历史中的一个投影。由于日志是立即持久化的,发生崩溃时,可以作为恢复其他所有持久化结构的可靠来源

机器可识别的日志的概念主要都被局限在数据库的内部。日志作为做数据订阅机制的用法似乎是偶然出现的。 但这正是支持各种的消息传输、数据流和实时数据处理的理想抽象

分布式系统中的日志

分布式系统以日志为中心的方案是来自于一个简单的观察,我们称之为状态机复制原理(State Machine Replication Principle):

如果两个相同的、确定性的进程从同一状态开始,并且以相同的顺序获得相同的输入,那么这两个进程将会生成相同的输出,并且结束在相同的状态

确定性(deterministic)
和幂等概念类似,同样的输入,任何情况都得到同样的输出
意味着处理过程是与时间无关的,而且不会让任何其他『带外』输入("out of band" input)影响其处理结果

举例:
我们甚至可以记录各个副本执行的机器指令序列的日志 或是 所调用的方法名和参数序列的日志。 只要两个进程用相同的方式处理这些输入,这些副本进程就会保持一致的状态。

物理与逻辑日志
对日志用法不同群体有不同的说法。数据库工作者通常说成 物理 日志(physical logging)和 逻辑 日志(logical logging)。 物理日志是指记录每一行被改变的内容。逻辑日志记录的不是改变的行而是那些引起行的内容改变的SQL语句(insert、update和delete语句)

状态机器模型
分布式系统文献通常把处理和复制(processing and replication)方案宽泛地分成两种。『状态机器模型』

『主-主模型』(active-active model), 记录输入请求的日志,各个复本处理每个请求。
『主-备模型』(primary-backup model),即选出一个副本做为leader,让leader按请求到达的顺序处理请求,并输出它请求处理的状态变化日志。 其他的副本按照顺序应用leader的状态变化日志,保持和leader同步,并能够在leader失败的时候接替它成为leader。

变更日志(changelog)101:表与事件的二象性(duality)

变更的日志 和 表之间有着迷人的二象性。 日志类似借贷清单和银行处理流水,而数据库表则是当前账户的余额。如果有变更日志,你就可以应用这些变更生成数据表并得到当前状态

可以认识到日志是更基本的数据结构:日志除了可用来创建原表,也可以用来创建各类衍生表
表与事件的二象性: 表支持了静态数据,而日志记录了变更。日志的魅力就在于它是变更的 完整 记录,它不仅仅包含了表的最终版本的内容, 而且可以用于重建任何存在过其它版本。事实上,日志可以看作是表 每个 历史状态的一系列备份

表与事件是数据在不同条件/场景下数据的性质,是对人们对数据的认识、理解或描述方式。

版本控制系统通过日志来完成复制:更新代码即是拉下补丁并应用到你的当前快照中。

日志结构设计

这块没有总结出一个完成的定义,还是把各种实现方式带给大家,让大家来理解看看大家有没有其他的更好的理解和建议

OpenTracing语义标准规范及实现
https://www.jianshu.com/p/a963ad0bbe3e

如何使用 AOP 和自定义注解实现请求方法前后日志打印
https://mp.weixin.qq.com/s/J9eyqIx5Oq-z6mYv8j1zpg

在SpringBoot项目中添加logback的MDC
(Mapped Diagnostic Context,用于打LOG时跟踪一个“会话“、一个”事务“)
https://blog.csdn.net/hongyang321/article/details/78803584

服务端最佳日志实践(v2.0)
https://zhuanlan.zhihu.com/p/27363484

基于elk的业务日志格式设计
https://segmentfault.com/a/1190000008227989

跟踪日志,程序日志,操作日志
http://dev.bingocc.com/dtls/logs/prog.html

{
    "type": 
    "trace_id":
    "span_id":
    "thread":
    "timestamp":
    "source_host":
    "component":    
    "logs": [
        {"LogItem":"LogItem"}
    ]
}

LogItem

{
    "level":
    "thread":
    "timestamp":
    "logger":    
    "message":
    "stack":
    "tags": {
    }
}

日志能做什么事情

线上日志排错

我们平常使用的 tail -f xxx.log 的形式,来动态观察错误,调试程序

借助 ELK,GreyLog 等第三方工具监控程序

方便查看
使用这类工具依赖可以有web界面来查看log,使用起来更友好,搜索条件,无需熟悉linux命令,即可快速的查询指定时间段的log或是包含指定关键字的log

聚类分析
elk 和 grey log都有一点分析图表和监控报警功能,都可以帮忙实现应用监控指标分析和报警

借助FileBeat,Flume等工具自定义日志收集

自定义日志收集处理
服务端接收到格式化的日志log,对log进行业务分析和统计,对程序状态,应用使用,业务状态进行分析,并能对突发情况作出预警和响应紧急措施

一段nginx log示例
-- 01 online-php7-6 2018-04-20T12:00:00+08:00 2018-04-20T12:00:00+08:00 0 web 34055 272408431 read_article {"novel_id":"2460","article_id":"883878","price":"35","consume":0}

各个字段含义
-- version, hostname, occur_time, log_time, type, platform, user_id, action, action_json

version: 01
hostname: online-php7-6 hostname
occur_time: 2018-04-20T12:00:00+08:00
log_time: 2018-04-20T12:00:00+08:00
platform: web
user_id: 367245568
action: read_article {"novel_id":"1542","article_id":"672651","price":"35","consume":1}

业务含义
根据上述的打印方式可以追踪到app内部每个模块业务被触发的频次,这只是处理了业务这一范围,其实还可以有很多范围比如:关键业务路径范围业务异常警告报错范围 等等,一切围绕应用的日志

日志该怎么打印

什么时候应该打日志

  1. 当你遇到问题的时候,只能通过debug功能来确定问题,你应该考虑打日志,良好的系统,是可以通过日志进行问题定为的。

  2. 当你碰到if…else 或者 switch这样的分支时,要在分支的首行打印日志,用来确定进入了哪个分支

  3. 经常以功能为核心进行开发,你应该在提交代码前,可以确定通过日志可以看到整个流程

基本格式

必须使用参数化信息的方式:

logger.debug("Processing trade with id:[{}] and symbol : [{}] ", id, symbol);

对于debug日志,必须判断是否为debug级别后,才进行使用:

if (logger.isDebugEnabled()) {
    logger.debug("Processing trade with id: " +id + " symbol: " + symbol);
}

使用[]进行参数变量隔离

logger.debug("Processing trade with id:[{}] and symbol : [{}] ", id, symbol);

并不是所有的service都进行出入口打点记录,单一、简单service是没有意义的(job除外,job需要记录开始和结束,)。
反例(不要这么做):

public List listByBaseType(Integer baseTypeId) {


   log.info("开始查询基地");
BaseExample ex=new BaseExample();
BaseExample.Criteria ctr = ex.createCriteria();
ctr.andIsDeleteEqualTo(IsDelete.USE.getValue());
Optionals.doIfPresent(baseTypeId, ctr::andBaseTypeIdEqualTo);
   log.info("查询基地结束");
return baseRepository.selectByExample(ex);


}

对于复杂的业务逻辑,需要进行日志打点,以及埋点记录,比如电商系统中的下订单逻辑,以及OrderAction操作(业务状态变更)

重要的状态的变更发送事件并留出监听接口,这个主题下含义是至少要打印log

service为SOA架构,微服务架构,REST接口,那么可以看成是一个外部接口提供方,那么必须记录入参。

调用其他第三方服务时,所有的出参和入参是必须要记录的(因为你很难追溯第三方模块发生的问题)

特别详细的系统运行完成信息,业务代码中,不要使用.(除非有特殊用意,否则请使用DEBUG级别替代)

@Override
@Transactional
public void createUserAndBindMobile(@NotBlank String mobile, @NotNull User user) throws CreateConflictException{
    boolean debug = log.isDebugEnabled();
    if(debug){
        log.debug("开始创建用户并绑定手机号. args[mobile=[{}],user=[{}]]", mobile, LogObjects.toString(user));
    }
    try {
        user.setCreateTime(new Date());
        user.setUpdateTime(new Date());
        userRepository.insertSelective(user);
        if(debug){
            log.debug("创建用户信息成功. insertedUser=[{}]",LogObjects.toString(user));
        }
        UserMobileRelationship relationship = new UserMobileRelationship();
        relationship.setMobile(mobile);
        relationship.setOpenId(user.getOpenId());
        relationship.setCreateTime(new Date());
        relationship.setUpdateTime(new Date());
        userMobileRelationshipRepository.insertOnDuplicateKey(relationship);
        if(debug){
            log.debug("绑定手机成功. relationship=[{}]",LogObjects.toString(relationship));
        }
        log.info("创建用户并绑定手机号. userId=[{}],openId=[{}],mobile=[{}]",user.getId(),user.getOpenId(),mobile); 
        // 如果考虑安全,手机号记得脱敏
    }catch(DuplicateKeyException e){
        log.info("创建用户并绑定手机号失败,已存在相同的用户. openId=[{}],mobile=[{}]",user.getOpenId(),mobile);
        throw new CreateConflictException("创建用户发生冲突, openid=[%s]",user.getOpenId());
    }
}

jvm 动态调试

日志打印一般都是,代码写好后部署上去的,忘记加的需要重新编写然后重启发布,这块其实也有热部署的方案

这块其实更侧重于在线调试,不过都是定位问题,也可以放在日志范畴来讨论,动态替换代码,加入日志打印代码,重新使用classloader加入内存

BTrace
这个偷懒贴了两篇帖子
https://tech.meituan.com/2019/02/28/java-dynamic-trace.html
https://www.ezlippi.com/blog/2018/01/btrace-introduce.html

arthas 同样可以动态打印追踪
使用redefine动态修改代码,加入log打印然后在重新编译会classloader
https://alibaba.github.io/arthas/
https://alibaba.github.io/arthas/redefine.html

参考资料

Logging 日志记录最佳实践
https://www.oschina.net/question/12_44624

Logging 最佳实践
https://www.cnblogs.com/zhengyun_ustc/archive/2012/12/15/logging_bp.html

OpenTracing语义标准规范及实现
https://www.jianshu.com/p/a963ad0bbe3e

统一日志服务系统架构
http://dev.bingocc.com/dtls/arch.html

原来这才是日志打印的正确姿势
https://blog.csdn.net/u013256816/article/details/94518764

一些设计上的基本常识

https://gitee.com/52itstyle/spring-boot-seckill/blob/master/架构之路/一些设计上的基本常识 - 梁飞.md#

日志:每个软件工程师都应该知道的有关实时数据的统一抽象
https://github.com/oldratlee/translations/tree/master/log-what-every-software-engineer-should-know-about-real-time-datas-unifying

posted @ 2019-07-08 16:46  buoge  阅读(3941)  评论(0编辑  收藏  举报