Replicating/overriding javascript's console.log function - javascript

OK, so here's what I'm trying to do:
I'm building a Javascript->Cocoa/Objective-C bridge (a tiny test actually), which means that I'm able to call an Objective-C function from my JavaScript code.
One of the issues faced is that messages/errors/logs/etc from console.log are not visible, so they are to be forwarded to the corresponding Cocoa function (i created one like - (void)log:(NSString*)msg which simply takes a parameter as string and prints it out in the Xcode's console window.
Now, the thing is how do I replicate 100% what console.log does + forward the message to my own log function?
This is what I've done so far:
Javascript
console.log = function(text) {
API.log_(text);
}
Objective-C
- (void)log:(NSString*)msg
{
NSLog(#"Logging from JS : %#", msg);
}
If "text" is a simple text string when calling console.log("something"), then it works fine. If I pass the console.log function an object, then it's not working as expected (e.g. in Chrome's Javascript console window).
How would you go about it?
Is it possible to get the string output of what console.log would normally print out?
P.S. I tried playing with JSON.stringify(obj, null, 4) a bit, but it still doesn't seem right. Any ideas?

It sounds like you want the original functionality to persist whilst also capturing the input and passing it to your API. That could be achieved by something like (JSFiddle):
var original = console.log,
API = {
log_: function() {
// Your API functionality
}
};
console.log = function() {
original.apply(console, arguments);
API.log_(arguments);
};
A couple of things to point out. Firstly, the native console.log can receive any number of parameters, so we use arguments to handle this. We have to use .apply() to call the original method in the context of console and it also allows us to pass the arguments exactly as they were on the original call.

Related

is it possible to run a function before console.log("hello") in angular?

the problem is very simple to understand yet tricky to implement.
if i have a predefined function(not one that i make) console.log("hello"). I would like to modify console.log() so that when i run console.log() anywhere, i can run a function where i get the arguments make some changes, example "hello" becomes "user1: hello".
The only way i can think of is:
Function MyOwnConsoleLog(string){
string = "test.. " + string;
console.log(string);
};
console.log = myCustomConsoleLogFunction
console.log("hello") //almost works!
This almost works but as you see console.log inside MyOwnConsoleLog is no longer console.log() XD which means that it will recurse infinitely and fail
Is it possible to make this work, maybe with some console.log.bind()/.apply()/.xxx or something else?
btw I have tried saving the old console.log() to another variable, but it seems pretty impossible to make a copy of this function, have tried alot of deep copies and other methods here at stackoverflow, none of them managed to make a fully working console.log() independent copy.
You can assign original console.log to a variable then assign new function to it.
var console_log = console.log;
console.log = function MyOwnConsoleLog(string){
string = "test.. " + string;
console_log(string);
};
console.log("hello") // works!
But in reality it is not that simple since you only care about first argument so this:
console.log('Hello', 'world');
would only print 'Hello'
Something like this would work better
console_log.apply(this, arguments);
Store the original console.log() function as a global variable in the app.
let originalConsoleLog;
Function MyOwnConsoleLog(string){
if (!originalConsoleLog) {
originalConsoleLog = console.log;
}
string = "test.. " + string;
originalConsole.log(string);
};
console.log = myCustomConsoleLogFunction
console.log("hello")
I wouldn't recommend this approach lightly, because replacing/modifying global objects like this is ripe for unexpected errors, but it should work.
I've played around with this approach to create a TypeScript annotation to create a #JsonIgnore tag, similar to what might be seen in some Java frameworks.
I think the approach you mentioned at the end (keep a reference to the original console.log for your new function) does actually work for me in the console.
See this code:
const logfunc = console.log;
function consoleLogHighjack(foo) {
logfunc("before the actual log");
logfunc(foo);
}
console.log = consoleLogHighjack;
console.log("hello"); // prints "before the actual log" then "hello"
Note that the actual console log is able to handle multiple arguments, so a correct signature that mimics console.log would be advisable (see use of .apply in Molda's answer )

console.log() called on object other than console

I remember that always when I wanted to pass console.log as a callback parameter to some function, it didn't work unless I used the bind() method to bind console to it.
For example:
const callWithTest = callback => callback('test');
callWithTest(console.log); // That didn't use to work.
callWithTest(console.log.bind(console)); // That worked (and works) fine.
See Uncaught TypeError: Illegal invocation in javascript.
However, recently I noticed that console.log() works fine even when called on object other than console. For example:
console.log.call(null, 'test');
logs 'test'.
When and why did it change? Does the specification say anything about it?
Editor's Draft of Console API used to say:
Logging APIs SHOULD all be callable functions allowing them to be passed as arguments to error handling callbacks, forEach methods, etc.
This is no longer included in the current version of the specification.
I thought that Chrome and Node.js changed it to work like in the specification, but it seems that it worked like that even before it.
I'm still curious when did it change and what was the reason of that.
I don't know when the change was made, but I have an idea about why it didn't work.
Consider the following code
callWithTest = callback => callback('test');
var Demo = function () {this.str = 'demo';}
Demo.prototype.getStr = function () { return this.str;}
demo = new Demo ();
demo.getStr(); // returns 'demo'
callWithTest(demo.getStr); // returns undefined
window.str = 'window';
callWithTest(demo.getStr); // returns 'window'
If you trace the code, you will see that when demo.getStr gets called through another function, this refers to window, and sine str is not defined within window, it returns undefined. If you called it directly or bind with demo, this refers to demo and thus it returns 'demo'.
In nodeJS (v6.6.0), there exists a class called Console within the console module which user can explicitly pipe logs into a file (or whatever stream a user like). According to Node.js v6.6.0 api specification,
console = new Console(process.stdout, process.stderr);
Console does not exist in browser as it isn't necessary. The output of console only exists in a canvas used for debugging, and there are exactly one instance of it. User can't, and should not be able to, pipe output of console to any other places as it will become a serious security issue. Because of this, developers can do something within the log function like var x = this.x || console.x as there is exactly one instance of the console object.

Is it possible to flush the console (make it print immediately)?

I use Firefox + Firebug for some Javascripting. The text I'm trying to log with console.log does not immediately appear in Firebug's console. It seems like it piles up in a buffer somewhere, and then gets flushed to console in chunks. I have a function that makes a few log calls. Sometimes I get just the first line, sometimes - nothing. I do, however, see the whole bunch of lines when I refresh the page.
Can I flush the console log manually?
The short answer is no. There is no flush. You could clear the console
console.clear();
But I don't think that's what you want. This is most likely from the code. If we can see it, I can revise my answer with better feedback.
If you want to see all the available methods under console, execute this in the command line:
for(var i in console) {
console.log(i);
}
or have a look at the wiki page of the console API.
It's not a Firefox problem, It's a JavaScript problem because execution is delayed and variables are updated so you can see only the last value.
To see immediately the output you need to convert your object in string so it will not change also if object will be updated.
I wrote this easy function :
function printLog(s) {
if (typeof(s) === 'object') {
console.log( JSON.stringify(s) );
} else {
console.log(s);
}
}
The printed output is a string (so you can't interact with it) but it contains the real dynamic object that you want to see at the print time instant.

Get Javascript function name

Is there a way to obtain function's name from outside of it?
Lets say there is a js script on web page that we cannot modificate, just read. The script contains object, which contains objects and functions. Lets say that we want to find function named "HelloWorld".
With firebug, we loop through these objects and methods with a script, which looks something like this
// Parameter is target object.
function getFunctionNames(obj) {
// For each objects / functions
for (var id in obj) {
// Focus only on functions
if (typeof(obj[id]) == "function") {
// Get name of the function.
// console.log("Function: " + obj[id].toString());
// Code above returns a block of code without the name. Example output:
// Function: function(name) { alert("Hello World! Hello " + name + "!"); }
//
// Expected output would be
// Function: HelloWorld
}
}
}
obj[id].toString() returns a block of code instead of a name.
obj[id].name returns an empty string. Anonymous function(?).
I cannot use arguments.callee.name because I cannot modify the target code.
I could just browse objects and functions in firebug or just read source code, but I'm looking a way to do it with Javascript.
Edit
For real world example, head to Youtube and try to get the name of function "setMsg()" from "yt" object via Javascript.
Edit2
Accepting Simon's answer for being kinda closest what I was looking for. It appears that I was seeking variable name, rather than function name. While answer didn't help me on original problem, it surely answered to original question. Paul Draper's comments helped me to right direction.
Thanks!
Use obj.name
Note that arguments.callee returns a function. name is property on every function (though it's empty for anonymous functions), so that's why arguments.callee.name works.
This works for webkit (Chrome and Safari), Firefox, and possibly others. It does not work for IE: function.name not supported in IE.
As mentioned, the function doesn't have any intrinsic name other than the "" it gets from being an anonymous function. Some browsers (Firefox, probably Chrome, maybe others) do however perform some limited form of static analysis to figure out names of declared functions, to help with error stack traces. You can get to it in an relatively cross-browser way by getting setMsg to throw an exception and then parse exc.stack:
// cheat with .% in Firebug; there might be other ways of doing this, I dunno:
yt.setMsg.%m.za.__defineSetter__('a', function() { throw new Error(); });
try { yt.setMsg('a', 'a'); }
catch(e) { alert(e.stack.split('\n')[2].split('#')[0]); }
... On the other hand, this is a pretty terrible hack and dependent on the actual function involved (and if you know the function, you probably know its name already). It does work a bit more reliably when done from inside the function.
If you restrict yourself to just Firefox and are doing this for debug purposes, there are better ways of getting to it. Set devtools.chrome.enabled to true in about:config, open a Scratchpad (Shift+F4), set it to environment: browser, and run the following:
Components.utils.import("resource://gre/modules/jsdebugger.jsm");
window.addDebuggerToGlobal(window);
dbg = new Debugger();
dw = dbg.addDebuggee(content);
f = content.wrappedJSObject.yt.setMsg;
name = dw.makeDebuggeeValue(f).displayName;
dbg.removeDebuggee(content);
alert(name);
In both cases, you will note that it alerts "m.ya" instead of "setMsg", and indeed this is because the function was originally declared as m.ya = function() { ...; }. There is no reason why "setMsg" would be a preferable name, from the point of the browser.

Jquery - $(selector).scroll is passing parameters to its callback function?

i was reading those few lines of code from a javascript(Jquery) file and I was wondering where those arguments "x" and "y" are coming from.Is the scroll event that is taking care peraphs?
$(window).scroll(function(x,y) {
dosomething(withThis);
});
thanks
Luca
P.S. here is the jquery excerpt that made me ask this question Is this an elegant way to make elements fade in while scrolling?
Well, open Firebug console (or web dev. tools) and paste there this code:
$(window).scroll(function(x,y,z) {
console.log(x,y,z)
});
After execution and scrolling you'll see the result (works on sites with jQuery).
As expected, first argument - event object, 2nd and 3rd - undefined;
But, you can trigger events manually, and pass any arguments.
More info here: http://api.jquery.com/trigger/
No, the handler for the scroll event takes only an event object, not x,y. That code is misleading and wrong. We can't help if you don't show the full code.
This is the same thing as if you define a method
function add(a,b) {
return a + b;
}
// When called like this, b will be undefined, as is y in your example
// programmer error not caught by the compiler
add(7);
You could inspect the arguments variable on any function, so you can get an idea of the data that's passed in.
$(window).scroll(function() {
console.log(arguments);
});

Categories