user4159962
user4159962

Reputation:

JavaFX 8 WebEngine: How to get console.log() from javascript to System.out in java?

I'm using both JavaFX and the javascript engine inside JavaFX WebEngine to develop an application. I'd like to get feedback from javascript for debugging purposes. What happens to the console output inside the WebEngine? Is there any way I can access it, or redirect to System.out in java?

Upvotes: 21

Views: 19606

Answers (4)

NateW
NateW

Reputation: 978

Thanks to Gili for his answer which taught me how to get this working in my code. My modifications to his solution I think are worth sharing though because it is a little less code and it redirects both console.log and console.error:

webEngine.getLoadWorker().stateProperty().addListener((ob, o, n) -> {
                JSObject window = (JSObject) webEngine.executeScript("window");
                window.setMember("javaOut", System.out);
                window.setMember("javaErr", System.err);
                webView.getEngine().executeScript(
                        "console.log = function(message) { javaOut.println(message); };\n"
                        + "console.error = function(message) {javaErr.println(message); };");
            });

If you want to do more than just redirect to standard out/error (like outputting to some GUI console or adding timestamps) then making something like Gili's JavaBridge class is probably worthwhile, but for a simple re-direct to standard out/err, this seems easier to me.

I did notice one limitation because I had some javascript code that did console.error('And error occured', error) which of course didn't work because I only overwrote the version of console.error() that only takes one argument. It was good enough for my purposes though.

Upvotes: 0

dzikoysk
dzikoysk

Reputation: 1578

You can just add message listener to see what's happening in your output. You don't have to inject js bridge with redefinition of functions like console.log for every single loaded page

WebConsoleListener.setDefaultListener((webView, message, lineNumber, sourceId) -> {
    System.out.println(message + "[at " + lineNumber + "]");
});

Upvotes: 14

Gili
Gili

Reputation: 90180

The following code redirects console.log() to JavaBridge.log():

import netscape.javascript.JSObject;

[...]

public class JavaBridge
{
    public void log(String text)
    {
        System.out.println(text);
    }
}

// Maintain a strong reference to prevent garbage collection:
// https://bugs.openjdk.java.net/browse/JDK-8154127
private final JavaBridge bridge = new JavaBridge();

[...]

webEngine.getLoadWorker().stateProperty().addListener((observable, oldValue, newValue) ->
{
    JSObject window = (JSObject) webEngine.executeScript("window");
    window.setMember("java", bridge);
    webEngine.executeScript("console.log = function(message)\n" +
        "{\n" +
        "    java.log(message);\n" +
        "};");
});

Upvotes: 27

Tim Overly
Tim Overly

Reputation: 445

I like to go the other direction. We use log4j so I created a javascript wrapper like the following:

module.exports = {

    levels:[ "ALL", "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF"],

    level:"INFO",

    error:function(msg){
      if(this.isErrorEnabled()){
        console.error(msg)
      }
    },
    warn:function(msg){
      if(this.isWarnEnabled()){
        console.warn(msg)
      }
    },
    info:function(msg){
      if(this.isInfoEnabled()){
        console.log("INFO: "+msg)
      }
    },
    debug:function(msg){
      if(this.isDebugEnabled()){
        console.log("DEBUG: "+msg)
      }
    },
    trace:function(msg){
      if(this.isTraceEnabled()){
        console.log("TRACE: "+msg)
      }
    },

    isErrorEnabled:function(){
      return this.isEnabled("ERROR");
    },
    isWarnEnabled:function(){
      return this.isEnabled("WARN");
    },
    isInfoEnabled:function(){
      return this.isEnabled("INFO");
    },
    isDebugEnabled:function(){
      return this.isEnabled("DEBUG");
    },
    isTraceEnabled:function(){
      return this.isEnabled("TRACE");
    },
    isEnabled:function(statementLevel){
      return this.levels.indexOf(this.level)<=this.levels.indexOf(statementLevel);
    }
  }

Then at the beginning of the javascript I check to see if the log is present and set it:

if(window.log == undefined){
  window.log = require("./utils/log4j-wrapper")
  window.log.level = "INFO"
}

And that way if you set the Log4j logger directly on the engine before you even load the url like:

WebEngine webEngine = webView.getEngine()
JSObject win = (JSObject) webEngine.executeScript("window")
win.setMember("log", log)  //log being the java log4j logger

This way I can get logging in if I am opening directly in a browser or it is being run from a WebView in a JavaFX program. And has the added benefit of having levels to the logging in javascript that match your packages of the WebView controller. Just an alternative for larger javascript views.

Upvotes: 6

Related Questions