注:MaxCompute原名ODPS,是阿里云自研的大数据计算平台,文中出现的MaxCompute与ODPS都指代同一平台,不做区分
与MaxCompute JDBC相关的日志有两种,一种是由JDBC内部代码直接输出的日志,第二种是JDBC抛出异常后,由调用JDBC API的宿主应用捕获后输出的。由于第二类日志取决于宿主应用如何处理异常及如何配置日志体系,所以本文主要讨论的对象是第一种日志。
在2.0-beta之前,MaxCompute JDBC的日志只会输出到命令行终端(标准输出流),它底层使用的是JDK自带的 java.util.logging 的 ConsoleHandler。所以除非你是在命令行启动宿主应用的(相对的另一种方式是在可视化界面里双击可执行文件来启动宿主应用,并没有控制台界面),不然日志信息并不好找。这就给用户诊断问题和排查错误带来了不便。
为了让用户能自定义日志的相关配置,比如让用户自己来决定日志输出到终端还是文件、输出的日志文件应该放在哪里,又或者输出的日志文件如何做rotation等。MaxCompute JDBC 在 2.0-beta 中引入了slf4j和logback来让用户能自定义日志配置。为了避免JDBC选择的 slf4j 日志实现框架与宿主应用的 slf4j 日志实现框架产生冲突,MaxCompute JDBC的JAR包中并没有包含某个特定 slf4j 实现的JAR包。而与 logback 之间也不是强依赖的,只有当宿主应用不包含任何 slf4j 日志实现的时候,用户可以通过在classpath中加入 logback 的JAR包,并向JDBC传入logback的配置文件来启用该实现。
- 为什么slf4j日志实现框架之间会产生冲突?
slf4j 是为了统一不同日志框架的行为和API而产生的一套日志门面框架,它只提供API,但并不提供具体的日志解决方案实现。所以通常需要配合另一种具体的日志实现框架一起使用,比如 logback 、 log4j 。而假设MaxCompute JDBC选择的是 slf4j + logback ,而宿主应用选择的是 slf4j + log4j ,那么运行时就会出现冲突。因为通常用户在编程时会调用 slf4j 的LoggerFactory.getLogger
来获取日志实例,而在该方法首次被调用时会绑定一个具体的 slf4j 的实现,该行为依赖一个叫做org.slf4j.impl.StaticLoggerBinder
的类。而该类在所有 slf4j 的日志实现框架中都会存在,所以当多套 slf4j 日志实现同时存在于classpath的时候,完全依赖于运行时classloader先加载了哪套实现的org.slf4j.impl.StaticLoggerBinder
,加载了谁就使用谁的实现以及配置,而这是带有不确定性的,这也是常见的一种JAR包冲突问题。所以如果MaxCompute JDBC带入了与宿主应用不同的 slf4j 日志实现,那么可能会造成宿主应用在运行时获取到另一套实现的日志实例,从而让宿主应用原有的日志配置失效。
据不完全归纳,我暂且将MaxCompute JDBC的用户大致分为两类,分别是取数者和开发者。
-
取数者
这类用户通常是某类数据查询或分析工具的使用者,他们需要在取数工具中添加MaxCompute的JDBC Driver,然后配置jdbc url等若干参数,从而利用该工具完成对MaxCompute的SQL查询。 -
开发者
这类用户通常是数据产品的开发者,他们通过调用SDK或JDBC的API来编写查询逻辑的相关代码,从而开发出适合目标用户使用的软件产品。
那么现在我将针对这两类用户分别讲讲如何完成自定义日志配置,然后再针对具体的配置做一下说明。如果你对MaxCompute JDBC还不熟悉,那么请在阅读以下内容前先看看这篇《使用 odps-jdbc 接入 ODPS,不再从零开始》。
- 取数者
我打算继续以SQL Workbench/J作为例子进行讲解。
将 SQL Workbench/J 下载到本地后,你会得到一个可执行文件,双击后在弹出界面中点击 Manager Driver 加载我们的 JDBC jar 包。
此处请注意,由于 SQL Workbench/J 自身没有引入 slf4j 的任何日志实现,所以我们可以通过添加 logback 的jar包来启用MaxCompute JDBC默认的logback实现。
然后新建一个 Connection Profile 。请注意在 URL 部分填入的JDBC URL中较之前多了一个名为 log_conf_file的参数:
该参数可用于指定一个本地的 logback 配置文件作为用于MaxCompute JDBC的日志配置。请注意,该参数是一个指定配置文件的参数,而非指定日志输出位置的参数。所以请确保该参数对应的目录下该文件是存在且应用有权限访问的,并且配置文件需要符合logback配置文件的格式。如果不传递该参数,日志将只输出到控制台。而以之前双击方式打开的 SQL Workbench/J 是没有控制台界面的。如果需要将日志输出到特定目录的日志文件中,只需在配置文件中为其添加相应的FileAppender即可。详见第3部分的配置说明。
这一段中提到的JAR包和配置文件请于此处下载:v2.0-beta
- 开发者
对于开发者而言,应该对Java的日志框架体系并不陌生,且所开发的应用可能已经使用了 slf4j 及其实现。所以首先需要明确这一点,如果已经使用了 slf4j 及其实现,那么就没有必要再使用JDBC自带的配置功能,可以直接在现有应用的log配置中添加对包com.aliyun.odps.jdbc
的日志配置。
如果你没有使用任何的 slf4j 实现,那么请在添加MaxCompute JDBC的JAR包的同时,同时向你的classpath添加logback-core-1.1.7.jar和logback-classic-1.1.7.jar两个JAR包。如果你使用的是maven,可以添加如下依赖:
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.7</version>
</dependency>
你可以采用两种方式来指定配置文件,一种是直接将logback.xml放入classpath之中。
还有一种方式是以编程的方式,在如下的config中加入了 log_conf_file 属性的配置或是在jdbc URL串中带上 log_conf_file 参数来指定(见之前SQL Workbench/J的示例图)。当你同时在URL和config中配置该项时,以config的值作为优先项使用。
Properties config = new Properties();
config.put("access_id", "...");
config.put("access_key", "...");
config.put("project_name", "...");
config.put("charset", "...");
config.put("log_conf_file","/Users/emerson/logback.xml");
Connection conn = DriverManager.getConnection("jdbc:odps:<endpoint>", config);
- 配置说明
接着我们来看看 logback.xml 的内容:
<configuration>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<!--*Linux or Windows:*-->
<!--tries $HOME first; if it doesn't exist, it tries $USERPROFILE-->
<file>${HOME:-${USERPROFILE}}/logs/odps.log</file>
<encoder>
<pattern>%date %level [%thread] %logger{10} %X{connectionId} [%file:%line] %msg%n</pattern>
</encoder>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<logger name="com.aliyun.odps.jdbc" level="debug"/>
<root level="error">
<appender-ref ref="FILE" />
<appender-ref ref="STDOUT" />
</root>
</configuration>
这是一个满足基本常规需求的logback配置,如果你没有更多的特殊需求,可以直接复制该配置文件来使用。我将简单对该配置做一下介绍,如果你对logback的配置想要有更深入的了解,请查看官方文档的configuration章节。
其中<configuration>
是配置文件的根标签,所有子标签必须包含在根标签之中。其中子标签分为<appender>
、<encoder>
、<logger>
和 <root>
。
Logback 的日志实例是有继承关系的。其中 <root>
就是所有日志实例的父实例,而其余 <logger>
都是子实例,默认都继承父实例的配置。而所有的 <logger>
又根据其 name
属性的前缀来继续维持继承关系,假设这段配置中定义了 com.aliyun.odps
和 com.aliyun.odps.jdbc
两个日志实例,那后者就是前者的子实例。由于我们目前只有 com.aliyun.odps.jdbc
这个包中有输出日志的代码,所以我们只配置了一个日志实例,它覆盖了 <root>
的 error
日志级别,改为了 debug
级别。
其中日志的输出级别及其严重等级关系为:
TRACE < DEBUG < INFO < WARN < ERROR < OFF
即从左往右的严重等级依次递增,日志数量则依次递减。严重等级越小的日志级别将输出所有大于等于它级别的日志。举个例子,如果你选择DEBUG
级别,那除TRACE
之外的所有日志都将输出。其中OFF
表示不输出日志。
其中的 <encoder>
用于将日志事件编码成字节流,它引用了 <pattern>
,<pattern>
表明使用默认的PatternLayout
,其中 <pattern>%date %level [%thread] %logger{10} %X{connectionId} [%file:%line] %msg%n</pattern>
中引用了很多 PatternLayout
中内置的一些变量,用于分别输出日志的日期、级别、线程名、日志实例名、connectionId、类文件、行号、msg信息以及行分隔符。其中更多的内置变量请参阅官方的layouts文档。
这些变量中唯一需要你特别关注的是 %X{connectionId}
,这是一个通过 MDC 设置的每个JDBC Connection唯一的ID,用于标识来自于同一Connection的JDBC日志。
而<appender>
用于指定日志的输出目标,目前的示例中配置了 FILE
和 STDOUT
两个appender,表明分别将日志输出到文件和标准输出流,其中FILE
的appender中又通过 <file>
指明了日志文件的存储路径。
最后我们可以看到日志实例会引用appender,而appender又会引用encoder,从而构造了整个日志配置的体系。
通过以上配置,我们可以在路径/Users/emerson/odps.log下(也就是你的home目录下,上述配置示例中针对Linux和windows的home目录做了兼容)得到格式如下的输出日志:
Logback 是一款相当强大的Java日志框架,你可以通过配置来获取像日志文件rotation、归档,甚至以JMS、邮件方式输出日志等功能。更多的功能请查阅官方文档来获取进一步了解。
欢迎加入MaxCompute钉钉群讨论