游戏上线后如何根据日志定位服务器问题
在游戏服务器开发中,日志是定位服务器问题的一个主要手段。特别是游戏服务上线之后,大量的用户操作会产生庞大的日志数据,假如在线上的服务器出现了bug,或者是数据异常,那么怎么样在这么庞大的日志文件中,怎么样方便快速的找到自己所要的数据呢。
线上的日志配置和开发环境的日志配置是不一样的。日志输出会分成几个级别,以log4j2为例,优先级从高到低依次为:OFF、FATAL、ERROR、WARN、INFO、DEBUG、TRACE、 ALL。
-
ALL 最低等级的,它会打印所有的日志输出。
-
TRACE 非常低等级的级别,一般记录的都是不重置的日志输出。
-
DEBUG 用于调试程序时的日志记录,它是最常用的级别之一。基本上程序中随处可见。但是如果程序调试没有问题之后,debug级别的日志是不应该再被记录的。
-
INFO 这个主要记录一些比较重要的数据。比如一些数据想要形成文件形式的存储,可以使用这个级别。在生产环境也是会记录这个级别的日志的。
-
WARN 这个级别会记录一些可以预料到,但是不影响程序运行的意外情况。比如如果某个值特别重置,如果没有配置的话,可以容忍使用默认值,服务器可以正常运行,但是需要给程序员知道这个问题,就使用这个日志级别记录。
-
ERROR 指出虽然发生错误事件,但仍然不影响系统的继续运行。打印错误和异常信息,如果不想输出太多的日志,可以使用这个级别。
-
FATAL 指出每个严重的错误事件将会导致应用程序的退出。这个级别比较高了。重大错误,这种级别你可以直接停止程序了。
-
OFF 最高等级的,用于关闭所有日志记录。
如果将log level设置在某一个级别上,那么比此级别优先级高的log都能打印出来。例如,如果设置优先级为WARN,那么OFF、FATAL、ERROR、WARN 4个级别的log能正常输出,而INFO、DEBUG、TRACE、 ALL级别的log则会被忽略。Log4j建议只使用四个级别,优先级从高到低分别是ERROR、WARN、INFO、DEBUG。
为了方便服务器上线之后查询日志,在开发的时候,就需要提前对日志进行良好的规划。在一些游戏服务器构架设计时,有些开发者为了统一记录日志,会在服务器中提供一个日志工具类,如下所示:
public class LogUtil {
private static Logger logger = LoggerFactory.getLogger(LogUtil.class);
public static void debug(String message) {
logger.debug(message);
}
public static void info(String message) {
logger.info(message);
}
public static void warn(String message) {
logger.warn(message);
}
public static void error(String message) {
logger.error(message);
}
}
然后在日志配置文件中添加一条配置,所有的日志都输出到同一个文件中。这样在开发的时候,需要打印日志的地方都统一调用这个日志工具类,以前我也这样使用过一段时间。这样做的好处就是在开发的时候不需要再创建使用日志类,直接调即可。统一打印到同一个日志文件中也省去了麻烦的配置。但是这就导到了别一个问题,也是我们今天要解决的问题,把所的日志都统一使用这一个日志工具类记录,如果上线之后,某个业务功能出问题了,想实时查看一下某个方法的调用时,打印的debug日志,该怎么办?如果将日志级别调到debug级别,那么所有的类的debug日志都将输出,产生的debug数据会比原来info级别多出好几倍来,如果这个时候在线人数比较多的话,就可以明显感觉到服务器的吞吐量下降,甚至出现数据返回延迟过高,客户端卡顿的现象。而且这个大量的debug日志输出,也不方便定位我们想要的debug日志数据。
解决的办法也很简单,分而治之。在业务开发中,每个类的职责应该尽量的少,理想的状态就是一个类就处理一个问题或一类相同的问题。所以需要记录日志的类,应该使用自己单独的Logger。如下面代码所示:
public class ChatDispatchHandler extends ChannelInboundHandlerAdapter {
private static Logger logger = LoggerFactory.getLogger(ChatDispatchHandler.class);
//省略业务调
}
@Service
public class UserService {
private static Logger logger = LoggerFactory.getLogger(UserService .class);
//省略业务调
}
在开发阶段,想要所有的业务日志都输出为debug级别,以log4j2为例,配置如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<!-- 用于指定log4j自动重新配置的监测间隔时间,单位是秒 -->
<configuration monitorInterval="10">
<Properties>
<Property name="log-path">server_logs</Property>
</Properties>
<Appenders>
<Console name="console" target="SYSTEM_OUT">
<PatternLayout
pattern="%d{yyyy-MM-dd HH:mm:ss} %-5level %l - %msg%xEx%n" />
</Console>
</Appenders>
<Loggers>
<logger name="com.xinyue" level="debug" additivity="false">
<appender-ref ref="console" />
</logger>
<root level="error">
<appender-ref ref="console" />
</root>
</Loggers>
</configuration>
这里使用到了一个日志logger配置规则,如果logger的name是一个包的目录,那么这个包下面包括子包的所有日志级别就是这个logger对应的级别。将来游戏服务器上线之后,就不需要debug级别的日志输出了,可以直接把它变成info级别,如下所示:
<logger name="com.xinyue" level="info" additivity="false">
<appender-ref ref="console" />
</logger>
这样,这个包包括子包下面的所有日志输出都变成info级别了,debug记录的日志就不会输出了。
那么如果线上的服务某个功能出现了问题,想要查看那个功能所在类的debug数据怎么做呢?这个问题日志框架的设计者已经想到了。在服务器运行的过程中,日志框架会定时自动扫描日志配置文件,如果发现日志配置文件有变动的话,它会自动热更新日志配置,对服务器运行没有任务的影响。以log4j2为例,在配置文件的最外层有这样一个配置,如下所示:
<configuration monitorInterval="10">
它表示每隔10秒,检测一次配置文件是否变动,如果有变动,就会自动加载。利用这个特性,如果想查看某个类的debug输出,就可以在日志配置文件中添加这个类的logger配置,比如要查看ChatDispatchHandler这个类的debug输出,可以添加如下配置:
<logger name="com.xinyue.chat.netty.ChatDispatchHandler" level="debug" additivity="false">
<appender-ref ref="console" />
</logger>
添加之后,过10秒就可以看到它的日志输出了。