How to read console logs of wkwebview programmatically
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:
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
anddebug
- 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);
}
Comments
-
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 about 8 yearsitz crashing when I read this
let context = self.webView.valueForKeyPath("documentView.webView.mainFrame.javaScriptContext") as! JSContext
.. I am using Wkwebview -
NaveenKumar about 8 yearsYes Buddy...But I just want to read the JS console logs in my app programatically...
-
NaveenKumar about 8 yearsBuddy the bridge is meant for UIWebView.
-
Locksleyu about 5 yearsHas anyone else gotten this solution to work (or the other one below?). I tried both and ...didReceiveScriptMessage: is never called.
-
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.postMessage(msg) };
and call it withlogging('testing');
thedidReceiveScriptMessage
method is called. Don't know if there is a difference withevaluateJavaScript
as proposed in this solution. For me that means, that you can't useconsole.log()
. Instead you have to use your custom JS function, which has some identifier inmessage
so that you can differentiate betweenlog
and other methods. Or use multiple handler. -
γγγΎγ about 5 yearsI see empty console in wkwebview, but if I open that link in safari I see logs. :(
-
Curtis about 5 yearsI 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 about 5 yearsTap on Safari at the top menu, from there: Safari --> Preferences --> Advanced --> "Show Develop menu"
-
Amos almost 5 yearsthis is not programatically, not answering the question
-
mah almost 5 yearsThis 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 about 4 yearsI've used the same code but it dint work for some reason
-
Curtis about 4 yearsNow 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 about 4 yearswatch 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 almost 4 yearsThis one worked for me (iOS 13.5, Objective-C, Xcode 11.5)
-
Anton Tropashko over 3 yearsI have "Show develop menu" but develop menu does not have "Simulator" option. Xcode 11.5 safari 13.1.2
-
Anton Tropashko over 3 years2020-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 over 3 yearswas a fluke or unrelated to your logger. works now. thank you.
-
spodell over 3 yearsremember to
import WebKit
-
xaphod over 3 yearsGreat thanks. You might want to also capture
console.warn
andconsole.error
also, ie. add to end oflet source ...
line. -
xaphod over 3 years... I ended up posting my own answer because I needed
console.log
's string substitution to work. -
xaphod over 3 yearsKVO 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 over 3 yearsto capture error mesaj use this let source = "function captureLog(msg) { window.webkit.messageHandlers.logHandler.postMessage(msg); } window.onerror = captureLog;"
-
Mehul Prajapati about 2 yearsHope you have a good day, You just helped me kill 7 days old hard skinned bug π