How to read console logs of wkwebview programmatically

23,478

Solution 1

Please use this beautiful in-app "Bridge"

Edit:

self.webView = [[WBWKWebView alloc] initWithFrame:self.view.bounds];
self.webView.JSBridge.interfaceName = @"WKWebViewBridge";
WBWebDebugConsoleViewController * controller = [[WBWebDebugConsoleViewController alloc] initWithConsole:_webView.console];

Then , you can use the delegate method:

- (void)webDebugInspectCurrentSelectedElement:(id)sender
{
// To use the referenced log values
}

Solution 2

It's possible to connect Safari browser on you Mac to the WKWebView and get access to the console.

From Safari, open "Develop" tab and while the iOS Simulator is running with the WKWebView open - just click it to open the console. See:

enter image description here

Solution 3

This worked for me (Swift 4.2/5):

// inject JS to capture console.log output and send to iOS
let source = "function captureLog(msg) { window.webkit.messageHandlers.logHandler.postMessage(msg); } window.console.log = captureLog;"
let script = WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: false)
webView.configuration.userContentController.addUserScript(script)
// register the bridge script that listens for the output
webView.configuration.userContentController.add(self, name: "logHandler")

Then, conforming to the protocol WKScriptMessageHandler, pick up redirected console messages with the following:

func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
    if message.name == "logHandler" {
        print("LOG: \(message.body)")  
    }
}

Solution 4

I needed a way to see JavaScript logs in Xcode's console. Based on the answer by noxo, here's what I came up with:

let overrideConsole = """
    function log(emoji, type, args) {
      window.webkit.messageHandlers.logging.postMessage(
        `${emoji} JS ${type}: ${Object.values(args)
          .map(v => typeof(v) === "undefined" ? "undefined" : typeof(v) === "object" ? JSON.stringify(v) : v.toString())
          .map(v => v.substring(0, 3000)) // Limit msg to 3000 chars
          .join(", ")}`
      )
    }

    let originalLog = console.log
    let originalWarn = console.warn
    let originalError = console.error
    let originalDebug = console.debug

    console.log = function() { log("πŸ“—", "log", arguments); originalLog.apply(null, arguments) }
    console.warn = function() { log("πŸ“™", "warning", arguments); originalWarn.apply(null, arguments) }
    console.error = function() { log("πŸ“•", "error", arguments); originalError.apply(null, arguments) }
    console.debug = function() { log("πŸ“˜", "debug", arguments); originalDebug.apply(null, arguments) }

    window.addEventListener("error", function(e) {
       log("πŸ’₯", "Uncaught", [`${e.message} at ${e.filename}:${e.lineno}:${e.colno}`])
    })
"""

class LoggingMessageHandler: NSObject, WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        print(message.body)
    }
}

let userContentController = WKUserContentController()
userContentController.add(LoggingMessageHandler(), name: "logging")
userContentController.addUserScript(WKUserScript(source: overrideConsole, injectionTime: .atDocumentStart, forMainFrameOnly: true))

let webViewConfig = WKWebViewConfiguration()
webViewConfig.userContentController = userContentController

let webView = WKWebView(frame: .zero, configuration: webViewConfig)

It has a few improvements:

  • It still calls the original log function, in case you decide to look in the Web Inspector
  • It reports from both log, warn, error and debug
  • It adds a nice emoji so you can easily distinguish the different kinds og logs and JS logs stands out in the Xcode console
  • It logs all arguments given to console.log, not just the first one
  • It logs uncaught errors, in case you need that

Solution 5

You can re-evaluate (override) Javascript console.log() default implementation to use window.webkit.messageHandlers.postMessage(msg) to pass message forwards instead. And then intercept the javascript postMessage(msg) call at native code using WKScriptMessageHandler ::didReceiveScriptMessage to get the logged message.

Step 1) Re-evaluate console.log default implementation to use postMessage()

// javascript to override console.log to use messageHandlers.postmessage
NSString * js = @"var console = { log: function(msg){window.webkit.messageHandlers.logging.postMessage(msg) } };";
// evaluate js to wkwebview
[self.webView evaluateJavaScript:js completionHandler:^(id _Nullable ignored, NSError * _Nullable error) {
    if (error != nil)
        NSLog(@"installation of console.log() failed: %@", error);
}];

Step 2) Intercept javascript postMessage in native code at WKScriptMessageHandler::didReceiveScriptMessage

- (void)viewDidLoad
{
    // create message handler named "logging"
    WKUserContentController *ucc = [[WKUserContentController alloc] init];
    [ucc addScriptMessageHandler:self name:@"logging"];
    // assign usercontentcontroller to configuration    
    WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
    [configuration setUserContentController:ucc];
    // assign configuration to wkwebview    
    self.webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height) configuration:configuration];
}


- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
    // what ever were logged with console.log() in wkwebview arrives here in message.body property
    NSLog(@"log: %@", message.body);
}
Share:
23,478
NaveenKumar
Author by

NaveenKumar

I am Curious

Updated on July 21, 2022

Comments

  • NaveenKumar
    NaveenKumar almost 2 years

    I am trying to read the console logs of webapp that is loaded in my WkWebview programmatically.

    so far in my research it's not possible.

    How can I achieve this?

  • NaveenKumar
    NaveenKumar about 8 years
    itz crashing when I read this let context = self.webView.valueForKeyPath("documentView.webView.mainFrame‌​.javaScriptContext") as! JSContext .. I am using Wkwebview
  • NaveenKumar
    NaveenKumar about 8 years
    Yes Buddy...But I just want to read the JS console logs in my app programatically...
  • NaveenKumar
    NaveenKumar about 8 years
    Buddy the bridge is meant for UIWebView.
  • Locksleyu
    Locksleyu about 5 years
    Has anyone else gotten this solution to work (or the other one below?). I tried both and ...didReceiveScriptMessage: is never called.
  • testing
    testing about 5 years
    @Locksleyu: I tried by adding it as user script at the end of the page. It is not called. If I use this code function logging(msg){window.webkit.messageHandlers.logging.postMessa‌​ge(msg) }; and call it with logging('testing'); the didReceiveScriptMessage method is called. Don't know if there is a difference with evaluateJavaScript as proposed in this solution. For me that means, that you can't use console.log(). Instead you have to use your custom JS function, which has some identifier in message so that you can differentiate between log and other methods. Or use multiple handler.
  • γˆγ‚‹γΎγ‚‹
    γˆγ‚‹γΎγ‚‹ about 5 years
    I see empty console in wkwebview, but if I open that link in safari I see logs. :(
  • Curtis
    Curtis about 5 years
    I do not have a "Simulator" option in my Safari's Develop menu, even when the simulator is running. I have the latest Xcode and the latest Safari. This has been the case for months.
  • Oded Regev
    Oded Regev about 5 years
    Tap on Safari at the top menu, from there: Safari --> Preferences --> Advanced --> "Show Develop menu"
  • Amos
    Amos almost 5 years
    this is not programatically, not answering the question
  • mah
    mah almost 5 years
    This works but there are minor problems in the post preventing it from working; I'll edit momentarily. The most important change is to add a single } at the end of the JavaScript that gets evaluated in the first step.
  • Chetan Rajagiri
    Chetan Rajagiri about 4 years
    I've used the same code but it dint work for some reason
  • Curtis
    Curtis about 4 years
    Now I have My iPhone as an option to inspect, but when I do it's blank. No logs even though it is calling console.log and it won't let me enter commands either.
  • Curtis
    Curtis about 4 years
    watch out if you have catch(e){console.log(e)} it won't even send a message. Doesn't work with logging objects but you can do e.toString()
  • Pierre
    Pierre almost 4 years
    This one worked for me (iOS 13.5, Objective-C, Xcode 11.5)
  • Anton Tropashko
    Anton Tropashko over 3 years
    I have "Show develop menu" but develop menu does not have "Simulator" option. Xcode 11.5 safari 13.1.2
  • Anton Tropashko
    Anton Tropashko over 3 years
    2020-08-28 16:09:24.170424+0300 UP[6113:11057331] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Actions added to UIAlertController must have a title' *** First throw call stack:
  • Anton Tropashko
    Anton Tropashko over 3 years
    was a fluke or unrelated to your logger. works now. thank you.
  • spodell
    spodell over 3 years
    remember to import WebKit
  • xaphod
    xaphod over 3 years
    Great thanks. You might want to also capture console.warn and console.error also, ie. add to end of let source ... line.
  • xaphod
    xaphod over 3 years
    ... I ended up posting my own answer because I needed console.log's string substitution to work.
  • xaphod
    xaphod over 3 years
    KVO is brittle and it's easy to end up with crashes if you make a mistake. I'd suggest using the WKScriptMessageHandler-based approaches in other answers instead.
  • Ucdemir
    Ucdemir over 3 years
    to capture error mesaj use this let source = "function captureLog(msg) { window.webkit.messageHandlers.logHandler.postMessage(msg); } window.onerror = captureLog;"
  • Mehul Prajapati
    Mehul Prajapati about 2 years
    Hope you have a good day, You just helped me kill 7 days old hard skinned bug πŸ™Œ