广告平台的业务逻辑比较复杂,一次竞价请求,可能有几千个候选广告,最终的胜出者就那么几个。其他那些广告为什么没有曝光的机会呢?
经常被运营问这样的问题,而运营同学又往往是因为广告主问他们。如果没有一份详细的日志,就没有办法回答。
但是,上千的候选广告被淘汰,如果每一个广告被淘汰的原因都写入日志,不说磁盘空间爆炸,还影响程序的整体性能
另外,测试环境一般会输出 debug 级别的日志,在生产环境肯定不能输出 debug 级别,这也涉及到日志级别和环境的关系
有时候线上有问题,想要查一下 debug 日志,因为当前日志级别高于 debug 没法查,那要专门部署一个版本吗?
这里分享下我在日志实践中采用的方法
java
很多同学不注意日志的配置,自己的业务在输出日志,引入的二方库,三方库也在输出日志,导致日志文件里一大堆垃圾信息
一般来说,我们希望自己的业务输出 info 级别的日志,其他库的日志到 error 级别就行了。
我的实践是将日志级别整体设置到 error,再将自身业务的日志级别设为 info,实例如下
logger> logger> root>
slf4j 是事实上的日志标准,其支持 mdc,其实本质就是利用 threadlocal 来保存你的业务 id,在每一次日志打印时都自动输出,这样可以通过业务 id 将多个日志串起来,方便日志查看
日志配置如下
"info_file" %nopex�te %level [%logger] %mdc{sid:--} - %msg%npattern> utf-8charset> encoder>
注意日志格式配置里的 %mdc{sid:--},这里用 %mdc 来映射你存储在 mdc 里的变量,后面的 :-- 是当 mdc 里没有对应变量时输出的默认值(-)
代码里 mdc 里存放数据如下
mdc.put("sid", sessionid);
你只需要在接收到请求的第一时间将 sessionid 存储到 mdc,后续业务日志里就自动打印出这个 sessionid 了,当然,因为是基于 threadlocal 的,如果你的业务在某个环节需要使用多线程,使用了多线程的那部分处理逻辑的 mdc 里是没有这个 sid 的
这种因为多线程导致 mdc 失效的问题,目前没有很好的解决方法,有的文章表示可以自定义线程池,我的做法是在多线程的第一行代码里手动将 sid 存入 mdc
在业务处理结束时,应该清除 mdc,代码如下
mdc.remove("sid");
默认情况下,我们的日志级别设为 info,直接上生产环境没有问题。但是在开发和测试环境,想要用 debug 级别,该怎么做呢?
我有个同事的解决办法是预先写了多个日志配置文件,分别设定不同的级别,在程序启动时,根据当前配置中心的配置加载不同的配置文件
我的做法是程序启动时修改日志级别,比如我的业务代码都在 com.refusea.xxx 包下,那么启动时如果是开发测试环境,我会修改日志级别到 debug,代码示意如下
@componentpublic class envhelper {
// 是否生产环境 private final boolean online; public envhelper() {
// 基于注册中心提供的工具类, 判断当前环境 string env = env.getenvironmental(); online = env != null && env.startswith("online"); // 非生产环境, 修改日志级别到 debug if (!online) {
ch.qos.logback.classic.logger rootlogger = (ch.qos.logback.classic.logger) loggerfactory .getlogger("com.refusea.xxx"); rootlogger.setlevel(level.debug); } } /** 是否生产环境 */ public boolean isonline() {
return online; }}
这里比较关键的就是在程序启动时判断出当前的环境,我司的配置中心提供了工具类来判断,如果采用开源的配置中心方案,应该也都有类似的功能
如果没有用配置中心或者配置中心不支持获取环境信息怎么办呢?我觉得只能用笨方法了,比如在配置文件里列出开发/测试环境的 ip 地址,启动时根据自身 ip 来判断;或者在启动时通过给 jvm 传递参数的方式
业务日志太多,全部打印出来系统性能顶不住,磁盘空间也顶不住,所以通常都是以 debug 级别输出日志,生产环境用 info 级别,但是生产环境完全不输出日志,遇到问题就干瞪眼。
我的解决办法是抽样,比如说每 10000 次请求,打印一次完整的日志,这就要求我的日志输出语句不能直接用 log.debug 的形式
首先,我会有个 session 类,该类会全程跟随我的业务逻辑,作为参数传给几乎所有方法,在这个类的构造方法里抽样决定本次日志是否打印,代码示意
public static interface trackable {
boolean istracking();}
public class session implements trackable {
private static final int cycle = 1000; private static final atomicinteger counter = new atomicinteger(0); private final boolean tracking; private final string id; public session(string id) {
this.id = id; // 每 10000 次跟踪一次日志 this.tracking = (counter.incrementandget() % 10000 == 0); this.mdc(); } @override public boolean istracking() {
return tracking; } public void mdc() {
mdc.put("sid", id); } public void clearmdc() {
mdc.remove("sid"); }}
在打印日志时,根据 session.tracking 属性来决定调用 debug 还是 info 方法,这个可以封装一个工具类,如下示意
public final class trackutil {
public static final void track(trackable trackable, logger log, string format, object... arg) {
if (trackable.istracking()) {
log.info(format, arg); } else if (log.isdebugenabled()) {
log.debug(format, arg); } }}
前面提到日志级别根据环境自动调整时,我的同事是使用了多个配置文件,在配置中心配置使用哪个配置文件。
这个方法有个缺点,就是在配置中心修改了配置文件后,影响当前环境的所有服务器,比如我们的生产环境是基于机房的,一个机房的服务器使用同一个环境配置。
而实际工作中,想要将日志级别短暂的调整为 debug,只希望修改某一台服务器
我的做法是通过 http 接口来发送修改日志级别的请求,在服务器上直接使用 curl 访问即可,为了安全,该接口会判断请求是否本机发起,如果不是本机发起是不会修改日志级别的
修改方法和启动时将级别从 info 调为 debug 是一样的,就不重复了
为了避免修改了日志级别后忘记还原,程序还会跑一个调度任务,每隔 3 分钟检查当前日志级别,如果当前日志级别为 debug 且持续时间超过阈值,会自动将日志级别重设为 info