Azkaban作为LinkedIn开源的任务流式管理工具,在工作中很大程度上被用到。但是,由于非国人开发,对中文的支持性很不好。大多数情况下,会出现几种乱码现象: - 执行内置脚本生成log乱码 - 直接command执行中文乱码 - 中文包名乱码等,其中对日常使用影响最大的就是日志乱码问题。不管是调度Hive、DataX还是Java程序,只要日志抛出来中文,中文都是乱码显示,将日志文件拷贝出来查看文件格式为GB2312,但是Linux系统编码和Azkaban日志编码明明设置的都是UTF-8,很疑惑,摸索许久,决定从源码入手开始层层解惑。

  文中大部分内容从源码一步步进入解析,有经验的朋友可以跳至文末见具体解决方法。

  根据页面获取日志的接口可以知道方法在 azkaban-web-server项目下package azkaban.webapp.servlet 下的方法handleAJAXAction,如下图 请求参数是fetchExecJobLogs

   对应的处理方法为  ajaxFetchJobLogs(req, resp, ret, session.getUser(), exFlow)和ajaxFetchExecFlowLogs(req, resp, ret, session.getUser(), exFlow)

  进入该方法后可以发现返回的data为经过 StringEscapeUtils.escapeHtml格式化过的,这就是引发乱码的原因之一。

  改用commons-lang3下的方法可以解决这个问题,增加如下依赖后更新gradle项目,将此处StringEscapeUtils.escapeHtml(data.getData())更改为 org.apache.commons.lang3.StringEscapeUtils.escapeHtml3(data.getData())

  // https://mvnrepository.com/artifact/org.apache.commons/commons-lang3
  compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.4'

  修改后为

  仔细研读代码可以发现其实这样还不够,读取日志的时候会先判断该任务是否正在执行,如果在执行的话就直接在服务器上暂存日志路径读取,否则是话就在mysql表读取,具体代码如下

  一.本地文件读取的话是走http请求的,对应的为azkaban-exec-server下的包package azkaban.execapp下的ExecutorServlet

  进入方法handleFetchLogEvent,一个是FlowLogs 一个是JobLogs ,

  进入这两个方法后发现最终都是调用的FileIOUtils.readUtf8File()这个方法

  读取日志的核心方法就是这个了

  BufferedInputStream读取的是字节byte,因为一个汉字占两个字节,而当中英文混合的时候,有的字符占一个字节,有的字符占两个字节,所以如果直接读字节,而数据比较长,没有一次读完的时候,很可能刚好读到一个汉字的前一个字节,这样,这个中文就成了乱码,后面的数据因为没有字节对齐,也都成了乱码.所以我们需要用BufferedReader来读取,
它读到的是字符,所以不会读到半个字符的情况,不会出现乱码。
  在这里修改方法后为一个新方法如下(编码方式GBK根据生成的日志编码格式设定)
  public static LogData readUtf8FileEncode(final File file, final int fileOffset, final int length)
