#scala #exception #logging
#scala #исключение #протоколирование
Вопрос:
Scala Try
очень полезен.
Я бы хотел использовать этот шаблон, но регистрировать все исключения.
Как я могу это сделать?
Комментарии:
1. Я вернусь и напишу официальный ответ позже. Однако вот сообщение в блоге, которое я только что закончил рассматривать только эту проблему (и другие связанные с ней): fromjavatoscala.blogspot.com/2016/09 /…
Ответ №1:
Определите следующего помощника:
import scala.util.{Try, Failure}
def LogTry[A](computation: => A): Try[A] = {
Try(computation) recoverWith {
case e: Throwable =>
log(e)
Failure(e)
}
}
Затем вы можете использовать его так, как вы бы использовали Try
, но любое исключение будет зарегистрировано log(e)
.
Комментарии:
1. Попробую. Кстати, для сопоставления попробуйте исходный код github.com/scala/scala/blob/v2.11.1/src/library/scala/util /… , разве он не должен улавливать только несмертельные исключения?
2. @SRobertJames — Нет, потому что он уже поймал только несмертельные исключения, так что есть только те, от которых нужно оправиться. Если вы не имеете в виду: могу ли я регистрировать все исключения из Try, включая те, которые он не может перехватить ? Тогда ответ будет отрицательным; вы должны вставить свой собственный пользовательский код ведения журнала.
3. Это решение не будет выдавать протоколирование, если
Throwable
есть! Нефатальный.
Ответ №2:
Начиная Scala 2.13
, операция цепочки U):A» rel=»noreferrer»> tap
может использоваться для применения побочного эффекта (в данном случае некоторого протоколирования) к любому значению при возврате исходного значения:
import util.chaining._
val x = Try("aa".toInt).tap(_.failed.foreach(println))
// java.lang.NumberFormatException: For input string: "aa"
// x: Try[Int] = Failure(java.lang.NumberFormatException: For input string: "aa")
Или эквивалентная версия сопоставления с шаблоном:
val x = Try("aa".toInt).tap { case Failure(e) => println(e) case _ => }
// java.lang.NumberFormatException: For input string: "aa"
// x: Try[Int] = Failure(java.lang.NumberFormatException: For input string: "aa")
Операция U):A» rel=»noreferrer»> tap
цепочки применяет побочный эффект (в данном случае println
или некоторое протоколирование) к значению (в данном случае a Try
), возвращая исходное неизмененное значение, к которому tap
применяется (the Try
):
def tap[U](f: (A) => U): A
Ответ №3:
Вы можете настроить его еще больше, используя неявный класс
def someMethod[A](f: => A): Try[A] = Try(f)
implicit class LogTry[A](res: Try[A]) {
def log() = res match {
case Success(s) => println("Success :) " s); res
case Failure(f) => println("Failure :( " f); res
}
}
Теперь вы можете вызывать someMethod
и вызывать его результат log
следующим образом:
scala> someMethod(1/0).log
Failure :( java.lang.ArithmeticException: / by zero
и
scala> someMethod(1).log
Success :) 1
Конечно println
, метод внутри неявного класса можно заменить любым протоколированием, которое вы хотите.
Комментарии:
1. Вы, вероятно, не хотите, чтобы параметр типа
A
был включенlog
, поскольку он затеняетA
LogTry
.2. Вы также можете просто вернуть
res
вместо возвратаSuccess(s)
иFailure(f)
, которые создают новые экземпляры.
Ответ №4:
Вы использовали термин «исключения», который является неоднозначным. (java.lang.)Throwable
является корнем всего, что может быть помещено за throw
термином. java.lang.Exception
является одним из двух потомков Throwable
(другое существо java.lang.Error
). Кроме того, это делает это неоднозначным java.lang.RuntimeException
, потомок Exception
, который, вероятно, является тем местом, где вы в основном хотите потратить время на ведение журнала (если вы не используете более низкоуровневую платформу приложений или аппаратные реализации драйверов).
Предполагая, что вы хотите регистрировать буквально ВСЕ экземпляры Throwable, тогда вам понадобится что-то вроде этого (НЕ РЕКОМЕНДУЕТСЯ):
def logAtThrowable(f: => A): Try[A] =
try
Try(f) match {
case failure @ Failure(throwable) =>
log(s"Failure: {throwable.getMessage}")
failure
case success @ _ =>
//uncomment out the next line if you want to also log Success-es
//log(s"Success: {throwable.getMessage}")
success
}
catch throwable: Throwable => {
//!NonFatal pathway
log(s"Failure: {throwable.getMessage}")
throw throwable
}
Внешний try/catch
требуется для захвата всех Throwable
экземпляров, которые отфильтровываются scala.util.control.NonFatal
внутри блока Try
‘s try
/ catch
.
Тем не менее … существует правило Java / JVM: вы никогда не должны определять предложение catch при разрешении Throwable (опять же, если вы не используете низкоуровневую платформу приложений или аппаратные реализации драйверов).
Следуя замыслу этого правила, вам нужно будет сузить круг Throwable
ведения журнала только для того, чтобы он выполнялся на более мелкозернистом уровне, скажем, что-то более утонченное, например java.lang.RuntimeException
. Если это так, код будет выглядеть следующим образом (рекомендуется):
def logAtRuntimeException(f: => A): Try[A] =
Try(f) match {
case failure @ Failure(throwable) =>
throwable match {
case runtimeException: RuntimeException =>
log(s"Failure: {runtimeException.getMessage}")
}
failure
case success @ _ =>
success
}
В обоих приведенных выше фрагментах кода вы заметите, что я использовал match
вместо .recoverWith
. Это делается для облегчения простого добавления re throw
, который работает. Оказывается, что все методы сами по Try
себе также обернуты try
/ catch
blocks . Это означает, что если вы хотите зарегистрировать Throwable
, а затем повторно throw
, если вы используете один из Try
методов, подобных recoverWith
, re throw
немедленно перехвачен и помещен в a Failure
, тем самым полностью подрывая значение преднамеренного re throw
. При использовании match
re throw
гарантируется успех, поскольку он остается вне любого из Try
методов.
Если вы хотите увидеть больше кроличьих нор вокруг этой конкретной области, я создал сообщение в блоге о своем собственном исследовании.