在开发UDF的过程中打印日志有助于用户进行UDF代码调试,本文为您介绍在MaxCompute中如何打印及查看UDF日志。
背景信息
当前MaxCompute的UDF运行在远端的机器上,很多用户代码在本地测试成功以后,提交到分布式的环境下,可能由于数据不同或者考虑不周,导致结果不符合预期,因此用户需要反复修改UDF进行代码调试,此时Log日志的打印就显得尤为重要。
目前MaxCompute提供的日志查看方法是通过在Logview中查看对应Worker的StdOut和StdErr来查看对应日志输出,即用户需要通过System.out.println()
或者System.err.println()
方式来打印。
部分用户会使用第三方依赖(比如Log4j)打印日志,这就需要用户进行一定的配置,重定向到StdOut和StdErr中,如果将日志打印到文件则无法进行查看。但是Log4j大多会在多个三方依赖中被使用,当多个配置同时存在时,可能导致用户无法确定日志最终的输出位置。所以本文为您提供一个简单的方法,即封装System.out和System.err形成一个日志类,来满足日志打印的需求。
如果您需要在开发UDF的过程中打印日志信息,可参考如下开发流程:
封装一个Logger类,并在UDF代码中引入该Logger。如果需要打印DEBUG日志,您可在MyUdf类的setup方法中自定义DEBUG相关的Flag。以便后续执行SQL查询时控制DEBUG日志的打印。
在本地执行测试代码,查看执行结果是否符合预期。
将Logger类和UDF类打入一个JAR包中,将该JAR包作为资源上传至MaxCompute,并注册UDF函数。
在查询数据代码中调用UDF函数,并在Logview中查看StdOut和StdErr中打印的日志信息。
编写代码
封装Logger类。
您可以参照一般Log的功能,封装对应的INFO、WARNING、ERROR及DEBUG信息。此处以封装一个简单的MyLogger类为例,日志打印信息包含当前类名、时间和日志级别等信息。代码示例如下:
import java.io.PrintWriter; import java.io.StringWriter; import java.text.SimpleDateFormat; public class MyLogger { private static final SimpleDateFormat DATEFORMATE = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static MyLogger getLogger(String name) { return new MyLogger(name); } private static boolean debug = false; private String prefix; public static void setDebug(boolean enableDebug) { debug = enableDebug; } public MyLogger(String name) { if (name == null) { this.prefix = ""; } else { this.prefix = name + " - "; } } private String logCurrentTime() { return "[" + dateFormat.format(System.currentTimeMillis()) + "]"; } public void log(String msg) { info(msg); } public void info(String msg) { System.out.println(logCurrentTime() + " " + prefix + msg); } public void debug(String msg) { if (MyLogger.debug) { System.out.println(logCurrentTime() + " [DEBUG] " + prefix + msg); } } public void warn(String msg) { System.err.println(logCurrentTime() + " [WARNING] " + prefix + msg); } public void warn(String msg, Throwable ex) { warn(msg); System.err.println(getStackTrace(ex)); } public void error(String msg) { System.err.println(logCurrentTime() + " [ERROR] " + prefix + msg); } public void error(String msg, Throwable ex) { error(msg); System.err.println(getStackTrace(ex)); } private String getStackTrace(Throwable t) { StringWriter sw = new StringWriter(); t.printStackTrace(new PrintWriter(sw)); return sw.toString(); } }
在UDF的代码中引入已创建的Logger类。
以创建一个MyUdf类为例,其功能为将表中的String类型数据转换为Integer类型,您可以使用如下代码引入MyLogger类。
public static final MyLogger logger = MyLogger.getLogger(MyUdf.class.getName());
完整的代码示例如下:
public class MyUdf extends UDF { public static final MyLogger LOGGER = MyLogger.getLogger(MyUdf.class.getName()); @Override public void setup(ExecutionContext ctx) throws UDFException, IOException { logger.info("setup MyUdf"); } public Integer evaluate(String s) { logger.debug("input s: " + s); int ret = 0; try { ret = Integer.parseInt(s); } catch (Exception e) { logger.error("parseInt error!", e); } return ret; } }
打印DEBUG日志。
说明MaxCompute中处理的数据量一般较大,如果每一条数据都打印,则很快会到达数据打印上限,同时会占据大量的存储空间,此时您可以通过传入Flag的方式自由控制日志的打印。
如果后续需要打印DEBUG日志,您可在MyUdf类的setup方法中自定义DEBUG相关的Flag,并在后续执行SQL查询时使用set语句设置该Flag,以控制本次提交的任务是否打印DEBUG日志。代码示例如下:
setup方法中自定义Flag
@Override public void setup(ExecutionContext ctx) throws UDFException, IOException { logger.info("setup MyUdf"); Properties properties = ctx.getConfigurations(); boolean debug = Boolean.parseBoolean(properties.getProperty("odps.user.properties.mylogger.debug", "false")); MyLogger.setDebug(debug); }
其中odps.user.properties.mylogger.debug为用户自定义的Flag参数。
执行SQL时使用set语句进行设置
set odps.user.properties.mylogger.debug=true;
调试代码
您可以在本地使用以下代码测试上述Logger类和UDF的执行结果是否符合预期,具体如下:
测试代码1:执行结果中只打印INFO信息和报错栈信息,不打印DEBUG日志。
public static void main(String[] args) throws UDFException, IOException { MyUdf myUdf = new MyUdf(); myUdf.setup(new LocalExecutionContext()); myUdf.evaluate(""); myUdf.evaluate("1"); myUdf.evaluate("2"); myUdf.evaluate("3"); }
执行结果:
测试代码2:执行结果中打印DEBUG日志。
说明您可以通过
MyLogger.setDebug(true);
语句开启DEBUG开关,方便对代码进行调试,并且在DEBUG完成以后关闭该开关,避免不必要的日志打印。public static void main(String[] args) throws UDFException, IOException{ MyLogger.setDebug(true); // 打印DEBUG日志 MyUdf MyUdf = new MyUdf(); MyUdf.setup(new LocalExecutionContext()); MyUdf.evaluate(""); MyUdf.evaluate("1"); MyUdf.evaluate("2"); MyUdf.evaluate("3"); }
执行结果:
打包注册
本地测试成功后,您可以将MyLogger类和MyUdf类打入一个JAR包中,然后提交至MaxCompute并进行函数注册,以便后续调用该函数。具体如下:
在MaxCompute中运行
您可以在MaxCompute中调用上述注册的stringToInt函数,并通过Logview查看打印的日志信息。
准备表数据
您需要提前准备好表数据,以供后续执行SQL查询时调用UDF。建表语句如下:
create table string_table(s string);
insert overwrite table string_table values (''), ('1'), ('2'), ('3');
通用场景(不打印DEBUG日志)
调用UDF。将string_table表的数据转换成对应的Integer类型,SQL语句如下:
set odps.sql.type.system.odps2=true; select stringToInt(s) from string_table;
运行结果:
+------+ | _c0 | +------+ | 0 | | 1 | | 2 | | 3 | +------+
在Logview中查看StdOut和StdErr,分别打印出了INFO信息和报错栈信息。
打印DEBUG日志
调用UDF。将string_table表的数据转换成对应的Integer类型,SQL语句如下:
set odps.sql.type.system.odps2=true; set odps.user.properties.mylogger.debug=true; select stringToInt(s) from string_table;
运行结果:
+------+ | _c0 | +------+ | 0 | | 1 | | 2 | | 3 | +------+
在Logview中查看StdOut和StdErr,可以看到StdOut中打印出了INFO信息和DEBUG信息,StdErr中打印出了报错栈信息。
说明通过该方法控制DEBUG信息的打印,DEBUG开关只在代码调试时打开,而在调试完成后不设置对应的Flag,即可实现本次任务的日志打印。