How to get console.log line numbers shown in Nodejs? - node.js

Got an old application, that prints out quite a lot of messages using console.log, but I just can not find in which files and lines console.log is called.
Is there a way to hook into the app and show file name and line numbers?

Having full stack trace for each call is a bit noisy. I've just improved the #noppa's solution to print only the initiator:
['log', 'warn', 'error'].forEach((methodName) => {
const originalMethod = console[methodName];
console[methodName] = (...args) => {
let initiator = 'unknown place';
try {
throw new Error();
} catch (e) {
if (typeof e.stack === 'string') {
let isFirst = true;
for (const line of e.stack.split('\n')) {
const matches = line.match(/^\s+at\s+(.*)/);
if (matches) {
if (!isFirst) { // first line - current function
// second line - caller (what we are looking for)
initiator = matches[1];
break;
}
isFirst = false;
}
}
}
}
originalMethod.apply(console, [...args, '\n', ` at ${initiator}`]);
};
});
It also patches other methods (useful for Nodejs, since warn and error don't come with a stack trace as in Chrome).
So your console would look something like:
Loading settings.json
at fs.readdirSync.filter.forEach (.../settings.js:21:13)
Server is running on http://localhost:3000 or http://127.0.0.1:3000
at Server.app.listen (.../index.js:67:11)

For a temporary hack to find the log statements that you want to get rid of, it's not too difficult to override console.log yourself.
var log = console.log;
console.log = function() {
log.apply(console, arguments);
// Print the stack trace
console.trace();
};
// Somewhere else...
function foo(){
console.log('Foobar');
}
foo();
That will print something like
Foobar
Trace
at Console.console.log (index.js:4:13)
at foo (index.js:10:13)
at Object.<anonymous> (index.js:12:1)
...
A lot of noise in there but the second line in the call stack, at foo (index.js:10:13), should point you to the right place.

All solutions to this question so far rely on splitting and matching the stack trace as a string, which will break in (the unlikely) case the format of that string is changed in the future. Inspired by this gist on GitHub and the other answers here, I want to provide my own solution:
'use strict';
const path = require('path');
['debug', 'log', 'warn', 'error'].forEach((methodName) => {
const originalLoggingMethod = console[methodName];
console[methodName] = (firstArgument, ...otherArguments) => {
const originalPrepareStackTrace = Error.prepareStackTrace;
Error.prepareStackTrace = (_, stack) => stack;
const callee = new Error().stack[1];
Error.prepareStackTrace = originalPrepareStackTrace;
const relativeFileName = path.relative(process.cwd(), callee.getFileName());
const prefix = `${relativeFileName}:${callee.getLineNumber()}:`;
if (typeof firstArgument === 'string') {
originalLoggingMethod(prefix + ' ' + firstArgument, ...otherArguments);
} else {
originalLoggingMethod(prefix, firstArgument, ...otherArguments);
}
};
});
// Tests:
console.log('%s %d', 'hi', 42);
console.log({ a: 'foo', b: 'bar'});
Unlike the other solutions, this script
outputs no additional lines and
handles string substitutions correctly.
You can color the prefix with chalk or color.js, but I didn't want to introduce dependencies for this here.
The above script uses the V8 API to customize stack traces. The callee is a CallSite object with the following methods in case you want to customize the prefix:
getThis: returns the value of this
getTypeName: returns the type of this as a string. This is the name of the function stored in the constructor field of this, if available, otherwise the object’s [[Class]] internal property.
getFunction: returns the current function
getFunctionName: returns the name of the current function, typically its name property. If a name property is not available an attempt is made to infer a name from the function’s context.
getMethodName: returns the name of the property of this or one of its prototypes that holds the current function
getFileName: if this function was defined in a script returns the name of the script
getLineNumber: if this function was defined in a script returns the current line number
getColumnNumber: if this function was defined in a script returns the current column number
getEvalOrigin: if this function was created using a call to eval returns a string representing the location where eval was called
isToplevel: is this a top-level invocation, that is, is this the global object?
isEval: does this call take place in code defined by a call to eval?
isNative: is this call in native V8 code?
isConstructor: is this a constructor call?
isAsync: is this an async call (i.e. await or Promise.all())?
isPromiseAll: is this an async call to Promise.all()?
getPromiseIndex: returns the index of the promise element that was followed in Promise.all() for async stack traces, or null if the CallSite is not a Promise.all() call.
This answer is a cross-post of an answer I just gave to a similar question as more people might find this page.

I found Dmitry Druganov's answer really nice, but I tried it on Windows 10 (with Node 8.9.4) and it didn't work well. It was printing the full path, something like:
Loading settings.json
at fs.readdirSync.filter.forEach (D:\Users\Piyin\Projects\test\settings.js:21:13)
Server is running on http://localhost:3000 or http://127.0.0.1:3000
at Server.app.listen (D:\Users\Piyin\Projects\test\index.js:67:11)
So I took said answer and made these improvements (from my point of view):
Assume the important line of the stack trace is the third one (the first one is the word Error and the second one is where you place this script)
Remove the current script folder path (given by __dirname, which in my case is D:\Users\Piyin\Projects\test). Note: For this to work well, the script should be on the project's main Javascript
Remove the starting at
Place the file information before the actual log
Format the information as Class.method at path/to/file:line:column
Here it is:
['log','warn','error'].forEach((methodName) => {
const originalMethod = console[methodName];
console[methodName] = (...args) => {
try {
throw new Error();
} catch (error) {
originalMethod.apply(
console,
[
(
error
.stack // Grabs the stack trace
.split('\n')[2] // Grabs third line
.trim() // Removes spaces
.substring(3) // Removes three first characters ("at ")
.replace(__dirname, '') // Removes script folder path
.replace(/\s\(./, ' at ') // Removes first parentheses and replaces it with " at "
.replace(/\)/, '') // Removes last parentheses
),
'\n',
...args
]
);
}
};
});
And here's the new output:
fs.readdirSync.filter.forEach at settings.js:21:13
Loading settings.json
Server.app.listen at index.js:67:11
Server is running on http://localhost:3000 or http://127.0.0.1:3000
Here's the minified-by-hand code (240 bytes):
['log','warn','error'].forEach(a=>{let b=console[a];console[a]=(...c)=>{try{throw new Error}catch(d){b.apply(console,[d.stack.split('\n')[2].trim().substring(3).replace(__dirname,'').replace(/\s\(./,' at ').replace(/\)/,''),'\n',...c])}}});

Slightly modified version of noppa's answer, this version will output something like:
/file/in-which/console/is/called.js:75:23
The stuff you want to log.
This is clean and convenient (especially for use in VSCode - which will turn the file path into a link).
const { log } = console;
function proxiedLog(...args) {
const line = (((new Error('log'))
.stack.split('\n')[2] || '…')
.match(/\(([^)]+)\)/) || [, 'not found'])[1];
log.call(console, `${line}\n`, ...args);
}
console.info = proxiedLog;
console.log = proxiedLog;
// test
console.log('Hello!');
The snippet will only work well in a NodeJS environment…

