目录
日志概念和分类
应用程序中的日志
数据库中的日志
分布式系统中的日志
日志结构设计
日志能做什么事情
线上日志排错
借助 ELK,GreyLog 等第三方工具监控程序
借助 FileBeat,Flume 等工具自定义日志收集
日志该怎么打印
什么时候应该打日志
基本格式
jvm 动态调试
参考资料
日志实际上只是一种按照时间顺序存储记录的数据表或文件
它记录了什么时间发生了什么事情. 而对分布式数据系统, 在许多方面, 这是要解决的问题的真正核心
日志概念和分类
应用程序中的日志
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 内部每个模块业务被触发的频次, 这只是处理了业务这一范围, 其实还可以有很多范围比如: 关键业务路径范围, 业务异常警告报错范围 等等, 一切围绕应用的日志
日志该怎么打印
什么时候应该打日志
当你遇到问题的时候, 只能通过 debug 功能来确定问题, 你应该考虑打日志, 良好的系统, 是可以通过日志进行问题定为的.
当你碰到 if...else 或者 switch 这样的分支时, 要在分支的首行打印日志, 用来确定进入了哪个分支
经常以功能为核心进行开发, 你应该在提交代码前, 可以确定通过日志可以看到整个流程
基本格式
必须使用参数化信息的方式:
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
来源: https://www.cnblogs.com/buoge/p/11152175.html