javascript - 如何将 log4javascript 与 ScalaJS/PhantomJS 一起使用

标签 javascript scala logging phantomjs scala.js

我尝试使用 ScalaJS 和 PhantomJS 运行 log4javascript 并收到错误,但使用 Rhino 可以正常工作。

我采用了以下 ScalaJS 示例:https://github.com/scala-js/scalajs-tutorial

和 log4javascript 我取自另一个 ScalaJS 示例:https://github.com/ochrons/scalajs-spa-tutorial 更具体地说,这 3 个文件:https://github.com/ochrons/scalajs-spa-tutorial/tree/master/client/src/main/scala/spatutorial/client/logger

我修改了 TutorialApp 以包含一些日志记录:

package tutorial.webapp

import scala.scalajs.js.JSApp

import org.scalajs.jquery.jQuery
import tutorial.logger.LoggerFactory

object TutorialApp extends JSApp {
  println("Before getLogger...")
  val log = LoggerFactory.getLogger(getClass().getName)
  log.info("After getLogger...")

  def main(): Unit = {
    jQuery(setupUI _)
  }

  def setupUI(): Unit = {
    jQuery("""<button type="button">Click me!</button>""")
      .click(addClickedMessage _)
      .appendTo(jQuery("body"))
    jQuery("body").append("<p>Hello World</p>")
  }

  def addClickedMessage(): Unit = {
    jQuery("body").append("<p>You clicked the button!</p>")
    log.info("Button clicked...")
  }
}

修改build.sbt

enablePlugins(ScalaJSPlugin)

name := "Scala.js Tutorial"

scalaVersion := "2.11.7"

libraryDependencies += "org.scala-js" %%% "scalajs-dom" % "0.8.1"
libraryDependencies += "be.doeraene" %%% "scalajs-jquery" % "0.8.0"

jsDependencies += RuntimeDOM

skip in packageJSDependencies := false

// uTest settings
libraryDependencies += "com.lihaoyi" %%% "utest" % "0.3.0" % "test"
testFrameworks += new TestFramework("utest.runner.Framework")

persistLauncher in Compile := true
persistLauncher in Test := false

// Modifications to original tutorial are here:
scalaJSStage in Global := FastOptStage // If NOT commented out: Uses Phantom, if IS commented out: Uses Rhino

libraryDependencies += "org.webjars" % "log4javascript" % "1.4.13"

jsDependencies += "org.webjars" % "log4javascript" % "1.4.13" / "1.4.13/log4javascript.js" 

记录器文件位于 src/main/scala/tutorial/logger 中,仅更改包名称即可编译它们。 修改后的package.scala:

package tutorial

package object logger {
  private val defaultLogger = LoggerFactory.getLogger("Log")

  def log = defaultLogger
}

修改LoggerFactory.scala

package tutorial.logger

import scala.annotation.elidable
import scala.annotation.elidable._

trait Logger {
  /*
   * Use @elidable annotation to completely exclude functions from the compiler generated byte-code based on
   * the specified level. In a production build most logging functions will simply disappear with no runtime
   * performance penalty.
   *
   * Specify level as a compiler parameter
   * > scalac -Xelide-below INFO
  */
  @elidable(FINEST) def trace(msg: String, e: Exception): Unit
  @elidable(FINEST) def trace(msg: String): Unit
  @elidable(FINE) def debug(msg: String, e: Exception): Unit
  @elidable(FINE) def debug(msg: String): Unit
  @elidable(INFO) def info(msg: String, e: Exception): Unit
  @elidable(INFO) def info(msg: String): Unit
  @elidable(WARNING) def warn(msg: String, e: Exception): Unit
  @elidable(WARNING) def warn(msg: String): Unit
  @elidable(SEVERE) def error(msg: String, e: Exception): Unit
  @elidable(SEVERE) def error(msg: String): Unit
  @elidable(SEVERE) def fatal(msg: String, e: Exception): Unit
  @elidable(SEVERE) def fatal(msg: String): Unit

  def enableServerLogging(url: String): Unit
  def disableServerLogging(): Unit
}

object LoggerFactory {
  private[logger] def createLogger(name: String) = {}

  lazy val consoleAppender = new BrowserConsoleAppender
  lazy val popupAppender = new PopUpAppender

  /**
   * Create a logger that outputs to browser console
   */
  def getLogger(name: String): Logger = {
    val nativeLogger = Log4JavaScript.log4javascript.getLogger(name)
    nativeLogger.addAppender(consoleAppender)
    new L4JSLogger(nativeLogger)
  }

  /**
   * Create a logger that outputs to a separate popup window
   */
  def getPopUpLogger(name: String): Logger = {
    val nativeLogger = Log4JavaScript.log4javascript.getLogger(name)
    nativeLogger.addAppender(popupAppender)
    new L4JSLogger(nativeLogger)
  }
}

修改Log4javascript.scala

package tutorial.logger

import scala.scalajs.js
import scala.scalajs.js.annotation.JSName

/**
 * Facade for functions in log4javascript that we need
 */