Appends the line number to the end of the log
const stackTrace = function () {
let obj = {}
Error.captureStackTrace(obj, stackTrace)
return obj.stack
}
const getLine = function (stack) {
let matchResult = stack.match(/\(.*?\)|\s.+/g) || []
let arr = matchResult.map((it) => {
return it.split(' ').pop().replace(/\(|\)/g, '')
})
return arr[1] ?? ''
}
const log = function (...args) {
let stack = stackTrace() || ''
let matchResult = getLine(stack)
let line = matchResult
for (var i in arguments) {
if (typeof arguments[i] == 'object') {
// util.inspect(arguments[i], false, 2, false)
arguments[i] = JSON.stringify(arguments[i])
}
}
arguments[i] += ' ' + line
console.log.apply(console, arguments)
}
log("test")

Simple & exhaustive solution if you want to temporarily find the origin of logs:
{
const logOriginal = process.stdout.write
// #ts-ignore
const log = (msg) => logOriginal.call(process.stdout, msg + '\n')
;['stdout', 'stderr'].forEach((stdName) => {
// #ts-ignore
var methodOriginal = process[stdName].write
// #ts-ignore
process[stdName].write = function (...args) {
log('LOG:')
// #ts-ignore
methodOriginal.apply(process[stdName], args)
// #ts-ignore
log(new Error().stack.replace(/^Error/, 'LOGGED FROM:'))
}
})
Error.stackTraceLimit = Infinity
}

const showName = (name) => {
return
console.log(name)
}
showName(“Crush”)

Related

Dynamic Slash Command Options List via Database Query?

