scala logging

cómo mantener el valor de retorno al iniciar sesión en Scala



logging (5)

Al programar en java, siempre registro el parámetro de entrada y el valor de retorno de un método, pero en Scala, la última línea de un método es el valor de retorno Así que tengo que hacer algo como:

def myFunc() = { val rs = calcSomeResult() logger.info("result is:" + rs) rs }

Para hacerlo más fácil, escribo una utilidad:

class LogUtil(val f: (String) => Unit) { def logWithValue[T](msg: String, value: T): T = { f(msg); value } } object LogUtil { def withValue[T](f: String => Unit): ((String, T) => T) = new LogUtil(f).logWithValue _ }

Luego lo usé como:

val rs = calcSomeResult() withValue(logger.info)("result is:" + rs, rs)

registrará el valor y lo devolverá. Funciona para mí, pero parece extraño. Como soy un viejo programador de Java, pero nuevo en Scala, no sé si hay una forma más idiomática de hacerlo en Scala.

gracias por su ayuda, ahora creo una mejor utilidad usando el combinador Kestrel mencionado por romusz

object LogUtil { def kestrel[A](x: A)(f: A => Unit): A = { f(x); x } def logV[A](f: String => Unit)(s: String, x: A) = kestrel(x) { y => f(s + ": " + y)} }

Agrego el parámetro f para poder pasarle un registrador desde slf4j, y el caso de prueba es:

class LogUtilSpec extends FlatSpec with ShouldMatchers { val logger = LoggerFactory.getLogger(this.getClass()) import LogUtil._ "LogUtil" should "print log info and keep the value, and the calc for value should only be called once" in { def calcValue = { println("calcValue"); 100 } // to confirm it''s called only once val v = logV(logger.info)("result is", calcValue) v should be === 100 } }


Compilando todas las respuestas, pros y contras, se me ocurrió esto (el contexto es una aplicación Play):

import play.api.LoggerLike object LogUtils { implicit class LogAny2[T](val value : T) extends AnyVal { def @@(str : String)(implicit logger : LoggerLike) : T = { logger.debug(str); value } def @@(f : T => String)(implicit logger : LoggerLike) : T = { logger.debug(f(value)) value } }

Como puede ver, LogAny es un AnyVal, por lo que no debería haber ninguna sobrecarga en la creación de nuevos objetos.

Puedes usarlo así:

scala> import utils.LogUtils._ scala> val a = 5 scala> val b = 7 scala> implicit val logger = play.api.Logger scala> val c = a + b @@ { c => s"result of $a + $b = $c" } c: Int = 12

O si no necesita una referencia al resultado, simplemente use:

scala> val c = a + b @@ "Finished this very complex calculation" c: Int = 12

¿Alguna desventaja de esta implementación?

Editar:

He hecho esto disponible con algunas mejoras en una esencia aquí


Digamos que ya tienes una clase base para todos tus registradores:

abstract class Logger { def info(msg:String):Unit }

Entonces podrías extender String con el método @@ logging:

object ExpressionLog { // default logger implicit val logger = new Logger { def info(s:String) {println(s)} } // adding @@ method to all String objects implicit def stringToLog (msg: String) (implicit logger: Logger) = new { def @@ [T] (exp: T) = { logger.info(msg + " = " + exp) exp } } }

Para usar el registro, tendría que importar miembros del objeto ExpressionLog y luego podría registrar expresiones fácilmente usando la siguiente notación:

import ExpressionLog._ def sum (a:Int, b:Int) = "sum result" @@ (a+b) val c = sum("a" @@ 1, "b" @@2)

Se imprimirá:

a = 1 b = 2 sum result = 3

Esto funciona porque cada vez que llama a un método @@ en un compilador String da cuenta de que String no tiene el método y lo convierte silenciosamente en un objeto con un tipo anónimo que tiene el método @@ definido (consulte stringToLog ). Como parte del compilador de conversión elige el registrador deseado como un parámetro implícito , de esta manera no tiene que seguir pasando el registrador a @@ cada vez que mantiene el control total sobre qué registrador debe usarse cada vez.

En cuanto a la prioridad, cuando se usa el método @@ en la notación de infijo, tiene la prioridad más alta, lo que hace que sea más fácil razonar sobre lo que se registrará.

Entonces, ¿qué sucede si desea utilizar un registrador diferente en uno de sus métodos? Esto es muy simple:

import ExpressionLog.{logger=>_,_} // import everything but default logger // define specific local logger // this can be as simple as: implicit val logger = new MyLogger implicit val logger = new Logger { var lineno = 1 def info(s:String) { println("%03d".format(lineno) + ": " + s) lineno+=1 } } // start logging def sum (a:Int, b:Int) = a+b val c = "sum result" @@ sum("a" @@ 1, "b" @@2)

Saldrá:

001: a = 1 002: b = 2 003: sum result = 3


Lo que estás buscando se llama Kestrel combinator (K combinator): Kxy = x . Puede realizar todo tipo de operaciones de efectos secundarios (no solo el registro) mientras le devuelve el valor pasado. Lea https://github.com/raganwald/homoiconic/blob/master/2008-10-29/kestrel.markdown#readme

En Scala la forma más sencilla de implementarlo es:

def kestrel[A](x: A)(f: A => Unit): A = { f(x); x }

Luego puede definir su función de impresión / registro como:

def logging[A](x: A) = kestrel(x)(println) def logging[A](s: String, x: A) = kestrel(x){ y => println(s + ": " + y) }

Y utilízalo como:

logging(1 + 2) + logging(3 + 4)

su función de ejemplo se convierte en una sola línea:

def myFunc() = logging("result is", calcSomeResult())

Si prefiere la notación OO, puede usar implícitos como se muestra en otras respuestas, pero el problema con este enfoque es que creará un nuevo objeto cada vez que quiera registrar algo, lo que puede causar una degradación del rendimiento si lo hace con la frecuencia suficiente. Pero para completar, se ve así:

implicit def anyToLogging[A](a: A) = new { def log = logging(a) def log(msg: String) = logging(msg, a) }

Utilízalo como:

def myFunc() = calcSomeResult().log("result is")


Si te gusta un enfoque más genérico mejor, podrías definir

implicit def idToSideEffect[A](a: A) = new { def withSideEffect(fun: A => Unit): A = { fun(a); a } def |!>(fun: A => Unit): A = withSideEffect(fun) // forward pipe-like def tap(fun: A => Unit): A = withSideEffect(fun) // public demand & ruby standard }

y usalo como

calcSomeResult() |!> { rs => logger.info("result is:" + rs) } calcSomeResult() tap println


Usted tiene la idea básica correcta: solo necesita ordenarla un poco para que sea lo más conveniente posible.

class GenericLogger[A](a: A) { def log(logger: String => Unit)(str: A => String): A = { logger(str(a)); a } } implicit def anything_can_log[A](a: A) = new GenericLogger(a)

Ahora usted puede

scala> (47+92).log(println)("The answer is " + _) The answer is 139 res0: Int = 139

De esta manera, no es necesario que se repita (por ejemplo, no dos veces).