@js.native
private[logger] trait Log4JavaScript extends js.Object {
  def getLogger(name:js.UndefOr[String]):JSLogger = js.native
  def setEnabled(enabled:Boolean):Unit = js.native
  def isEnabled:Boolean = js.native
}

@js.native
@JSName("log4javascript.Level")
private[logger] trait Level extends js.Object {
  val ALL:Level = js.native
  val TRACE:Level = js.native
  val DEBUG:Level = js.native
  val INFO:Level = js.native
  val WARN:Level = js.native
  val ERROR:Level = js.native
  val FATAL:Level = js.native
}

@js.native
@JSName("log4javascript.Logger")
private[logger] trait JSLogger extends js.Object {
  def addAppender(appender:Appender):Unit = js.native
  def removeAppender(appender:Appender):Unit = js.native
  def removeAllAppenders(appender:Appender):Unit = js.native
  def setLevel(level:Level):Unit = js.native
  def getLevel:Level = js.native
  def trace(msg:String, error:js.UndefOr[js.Error]):Unit = js.native
  def debug(msg:String, error:js.UndefOr[js.Error]):Unit = js.native
  def info(msg:String, error:js.UndefOr[js.Error]):Unit = js.native
  def warn(msg:String, error:js.UndefOr[js.Error]):Unit = js.native
  def error(msg:String, error:js.UndefOr[js.Error]):Unit = js.native
  def fatal(msg:String, error:js.UndefOr[js.Error]):Unit = js.native
  def trace(msg:String):Unit = js.native
  def debug(msg:String):Unit = js.native
  def info(msg:String):Unit = js.native
  def warn(msg:String):Unit = js.native
  def error(msg:String):Unit = js.native
  def fatal(msg:String):Unit = js.native
}

@js.native
@JSName("log4javascript.Layout")
private[logger] trait Layout extends js.Object

@js.native
@JSName("log4javascript.JsonLayout")
private[logger] class JsonLayout extends Layout

@js.native
@JSName("log4javascript.Appender")
private[logger] trait Appender extends js.Object {
  def setLayout(layout:Layout):Unit = js.native
  def setThreshold(level:Level):Unit = js.native
}

@js.native
@JSName("log4javascript.BrowserConsoleAppender")
private[logger] class BrowserConsoleAppender extends Appender

@js.native
@JSName("log4javascript.PopUpAppender")
private[logger] class PopUpAppender extends Appender

@js.native
@JSName("log4javascript.AjaxAppender")
private[logger] class AjaxAppender(url:String) extends Appender {
  def addHeader(header:String, value:String):Unit = js.native
}

@js.native
private[logger] object Log4JavaScript extends js.GlobalScope {
  val log4javascript:Log4JavaScript = js.native
}

class L4JSLogger(jsLogger:JSLogger) extends Logger {

  private var ajaxAppender:AjaxAppender = null

  private def undefOrError(e:Exception):js.UndefOr[js.Error] = {
    if(e == null)
      js.undefined
    else
      e.asInstanceOf[js.Error]
  }

  override def trace(msg: String, e: Exception): Unit = jsLogger.trace(msg, undefOrError(e))
  override def trace(msg: String): Unit = jsLogger.trace(msg)
  override def debug(msg: String, e: Exception): Unit = jsLogger.debug(msg, undefOrError(e))
  override def debug(msg: String): Unit = jsLogger.debug(msg)
  override def info(msg: String, e: Exception): Unit = jsLogger.info(msg, undefOrError(e))
  override def info(msg: String): Unit = jsLogger.info(msg)
  override def warn(msg: String, e: Exception): Unit = jsLogger.warn(msg, undefOrError(e))
  override def warn(msg: String): Unit = jsLogger.warn(msg)
  override def error(msg: String, e: Exception): Unit = jsLogger.error(msg, undefOrError(e))
  override def error(msg: String): Unit = jsLogger.error(msg)
  override def fatal(msg: String, e: Exception): Unit = jsLogger.fatal(msg, undefOrError(e))
  override def fatal(msg: String): Unit = jsLogger.fatal(msg)

  override def enableServerLogging(url: String): Unit = {
    if(ajaxAppender == null) {
      ajaxAppender = new AjaxAppender(url)
      ajaxAppender.addHeader("Content-Type", "application/json")
      ajaxAppender.setLayout(new JsonLayout)
      jsLogger.addAppender(ajaxAppender)

    }
  }

  override def disableServerLogging():Unit = {
    if(ajaxAppender != null) {
      jsLogger.removeAppender(ajaxAppender)
      ajaxAppender = null
    }
  }
}

当我尝试运行 sbt 时,收到以下错误消息:

> run
[info] Running tutorial.webapp.TutorialApp
Before getLogger...
TypeError: undefined is not an object (evaluating '$g["log4javascript"]["getLogger"]')

  /tmp/phantomjs-launcher8416853343047081941.js:9 in onError


  /tmp/phantomjs-launcher8416853343047081941.js:11 in onError
  file:///home/jk/workspace/scalajs-tutorial-0.6.x/target/scala-2.11/scala-js-tutorial-fastopt.js:1085 (in function "getLogger__T__Ltutorial_logger_Logger")

  /tmp/phantomjs-launcher8416853343047081941.js:13
  file:///home/jk/workspace/scalajs-tutorial-0.6.x/target/scala-2.11/scala-js-tutorial-fastopt.js:1969 (in function "init___")

  /tmp/phantomjs-launcher8416853343047081941.js:13
  file:///home/jk/workspace/scalajs-tutorial-0.6.x/target/scala-2.11/scala-js-tutorial-fastopt.js:2005 (in function "$m_Ltutorial_webapp_TutorialApp$")

  /tmp/phantomjs-launcher8416853343047081941.js:13
  file:///tmp/phantomjs-launcher-webpage6476048362931659173.html:9541

  /tmp/phantomjs-launcher8416853343047081941.js:13
org.scalajs.jsenv.ExternalJSEnv$NonZeroExitException: PhantomJS exited with code 2
    at org.scalajs.jsenv.ExternalJSEnv$AbstractExtRunner.waitForVM(ExternalJSEnv.scala:96)
    at org.scalajs.jsenv.ExternalJSEnv$ExtRunner.run(ExternalJSEnv.scala:143)
    at org.scalajs.sbtplugin.ScalaJSPluginInternal$.org$scalajs$sbtplugin$ScalaJSPluginInternal$$jsRun(ScalaJSPluginInternal.scala:479)
    at org.scalajs.sbtplugin.ScalaJSPluginInternal$$anonfun$45$$anonfun$apply$27$$anonfun$apply$28.apply(ScalaJSPluginInternal.scala:539)
    at org.scalajs.sbtplugin.ScalaJSPluginInternal$$anonfun$45$$anonfun$apply$27$$anonfun$apply$28.apply(ScalaJSPluginInternal.scala:533)
    at scala.Function1$$anonfun$compose$1.apply(Function1.scala:47)
[trace] Stack trace suppressed: run last compile:run for the full output.
[error] (compile:run) org.scalajs.jsenv.ExternalJSEnv$NonZeroExitException: PhantomJS exited with code 2
[error] Total time: 51 s, completed Jan 13, 2016 9:08:34 PM

此外,sbt 测试也会失败,并显示相同的错误消息:TypeError: undefined is not an object (evaluating '$g["log4javascript"]["getLogger"]')

如果我注释掉 build.sbt 中的以下行

//scalaJSStage in Global := FastOptStage // If NOT commented out: Uses Phantom, if IS commented out: Uses Rhino

然后我从 sbt run 得到以下结果:

> run
[info] Running tutorial.webapp.TutorialApp
Before getLogger...
After getLogger...
[success] Total time: 4 s, completed Jan 13, 2016 9:16:39 PM

该脚本也适用于浏览器,当加载文件 scalajs-tutorial-fastopt.html 时,会出现按钮,单击时控制台上会出现新的“您单击了按钮”文本和“按钮单击了...”。 sbt 测试也成功。

Phantom 版本为 2.0.1-development。

怎样才能让代码与 Phantom.js 一起工作?

编辑: 我将 Phantom 降级到版本 2.0.0,但错误消息保持不变。

最佳答案

我也遇到了这个问题,遵循相同的教程。事实证明,问题的根本原因是在log4javascript代码中,有嵌入html的字符串(里面有更多的javascript)。我不知道这背后的原因是什么......无论如何,PhantomJS 测试运行程序的工作原理是将所有 js 代码和依赖项一起吐出到一个 html 文件中(在脚本标记内);当 javascript 解释器看到未转义的 </script> 时标签,它出错了。 (顺便说一句,我发现这一点的方法是在我的临时文件中找到 phantomjs-launcher-*.html 页面并在 Chrome 中打开它)。

无论如何,在查看了 log4javascript 的源代码并注意到它托管在 sourceforge 上并且大约 9 年没有更新(并且源代码控制是 CVS...)之后,我们决定使用不同的日志记录框架实现,因为能够在 PhantomJS 中运行测试对我们来说非常重要。

非常令人沮丧,因为您希望日志框架成为“正常工作”的东西之一。

关于javascript - 如何将 log4javascript 与 ScalaJS/PhantomJS 一起使用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34776238/

相关文章:

javascript - 是否可以更改在网页上运行客户端的脚本?

javascript - Discord.js 修复了 V14 的 TypeError [ClientMissingIntents] : Valid intents must be provided for the Client but it is still having the same problem

scala - 内部特质破坏了隐式参数

c# - NLog 不从引用的 dll 写入

mysql - rails : implement auditing of table insertions/updates

javascript - 禁用对象文字 es6 中的scrollmagic Controller

javascript - Ajax 调用不适用于 android 但在 ios Cordova 上运行良好

scala - 为什么 Scala 改变了关系运算符和相等运算符的相对优先级(与 Java 相比)?

java - 将时间转换为UTC时间是相反的方式

php - 在PHP中使用配置