Background:
I am building a discord bot that operates as a Dungeons & Dragons DM of sorts. We want to store game data in a database and during the execution of certain commands, query data from said database for use in the game.
All of the connections between our Discord server, our VPS, and the VPS' backend are functional and we are now implementing slash commands since traditional ! commands are being removed from support in April.
We are running into problems making the slash commands though. We want to set them up to be as efficient as possible which means no hard-coded choices for options. We want to build those choice lists via data from the database.
The problem we are running into is that we can't figure out the proper way to implement the fetch to the database within the SlashCommandBuilder.
Here is what we currently have:
const {SlashCommandBuilder} = require('#discordjs/builders');
const fetch = require('node-fetch');
const {REST} = require('#discordjs/rest');
const test = require('../commonFunctions/test.js');
var options = async function getOptions(){
let x = await test.getClasses();
console.log(x);
return ['test','test2'];
}
module.exports = {
data: new SlashCommandBuilder()
.setName('get-test-data')
.setDescription('Return Class and Race data from database')
.addStringOption(option =>{
option.setName('class')
.setDescription('Select a class for your character')
.setRequired(true)
for(let op of options()){
//option.addChoice(op,op);
}
return option
}
),
async execute(interaction){
},
};
This code produces the following error when start the npm for our bot on our server:
options is not a function or its return value is not iterable
I thought that maybe the function wasn't properly defined, so I replaced the contents of it with just a simple array return and the npm started without errors and the values I had passed showed up in the server.
This leads me to think that the function call in the modules.exports block is immediatly attempting to get the return value of the function and as the function is async, it isn't yet ready and is either returning undefined or a promise or something else not iteratable.
Is there a proper way to implement the code as shown? Or is this way too complex for discord.js to handle?
Is there a proper way to implement the idea at all? Like creating a json object that contains the option data which is built and saved to a file at some point prior to this command being registered and then having the code above just pull in that file for the option choices?
Alright, I found a way. Ian Malcom would be proud (LMAO).
Here is what I had to do for those with a similar issues:
I had to basically re-write our entire application. It sucks, I know, but it works so who cares?
When you run your index file for your npm, make sure that you do the following things.
Note: you can structure this however you want, this is just how I set up my js files.
Setup a function that will setup the data you need, it needs to be an async function as does everything downstream from this point on relating to the creation and registration of the slash commands.
Create a js file to act as your application setup "module". "Module" because we're faking a real module by just using the module.exports method. No package.jsons needed.
In the setup file, you will need two requires. The first is a, as of yet, non-existent data manager file; we'll do that next. The second is a require for node:fs.
Create an async function in your setup file called setup and add it to your module.exports like so:
module.exports = { setup }
In your async setup function or in a function that it calls, make a call to the function in your still as of yet non-existent data manager file. Use await so that the application doesn't proceed until something is returned. Here is what mine looks like, note that I am writing my data to a file to read in later because of my use case, you may or may not have to do the same for yours:
async function setup(){
console.log('test');
//build option choice lists
let listsBuilt = await buildChoiceLists();
if (listsBuilt){
return true;
} else {
return false;
}
}
async function buildChoiceLists(){
let classListBuilt = await buildClassList();
return true;
}
async function buildClassList(){
let classData = await classDataManager.getClassData();
console.log(classData);
classList = classData;
await writeFiles();
return true;
}
async function writeFiles(){
fs.writeFileSync('./CommandData/classList.json', JSON.stringify(classList));
}
Before we finish off this file, if you want to store anything as a property in this file and then get it later on, you can do so. In order for the data to return properly though, you will need to define a getter function in your exports. Here is an example:
var classList;
module.exports={
getClassList: () => classList,
setup
};
So, with everything above you should have something that looks like this:
const classDataManager = require('./DataManagers/ClassData.js')
const fs = require('node:fs');
var classList;
async function setup(){
console.log('test');
//build option choice lists
let listsBuilt = await buildChoiceLists();
if (listsBuilt){
return true;
} else {
return false;
}
}
async function buildChoiceLists(){
let classListBuilt = await buildClassList();
return true;
}
async function buildClassList(){
let classData = await classDataManager.getClassData();
console.log(classData);
classList = classData;
await writeFiles();
return true;
}
async function writeFiles(){
fs.writeFileSync('./CommandData/classList.json', JSON.stringify(classList));
}
module.exports={
getClassList: () => classList,
setup
};
Next that pesky non-existent DataManager file. For mine, each data type will have its own, but you might want to just combine them all into a single .js file for yours.
Same with the folder name, I called mine DataManagers, if you're combining them all into one, you could just call the file DataManager and leave it in the same folder as your appSetup.js file.
For the data manager file all we really need is a function to get our data and then return it in the format we want it to be in. I am using node-fetch. If you are using some other module for data requests, write your code as needed.
Instead of explaining everything, here is the contents of my file, not much has to be explained here:
const fetch = require('node-fetch');
async function getClassData(){
return new Promise((resolve) => {
let data = "action=GetTestData";
fetch('http://xxx.xxx.xxx.xx/backend/characterHandler.php', {
method: 'post',
headers: { 'Content-Type':'application/x-www-form-urlencoded'},
body: data
}).then(response => {
response.json().then(res => {
let status = res.status;
let clsData = res.classes;
let rcData = res.races;
if (status == "Success"){
let text = '';
let classes = [];
let races = [];
if (Object.keys(clsData).length > 0){
for (let key of Object.keys(clsData)){
let cls = clsData[key];
classes.push({
"name": key,
"code": key.toLowerCase()
});
}
}
if (Object.keys(rcData).length > 0){
for (let key of Object.keys(rcData)){
let rc = rcData[key];
races.push({
"name": key,
"desc": rc.Desc
});
}
}
resolve(classes);
}
});
});
});
}
module.exports = {
getClassData
};
This file contacts our backend php and requests data from it. It queries the data then returns it. Then we format it into an JSON structure for use later on with option choices for the slash command.
Once all of your appSetup and data manager files are complete, we still need to create the commands and register them with the server. So, in your index file add something similar to the following:
async function getCommands(){
let cmds = await comCreator.appSetup();
console.log(cmds);
client.commands = cmds;
}
getCommands();
This should go at or near the top of your index.js file. Note that comCreator refers to a file we haven't created yet; you can name this require const whatever you wish. That's it for this file.
Now, the "comCreator" file. I named mine deploy-commands.js, but you can name it whatever. Once again, here is the full file contents. I will explain anything that needs to be explained after:
const {Collection} = require('discord.js');
const {REST} = require('#discordjs/rest');
const {Routes} = require('discord-api-types/v9');
const app = require('./appSetup.js');
const fs = require('node:fs');
const config = require('./config.json');
async function appSetup(){
console.log('test2');
let setupDone = await app.setup();
console.log(setupDone);
console.log(app.getClassList());
return new Promise((resolve) => {
const cmds = [];
const cmdFiles = fs.readdirSync('./commands').filter(f => f.endsWith('.js'));
for (let file of cmdFiles){
let cmd = require('./commands/' + file);
console.log(file + ' added to commands!');
cmds.push(cmd.data.toJSON());
}
const rest = new REST({version: '9'}).setToken(config.token);
rest.put(Routes.applicationGuildCommands(config.clientId, config.guildId), {body: cmds})
.then(() => console.log('Successfully registered application commands.'))
.catch(console.error);
let commands = new Collection();
for (let file of cmdFiles){
let cmd = require('./commands/' + file);
commands.set(cmd.data.name, cmd);
}
resolve(commands);
});
}
module.exports = {
appSetup
};
Most of this is boiler plate for slash command creation though I did combine the creation and registering of the commands into the same process. As you can see, we are grabbing our command files, processing them into a collection, registering that collection, and then resolving the promise with that variable.
You might have noticed that property, was used to then set the client commands in the index.js file.
Config just contains your connection details for your discord server app.
Finally, how I accessed the data we wrote for the SlashCommandBuilder:
data: new SlashCommandBuilder()
.setName('get-test-data')
.setDescription('Return Class and Race data from database')
.addStringOption(option =>{
option.setName('class')
.setDescription('Select a class for your character')
.setRequired(true)
let ops = [];
let data = fs.readFileSync('./CommandData/classList.json','utf-8');
ops = JSON.parse(data);
console.log('test data class options: ' + ops);
for(let op of ops){
option.addChoice(op.name,op.code);
}
return option
}
),
Hopefully this helps someone in the future!

