如何使用 JDBC 为异常创建日志?
How can I make a Log for exceptions using JDBC?
我目前正在处理一个数据库,需要记录我们客户的邮政编码,但是我在开发登录 SQL 时遇到了一些麻烦,我希望能够选择例外情况由 Java 发送到 SQL 上的“例外”table。
欢迎任何形式的帮助。
这个问题比乍一看要复杂得多。
您的第一个问题是如何首先访问这些异常。
不幸的是,几乎每个教程(还有很多,很多 stack overflow answers。我尽我最大的努力,但不能抓住所有的滥用)有大量的这个非常糟糕的代码:
try {
some stuff;
} catch (Exception e) {
e.printStackTrace();
}
如果你的代码中有这个,你想要的是不可能的,期间。
因此第 1 步是在您的源代码中搜索此模式并将其删除。正确的解决方案是,按优先顺序排列:
最好的解决方案是所有抛出(已检查)它们无法处理的异常的方法向前抛出它们。您的 public String readFile()
方法不应该 try/catch 那个 IOException,它应该被声明为 public String readFile() throws IOException
。一个名为 'readFile' 的方法应该抛出它,如果没有,你就搞砸了。
一个遥远的次佳解决方案是包装异常。如果异常类型是一个实现细节而不是方法 name/types/documentation 中固有的,这将成为最佳解决方案。名为 readFile
的方法显然与文件交互(如果不这样做,它的命名会很糟糕!),并且与文件的任何交互自然会发出 IOException。但是,如果该方法被命名为 readConfig
,它就更加模糊了,作为 API 设计者,它应该放在哪里取决于您(它可能应该,但是可以争论应该抛出 ConfigException 而不是 IOException ).这看起来像:
try {
some stuff;
} catch (IOException e) {
throw new RuntimeException("Unhandled", e);
}
注意格式!它必须是这种形式(你将异常传递给你抛出的新异常的构造函数),否则你想要的是不可能的。
- 更远的是你记录异常和return一些东西:
try {
some stuff;
} catch (IOException e) {
log.warn("Can't read config file {}", configPath, e);
}
同样,您必须将异常传递给日志系统,否则您想要的是不可能的。
java 中的日志系统分为 'front' 和 'back'。前面是你记录东西的方式(所以这里的 log.warn
方法)。 'back' 是这些日志所在的位置。如果你想让这些东西进入数据库,弄清楚你正在使用什么日志记录 'front'(如果你还没有使用任何东西,我建议 Flogger 因为它是最好的 API,或者 Slf4j因为它是使用最广泛的。您的选择)。阅读有关如何设置您自己的自定义记录器的文档,以便您可以在所有这些日志中编写代码 'arrive',然后您可以编写代码将它们转换为数据库行插入。
- 然而,并不是所有的异常都像这样被捕获和记录。相反,正确的异常处理会不断向上抛出异常,并最终会被调用入口点的任何代码一路捕获 'at the top'。入口点是您的代码开始 运行 的地方,这取决于您拥有的代码可能是什么:编写网络服务器处理程序?然后入口点是调用您的
doGet()
方法的 Web 框架。编写一个简单的简 java 应用程序?然后入口点是嵌入到 java 中的代码,它最终会调用您的 public static void main()
方法。触发了一个线程?然后入口点是嵌入到 java 的线程 class 中的代码,它最终会调用您的 run()
方法。这取决于框架如何处理异常。通常,框架会将其记录到一些日志记录中,因此请阅读文档并为该框架编写一个日志处理程序,以便您可以看到它们并将它们写入数据库。
对于java自己的东西(Thread的运行和你的main方法),就是线程的未捕获异常处理机制。因此,使用 Thread.setDefaultUncaughtExceptionHandler
注册一个默认的未捕获异常处理程序,这样您就可以编写代码,在任何时候抛出异常 和 冒泡到java 中的一个在入口点 运行ners.
中烘焙
好的,现在怎么办?
因此,您编写了(可能在多个地方)代码,在发生未捕获的异常时 运行s:日志处理程序和默认的未捕获异常处理程序。
异常由类型(例如 IOException)、消息、堆栈跟踪和原因组成。原因本身就是一个例外,也可以有原因。因此你可以有一系列事件:磁盘已满,因此我无法执行 SQL INSERT 语句,因此无法保留所有交易的记录,因此将 10 欧元从乔的余额转移到简的无法执行平衡。
这将显示为一个 BalanceTransferException
实例,其原因是 TransactionLogException
,其原因是 IOException
,其中包含一条消息 'disk full'。
并且此链中的每个异常都有自己的堆栈跟踪,但是,这些堆栈跟踪都倾向于在同一行结束。
(异常甚至还有第 5 个元素,它是一个被抑制的异常列表。但顾名思义,这些很少是有趣的,您可能根本不需要记录这些)。
现在是 2021 年。如果您想将 20MB 的文本推入数据库行,那就去做吧。什么都不是 'too big'.
那么,您想要的是:如何将异常的实例转换为一个或几个字符串,然后准备好将其插入到数据库中? (我想,您可以为各个堆栈跟踪行创建一个单独的 table,但这在很大程度上过度设计了东西)。
这些文本行很大,因此您需要一个支持可变长度字符串的数据库引擎。幸运的是,除了最愚蠢的设计糟糕的数据库之外,所有数据库都使这很容易,所以只需使用 VARCHAR
作为类型(例如,在 postgres 中,字符串存储无论如何都是可变长度的)。如果您使用的是其中一种奇怪的数据库设计,请使用 CLOB 类型。
获取这些字符串:
e.printStackTrace()
是一个合理的解决方案(它做了一些 'nice' 格式化)。请注意,方法名称完全是谎言,它不打印堆栈跟踪,它打印更多(类型、消息、跟踪和因果链)但它只能写入打印流。让我们使用它:
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
exception.printStackTrace(pw);
pw.close();
String blob = sw.toString();
// now save 'blob' in your log db. Probably include timestamp, that sounds useful.
数据库?
我假设您已经知道如何从 java 写入数据库。如果不了解,请查找 JDBI 或 JOOQ 教程。
但我真的很想抓住那些 catch (IOException e) { e.printStackTrace(); }
线!
我说不可能,不是吗?回到那里并修复该代码。您唯一的选择是摆脱 'capturing exceptions' 的概念,转向 'lets just assume this app never sends anything to system.err ever, so if anything appears there, neccessarily those are just stack traces emitted by folks who wrote horrible code'。您可以做什么:System.setErr
可用于更改 System.err
的去向。您必须编写 PrintStream 的自定义实现,它只会缓冲发送给它的所有字符,直到发送换行符,然后将该行写入数据库 table。除非你不厌其烦地解析所有这些并重建异常(可能,但困难),否则这与其说是 'table of exceptions thrown',不如说是 'table of lines that were otherwise heading out to the process error stream'.
那是一个遥远的,遥远的 计划 Z。计划 A 只是编写适当的代码,这涉及从不在 catch 块中编写 e.printStackTrace();
。即使如此多的 SO 答案和教程都在进行这种非常愚蠢的做法。
太多了!
我想有些想法首先要遵循因果链——通常异常远没有原因有趣。因此,继续调用 .getCause()
直到它 return 为 null:
Throwable mostRelevant = exception;
while (true) {
Throwable cause = mostRelevant.getCause();
if (cause == null) break;
mostRelevant = cause;
}
并仅记录该异常的类型、消息和堆栈跟踪,因此这 3 个字符串:
mostRelevant.getClass().getName()
mostRelevant.getMessage()
(可能为空!)
Arrays.toString(mostRelevant.getStackTrace())
如果你想进一步减少,你必须尝试写一些关于堆栈跟踪的哪些行实际上是有趣的智能猜测。但我只是把它全部记录下来。
我目前正在处理一个数据库,需要记录我们客户的邮政编码,但是我在开发登录 SQL 时遇到了一些麻烦,我希望能够选择例外情况由 Java 发送到 SQL 上的“例外”table。
欢迎任何形式的帮助。
这个问题比乍一看要复杂得多。
您的第一个问题是如何首先访问这些异常。
不幸的是,几乎每个教程(还有很多,很多 stack overflow answers。我尽我最大的努力,但不能抓住所有的滥用)有大量的这个非常糟糕的代码:
try {
some stuff;
} catch (Exception e) {
e.printStackTrace();
}
如果你的代码中有这个,你想要的是不可能的,期间。
因此第 1 步是在您的源代码中搜索此模式并将其删除。正确的解决方案是,按优先顺序排列:
最好的解决方案是所有抛出(已检查)它们无法处理的异常的方法向前抛出它们。您的
public String readFile()
方法不应该 try/catch 那个 IOException,它应该被声明为public String readFile() throws IOException
。一个名为 'readFile' 的方法应该抛出它,如果没有,你就搞砸了。一个遥远的次佳解决方案是包装异常。如果异常类型是一个实现细节而不是方法 name/types/documentation 中固有的,这将成为最佳解决方案。名为
readFile
的方法显然与文件交互(如果不这样做,它的命名会很糟糕!),并且与文件的任何交互自然会发出 IOException。但是,如果该方法被命名为readConfig
,它就更加模糊了,作为 API 设计者,它应该放在哪里取决于您(它可能应该,但是可以争论应该抛出 ConfigException 而不是 IOException ).这看起来像:
try {
some stuff;
} catch (IOException e) {
throw new RuntimeException("Unhandled", e);
}
注意格式!它必须是这种形式(你将异常传递给你抛出的新异常的构造函数),否则你想要的是不可能的。
- 更远的是你记录异常和return一些东西:
try {
some stuff;
} catch (IOException e) {
log.warn("Can't read config file {}", configPath, e);
}
同样,您必须将异常传递给日志系统,否则您想要的是不可能的。
java 中的日志系统分为 'front' 和 'back'。前面是你记录东西的方式(所以这里的 log.warn
方法)。 'back' 是这些日志所在的位置。如果你想让这些东西进入数据库,弄清楚你正在使用什么日志记录 'front'(如果你还没有使用任何东西,我建议 Flogger 因为它是最好的 API,或者 Slf4j因为它是使用最广泛的。您的选择)。阅读有关如何设置您自己的自定义记录器的文档,以便您可以在所有这些日志中编写代码 'arrive',然后您可以编写代码将它们转换为数据库行插入。
- 然而,并不是所有的异常都像这样被捕获和记录。相反,正确的异常处理会不断向上抛出异常,并最终会被调用入口点的任何代码一路捕获 'at the top'。入口点是您的代码开始 运行 的地方,这取决于您拥有的代码可能是什么:编写网络服务器处理程序?然后入口点是调用您的
doGet()
方法的 Web 框架。编写一个简单的简 java 应用程序?然后入口点是嵌入到 java 中的代码,它最终会调用您的public static void main()
方法。触发了一个线程?然后入口点是嵌入到 java 的线程 class 中的代码,它最终会调用您的run()
方法。这取决于框架如何处理异常。通常,框架会将其记录到一些日志记录中,因此请阅读文档并为该框架编写一个日志处理程序,以便您可以看到它们并将它们写入数据库。
对于java自己的东西(Thread的运行和你的main方法),就是线程的未捕获异常处理机制。因此,使用 Thread.setDefaultUncaughtExceptionHandler
注册一个默认的未捕获异常处理程序,这样您就可以编写代码,在任何时候抛出异常 和 冒泡到java 中的一个在入口点 运行ners.
好的,现在怎么办?
因此,您编写了(可能在多个地方)代码,在发生未捕获的异常时 运行s:日志处理程序和默认的未捕获异常处理程序。
异常由类型(例如 IOException)、消息、堆栈跟踪和原因组成。原因本身就是一个例外,也可以有原因。因此你可以有一系列事件:磁盘已满,因此我无法执行 SQL INSERT 语句,因此无法保留所有交易的记录,因此将 10 欧元从乔的余额转移到简的无法执行平衡。
这将显示为一个 BalanceTransferException
实例,其原因是 TransactionLogException
,其原因是 IOException
,其中包含一条消息 'disk full'。
并且此链中的每个异常都有自己的堆栈跟踪,但是,这些堆栈跟踪都倾向于在同一行结束。
(异常甚至还有第 5 个元素,它是一个被抑制的异常列表。但顾名思义,这些很少是有趣的,您可能根本不需要记录这些)。
现在是 2021 年。如果您想将 20MB 的文本推入数据库行,那就去做吧。什么都不是 'too big'.
那么,您想要的是:如何将异常的实例转换为一个或几个字符串,然后准备好将其插入到数据库中? (我想,您可以为各个堆栈跟踪行创建一个单独的 table,但这在很大程度上过度设计了东西)。
这些文本行很大,因此您需要一个支持可变长度字符串的数据库引擎。幸运的是,除了最愚蠢的设计糟糕的数据库之外,所有数据库都使这很容易,所以只需使用 VARCHAR
作为类型(例如,在 postgres 中,字符串存储无论如何都是可变长度的)。如果您使用的是其中一种奇怪的数据库设计,请使用 CLOB 类型。
获取这些字符串:
e.printStackTrace()
是一个合理的解决方案(它做了一些 'nice' 格式化)。请注意,方法名称完全是谎言,它不打印堆栈跟踪,它打印更多(类型、消息、跟踪和因果链)但它只能写入打印流。让我们使用它:
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
exception.printStackTrace(pw);
pw.close();
String blob = sw.toString();
// now save 'blob' in your log db. Probably include timestamp, that sounds useful.
数据库?
我假设您已经知道如何从 java 写入数据库。如果不了解,请查找 JDBI 或 JOOQ 教程。
但我真的很想抓住那些 catch (IOException e) { e.printStackTrace(); }
线!
我说不可能,不是吗?回到那里并修复该代码。您唯一的选择是摆脱 'capturing exceptions' 的概念,转向 'lets just assume this app never sends anything to system.err ever, so if anything appears there, neccessarily those are just stack traces emitted by folks who wrote horrible code'。您可以做什么:System.setErr
可用于更改 System.err
的去向。您必须编写 PrintStream 的自定义实现,它只会缓冲发送给它的所有字符,直到发送换行符,然后将该行写入数据库 table。除非你不厌其烦地解析所有这些并重建异常(可能,但困难),否则这与其说是 'table of exceptions thrown',不如说是 'table of lines that were otherwise heading out to the process error stream'.
那是一个遥远的,遥远的 计划 Z。计划 A 只是编写适当的代码,这涉及从不在 catch 块中编写 e.printStackTrace();
。即使如此多的 SO 答案和教程都在进行这种非常愚蠢的做法。
太多了!
我想有些想法首先要遵循因果链——通常异常远没有原因有趣。因此,继续调用 .getCause()
直到它 return 为 null:
Throwable mostRelevant = exception;
while (true) {
Throwable cause = mostRelevant.getCause();
if (cause == null) break;
mostRelevant = cause;
}
并仅记录该异常的类型、消息和堆栈跟踪,因此这 3 个字符串:
mostRelevant.getClass().getName()
mostRelevant.getMessage()
(可能为空!)Arrays.toString(mostRelevant.getStackTrace())
如果你想进一步减少,你必须尝试写一些关于堆栈跟踪的哪些行实际上是有趣的智能猜测。但我只是把它全部记录下来。