throws IOException {
final char[] chars = new char[length];
final FileInputStream fileStream = new FileInputStream(file); final long skipped = fileStream.skip(fileOffset);
if (skipped < fileOffset) {
fileStream.close();
return new LogData(fileOffset, 0, "");
} BufferedReader reader = null;
int read = 0;
try {
reader = new BufferedReader(new InputStreamReader(fileStream, "GBK"));
read = reader.read(chars);
} finally {
IOUtils.closeQuietly(reader);
} if (read <= 0) {
return new LogData(fileOffset, 0, "");
}
final byte[] buffer = new String(chars).getBytes();
final Pair<Integer, Integer> utf8Range = getUtf8Range(buffer, 0, read);
final String outputString = new String(buffer, utf8Range.getFirst(), utf8Range.getSecond(),
StandardCharsets.UTF_8); return new LogData(fileOffset + utf8Range.getFirst(), utf8Range.getSecond(), outputString);
}
  在原来引用的地方替换为新的即可,如下图。

  二.对于运行完成的,日志会被写入数据库,而且是经过压缩的,方法为 package azkaban.executor下的ExecutionLogsDao 搜索找到fetchLogs,在方法体中实例化了一个FetchLogsHandler

  在FetchLogsHandler中核心方法为handle,在这个方法中可以发现这块会有解压缩的过程,开始怀疑是不是这块出现问题,测试后否定了这一猜想。

  这里有解压,那在入库之前肯定会有压缩过程,再找到入库的方法(如何触发将本地文件写入到数据库在这里不讨论)uploadLogFile(ExecutionLogsDao方法中)

  进入方法体后可以发现和之前类似,也是采用BufferedInputStream读取文件

  修改后的方法为如下(编码方式GBK根据生成的日志编码格式设定)

    private void uploadLogFileEncode(final DatabaseTransOperator transOperator, final int execId, final String name,
final int attempt, final File[] files, final EncodingType encType) throws SQLException {
// 50K buffer... if logs are greater than this, we chunk.
// However, we better prevent large log files from being uploaded somehow
final char[] buffer = new char[50 * 1024];
int pos = 0;
int length = buffer.length;
int startByte = 0;
try {
for (int i = 0; i < files.length; ++i) {
final File file = files[i];
// BufferedInputStream读取的是字节byte,因为一个汉字占两个字节,而当中英文混合的时候,有的字符占一个字节,
// 有的字符占两个字节,所以如果直接读字节,而数据比较长,没有一次读完的时候,很可能刚好读到一个汉字的前一个字节,
// 这样,这个中文就成了乱码,后面的数据因为没有字节对齐,也都成了乱码.所以我们需要用BufferedReader来读取,
// 它读到的是字符,所以不会读到半个字符的情况,不会出现乱码
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), "GBK"));
try {
int size = reader.read(buffer, pos, length);
while (size >= 0) {
if (pos + size == buffer.length) {
// Flush here.
uploadLogPart(transOperator, execId, name, attempt, startByte, startByte + buffer.length,
encType, buffer, buffer.length); pos = 0;
length = buffer.length;
startByte += buffer.length;
} else {
// Usually end of file.
pos += size;
length = buffer.length - pos;
}
size = reader.read(buffer, pos, length);
}
} finally {
IOUtils.closeQuietly(reader);
}
} // Final commit of buffer.
if (pos > 0) {
uploadLogPart(transOperator, execId, name, attempt, startByte, startByte + pos, encType, buffer, pos);
}
} catch (final SQLException e) {
logger.error("Error writing log part.", e);
throw new SQLException("Error writing log part", e);
} catch (final IOException e) {
logger.error("Error chunking.", e);
throw new SQLException("Error chunking", e);
}
} private void uploadLogPart(final DatabaseTransOperator transOperator, final int execId, final String name,
final int attempt, final int startByte, final int endByte, final EncodingType encType, final char[] buffer,
final int length) throws SQLException, IOException {
final String INSERT_EXECUTION_LOGS = "INSERT INTO execution_logs "
+ "(exec_id, name, attempt, enc_type, start_byte, end_byte, "
+ "log, upload_time) VALUES (?,?,?,?,?,?,?,?)"; byte[] buf = new String(buffer).getBytes();
if (encType == EncodingType.GZIP) {
buf = GZIPUtils.gzipBytes(buf, 0, buf.length);
} else if (length < buffer.length) {
buf = new String(buffer, 0, length).getBytes();
} transOperator.update(INSERT_EXECUTION_LOGS, execId, name, attempt, encType.getNumVal(), startByte,
startByte + length, buf, DateTime.now().getMillis());

  以上代码直接放在ExecutionLogsDao内即可,修改引用的地方

   至此日志显示中文乱码问题即可解决。以上为代码分析过程,操作过程可直接总结如下:

1.添加commons-lang3依赖

// https://mvnrepository.com/artifact/org.apache.commons/commons-lang3
compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.4'

2.azkaban-web-server -->package azkaban.webapp.servlet-->ExecutorServlet-->将方法ajaxFetchExecFlowLogs和ajaxFetchJobLogs中的StringEscapeUtils.escapeHtml替换为org.apache.commons.lang3.StringEscapeUtils.escapeHtml3

3.azkaban-common-->package azkaban.utils-->FileIOUtils-->增加方法readUtf8FileEncode (文中第一段代码)

4.azkaban-common-->package azkaban.execapp-->FlowRunnerManager-->分别将readFlowLogs和readJobLogs中readUtf8File替换为readUtf8FileEncode

5.azkaban-common-->package azkaban.executor-->ExecutionLogsDao-->增加方法uploadLogFileEncode和uploadLogPart(重载)(文中第二段代码),将uploadLogFile(在70行左右的这个方法public void uploadLogFile(final int execId, final String name, final int attempt, final File... files))中的uploadLogFile替换为uploadLogFileEncode

  代码修改就是这些,我们可以发现代码中解析文件流都是采用UTF-8,如果生成的日志是其他编码就会出现乱码的情况,所以需要在读取文件的地方指定日志文件编码(还有一种思路就是Log4j写日志时候就指定编码,尝试没有成功就采用了现在的方式),来张前后对比图(对于已经写入数据库的日志,中文乱码问题无法解决,因为写入数据库前读取文件的方法不支持中文,修改代码之后入数据库的日志可以支持中文,另外代码中的GBK可以根据日志编码更换)

Azkaban日志中文乱码问题解决的更多相关文章

  1. WingIDE中文乱码问题解决方法

    WingIDE中文乱码问题解决方法 安装完WingIDE后,首次运行python脚本时,若脚本中含有UTF-8中文,在Debug I/O输出框中,全部变成了乱码. 这时其实我们设置下WingIDE的编 ...

  2. ubuntu mysql emma中文乱码问题解决

    ubuntu mysql emma中文乱码问题解决 emma默认用apt-get 安装的话,emma是不支持中文的,配置文件或直接修改emma程序源文件(python). apt-get安装emma ...

  3. Spring MVC3返回JSON数据中文乱码问题解决(转)

    Spring MVC3返回JSON数据中文乱码问题解决 查了下网上的一些资料,感觉比较复杂,这里,我这几使用两种很简单的办法解决了中文乱码问题. Spring版本:3.2.2.RELEASE Jack ...

  4. Ubuntu下Eclipse中文乱码问题解决(转)

    Ubuntu下Eclipse中文乱码问题解决 把Windows下的工程导入到了Linux下Eclipse中,由于以前的工程代码,都是GBK编码的(Windows下的Eclipse 默认会去读取系统的编 ...

  5. 【转】asp.net Cookie值中文乱码问题解决方法

    来源:脚本之家.百度空间.网易博客 http://www.jb51.net/article/34055.htm http://hi.baidu.com/honfei http://tianminqia ...

  6. soapUI参数中文乱码问题解决方法 (groovy脚本中文乱码)

    soapUI参数中文乱码问题解决方法 可能方案1: 字体不支持中文,将字体修改即可: file-preferences-editor settings-select font 修改字体,改成能显示中文 ...

  7. oracle中文乱码问题解决

    中文乱码问题解决:1.查看服务器端编码select userenv('language') from dual;我实际查到的结果为:AMERICAN_AMERICA.ZHS16GBK2.执行语句 se ...

  8. 【其他】【navicat】【1】navicat导入txt文件中文乱码问题解决

    正文: TXT文件默认编码为ANSI,另存为编码为UTF-8的文本文件即可 备注: 1,一般需要导入的数据都是一张excel表,需要将excel表另存为“文本文件(制表符分隔)(*.txt)”保存类型 ...

  9. LoadRunner中文乱码问题解决方法

    LoadRunner中文乱码问题解决方法 前段时间在录制,增强,整合LoadRunner脚本,期间两次遇到了中文乱码问题.在此记录一下中文乱码问题的解决办法. 一.录制回放中文乱码 我录制登陆的脚本, ...

随机推荐

  1. Visual Studio Shortcuts

    https://docs.google.com/file/d/0Bw8aEjCQGEquMjRaWFBKUUtuRE0/edit

  2. flex弹性盒子的使用

    前言:这是笔者学习之后自己的理解与整理.如果有错误或者疑问的地方,请大家指正,我会持续更新! CSS3引入了一种新的布局模型—— flex 布局.flex是 flexible box 的缩写,一般称之 ...

  3. kaldi通用底层矩阵运算库——CUDA

    cudamatrix/cublas-wrappers.h 该头文件对cuBLAS的接口进行了简单的封装(函数名的简化和部分kaldi函数的封装). 比如 cublasSgemm_v2封装为cublas ...

  4. BH1750FVI调试

    在写此博客之前已经看了几遍数据手册了,现在已经调试成功了,可以读出来数据,还有不如意的地方,写此博客整理下思路. 1.BH1750fvi介绍. 这是一个16bit的数字传感器,使用I2C作为通信接口, ...

  5. re模块 - 正则表达式 疏理(一)

    在网上总是很难找到令自己比较满意的,关于正则表达式的文章.所以决定自己来总结一波,并配上相应的示例. 正则表达式:定义了规则,用来字符串处理. 用途: 1.匹配 - 符合规则的字符串,则认为匹配了. ...

  6. Linux配置环境变量

    自己mark一下 gedit ~/.bashrc 后面记得要 source ~/.bashrc 使之马上生效(其中波浪线 ~ 代表用户主目录,即home/XX,XX是用户的用户名) Linux下配置环 ...

  7. 06mycat使用haproxy进行负载均衡

    集群的服务器列表 在10.11.0.210和10.11.0.216中部署mycat和haproxy(因为实验机器性能有限,实际生产环境中需要单独用服务做haproxy反向代理) 两台机器的Mycat配 ...

  8. layUI 实现自定义弹窗

    需求描述:点击表格中的数据,弹出一张具体信息表.描述的不是很清楚,放效果图,就明白了,上图 放心,能看到的数据,都不是生产数据,我造的假数据,但是功能效果就是这样,点击列表中的一行,弹出某些要展示的信 ...

  9. 【java】java基本用法记录

    java用法总结 计时 long startTime = System.nanoTime(); solution.process(inputFile); long endTime = System.n ...

  10. 网络流24题——数字梯形问题 luogu 4013

    题目描述:这里 极其裸的一道费用流问题 首先分析第一问,由于要求一个点只能经过一次,所以我们将梯形中的每一个点拆成两个点(记为入点和出点,顾名思义,入点用来承接上一行向这一行的边,出点用来向下一行连边 ...