DELETE FILES with Node.js

I'm trying to delete some files and then show a message.
EXPECTED OUTPUT
File deleted
Folder Cleared!!!
ACTUAL OUTPUT
Folder Cleared!!!
File deleted
The current code is:
function clearConverted() {
const resp = new Promise(async (resolve) => {
const converted = glob.sync('./converted/*.mp4');
if (converted.length) {
const fs = require('fs');
const promises = converted.map(v => {
fs.unlink(v, () => console.log('File deleted'))
})
Promise.all(promises);
} else {
console.log('No files to delete');
}
resolve();
})
resp.then(console.log('Folder Cleared!!!'))
}
May you help me?
Per my original comments on your question, you have heaps to fix with your code -
Don't use require inside a loop
fs.unlink does not return a Promise.You're probably looking for fsPromises.unlink
What is the explicit Promise constructor anti-pattern and how do I avoid it?
const { unlink } =
require("fs").promises // <-- fsPromises
function clearConverted() {
const converted =
glob.sync("./converted/*.mp4")
if (converted.length === 0)
return Promise.resolve([])
const promises =
converted.map(v => unlink(v).then(_ => v))
return Promise.all(promises)
}
clearConverted() // <-- returns a promise!
.then(deletedFiles => console.log("done! deleted files:", deletedFiles))
// done! deleted files: ./converted/foo.mp4, ./converted/bar.mp4
And see how we removed console.log side effects from the function? This allows our function to collect meaningful data, like the file names, and return a list of deleted files. Because the console.log effect is now outside of clearConverted we can change it, if we wish.
For example, we could simply display the count of deleted files, in a less verbose program -
clearConverted()
.then(deletedFiles =>
console.log("done! deleted %d files.", deletedFiles.length)
)
// done! deleted 9 files.
And we can do more. An obvious improvement now is to make clearConverted a generic function which accepts the path as an argument -
function unlinkFiles (path = "") { // <-- generic name and path parameter
const files =
glob.sync(path)
if (files.length === 0)
return Promise.resolve([])
else
return Promise.all(files.map(f => unlink(f).then(_ => f)))
}
unlinkFiles("./converted/*.mp4") // <-- supply path at call site
.then(deletedFiles => console.log("done! deleted files:", deletedFiles))
.catch(console.error) // <-- don't forget to catch Promises too
Modern features like async allow us to skip some of the ceremony around Promises -
async function unlinkFiles (path = "") { // <-- async keyword
const files =
glob.sync(path)
return files.length === 0
? [] // <-- automatically wrapped in a Promise
: Promise.all(files.map(f => unlink(f).then(_ => f)))
}
Now if you wanted, you could make the function accept a glob result instead of a path -
const unlinkFiles = async (files = []) => // <-- arrow function
files.length === 0
? []
: Promise.all(files.map(f => unlink(f).then(_ => f)))
unlinkFiles(glob.sync("./converted/*.mp4")) // <-- use glob as input
.then(console.log, console.error)
When you detangle the wires, programming can be fun and easy. Sadly, languages like JavaScript also make it easy to shoot yourself in the foot, so there's a lot of misery before enlightenment.
I have other answers involving the fs module and Promises. These additional code examples may help provide additional insight -
Recursively rename files
Recursively read all package.json
Recursively list all subdirectories
When you call Promise.all add .then method there, something likethis:
Promise.all(promises).then((response) => console.log("Folder Cleared")).catch((error) => console.log(error))
And also when you require modules or dependencies, you declare them at the top of the file and not inside functions or loops.

Get return value of module in calling file

my root node file requires a module called q1 (not including all the required libraries as not relevant)
const analyzeSentiment = function(message) {
sentiment.getSentiment(message).then(result => {
return (result.vote === 'positive') ? handlePositive() : handleNegative();
});
}
const handlePositive = function() {
return `That's great, we have an opening next Friday at 3pm. Would that work for you?`;
}
const handleNegative = function() {
return `That's okay. Thanks for you time. If you change your mind, give us a call at (xxx) yyy-zzzz.`;
}
exports.analyzeSentiment = analyzeSentiment;
I call it like this: const message = require('q1').analyzeSentiment('text string');
With console logging I can see that it makes it down into the proper handlePositive or handleNegative methods, but nothing comes back. I've tried a few different ways but can't get it to work. Anyone have any suggestions, or see something blatantly wrong I'm doing? This is my first time working with node.
Your function analyzeSentiment not returning anything (see explanation further down).
Try this:
const analyzeSentiment = function(message) {
return sentiment.getSentiment(message).then(result => {
return (result.vote === 'positive') ? handlePositive() : handleNegative();
});
}
And in your caller:
require('q1').sentimentAnalyzer('text string').then(message => {
// Do your thing with the message here
});
Alternatively, if you are in an async context you can use await on the caller:
const message = await require('q1').sentimentAnalyzer('text string');
You might be wondering why the return (result.vote === ... isn't returning from your analyzeSentiment-function. The reason is that the you are creating an anonymous function with the arrow-expression result => ... in the then-block.

module.exports return value undefined

Little info, i have an arp.js file which takes a subnet address "192.168.2" and gets all strings returned from arp -a and stores in an array.
I can't figure out why my arpList function is returning an undefined value in my index.js file.
All the console.logs are returning the correct values in the arp.js page when called from the index.js, but the ipObj is coming up undefined. Even the console.log before i return of ipObj works.
Any help would be greatly appreciated.
var { spawn } = require('child_process');
const arpLs = spawn('arp', ['-a']);
var bufferData;
module.exports = {
arpList: function (subnet) {
arpLs.stdout.on('data', data => {
bufferData += data
})
arpLs.stderr.on('data', data => {
console.log('error: ' + data);
});
arpLs.on('exit', function (code) {
if (code != 0) {
console.log("Error exiting"); //if error occurs
}
console.log("exit start 1"); // checking internal processes at stages
var dataArray = bufferData.split(' ');
var ipArray = [];
for (i = 0; i < dataArray.length; i++) {
if (dataArray[i].includes(subnet)) {
ipArray.push(dataArray[i]);
console.log("loop working");
}
}
var ipObj = { "lanIps": ipArray };
console.log("Object is there: "+ipObj)
return ipObj; // this obj should be returned to the index.js call using
})
},
sayMyName: function () {
return "Hello";
}
}
//arpList(ipSubnet);
//INDEX.js
//the index page looks like this
//var arp = require('./arp.js);
//var ipSubnet = "192.168.2";
//var lanIps = arp.arpList(ipSubnet);
//console.log(lanIps);
I ended up adding a callback function to arpList - function (subnet, callback)
Then instead of returning the value pass it into the callback
Then on the index.js side instead of
var lanIps = arp.arpList(value)
i used
arp.arpList(value, function(res){lanIps = res}
return ipObj; // this obj should be returned to the index.js call using
It won't be returned. The reference say nothing about return value. Node-style callbacks rarely work like that because they are potentially asynchronous and returned value cannot be taken into account.
This a special case of this well-known problem. The process is asynchronous and is finished after arp.arpList(ipSubnet) call, there's nothing to assign to lanIps. This is a use case for promises. There are already third-party promisified counterparts like child-process-promise.
The problem can be also solved by moving to synchronous API. child_process functions have synchronous counterparts, including spawnSync.

Catching console.log in node.js?

Is there a way that I can catch eventual console output caused by console.log(...) within node.js to prevent cloggering the terminal whilst unit testing a module?
Thanks
A better way could be to directly hook up the output you to need to catch data of, because with Linus method if some module write directly to stdout with process.stdout.write('foo') for example, it wont be caught.
var logs = [],
hook_stream = function(_stream, fn) {
// Reference default write method
var old_write = _stream.write;
// _stream now write with our shiny function
_stream.write = fn;
return function() {
// reset to the default write method
_stream.write = old_write;
};
},
// hook up standard output
unhook_stdout = hook_stream(process.stdout, function(string, encoding, fd) {
logs.push(string);
});
// goes to our custom write method
console.log('foo');
console.log('bar');
unhook_stdout();
console.log('Not hooked anymore.');
// Now do what you want with logs stored by the hook
logs.forEach(function(_log) {
console.log('logged: ' + _log);
});
EDIT
console.log() ends its output with a newline, you may want to strip it so you'd better write:
_stream.write = function(string, encoding, fd) {
var new_str = string.replace(/\n$/, '');
fn(new_str, encoding, fd);
};
EDIT
Improved, generic way to do this on any method of any object with async support See the gist.
module.js:
module.exports = function() {
console.log("foo");
}
program:
console.log = function() {};
mod = require("./module");
mod();
// Look ma no output!
Edit: Obviously you can collect the log messages for later if you wish:
var log = [];
console.log = function() {
log.push([].slice.call(arguments));
};
capture-console solves this problem nicely.
var capcon = require('capture-console');
var stderr = capcon.captureStderr(function scope() {
// whatever is done in here has stderr captured,
// the return value is a string containing stderr
});
var stdout = capcon.captureStdout(function scope() {
// whatever is done in here has stdout captured,
// the return value is a string containing stdout
});
and later
Intercepting
You should be aware that all capture functions will still pass the values through to the main stdio write() functions, so logging will still go to your standard IO devices.
If this is not desirable, you can use the intercept functions. These functions are literally s/capture/intercept when compared to those shown above, and the only difference is that calls aren't forwarded through to the base implementation.
Simply add the following snippet to your code will let you catch the logs and still print it in the console:
var log = [];
console.log = function(d) {
log.push(d);
process.stdout.write(d + '\n');
};

Resources