Skip to content
Prev Previous commit
Next Next commit
Also call the original console methods if they exist
Ex. When `console.log` or similar is called, this will call
the original method, which can be quite useful.

For example, under iOS, this will log to the Safari console debugger,
which has an expandable UI for inspecting objects, etc., and is also
just useful if you are using that as a REPL.

I don't believe this incurs a meaningful performance penalty unless
the console is open, but it would be easy to stick behind a flag
if that is a problem.
  • Loading branch information
ccheever committed Jul 1, 2015
commit 5f0d456567bb0cc967896aafbf80a166fbfb369b
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* This pipes all of our console logging functions to native logging so that
* JavaScript errors in required modules show up in Xcode via NSLog.
*
* @provides console
* @polyfill
*/

/*eslint global-strict:0*/
(function(global) {
'use strict';

var OBJECT_COLUMN_NAME = '(index)';
var LOG_LEVELS = {
trace: 0,
log: 1,
info: 2,
warn: 3,
error: 4
};

function setupConsole(global) {

var originalConsole = global.console;

if (!global.nativeLoggingHook) {
return;
}

function getNativeLogFunction(level) {
return function() {
var str = Array.prototype.map.call(arguments, function(arg) {
var ret;
var type = typeof arg;
if (arg === null) {
ret = 'null';
} else if (arg === undefined) {
ret = 'undefined';
} else if (type === 'string') {
ret = '"' + arg + '"';
} else if (type === 'function') {
try {
ret = arg.toString();
} catch (e) {
ret = '[function unknown]';
}
} else {
// Perform a try catch, just in case the object has a circular
// reference or stringify throws for some other reason.
try {
ret = JSON.stringify(arg);
} catch (e) {
if (typeof arg.toString === 'function') {
try {
ret = arg.toString();
} catch (E) {}
}
}
}
return ret || '["' + type + '" failed to stringify]';
}).join(', ');
global.nativeLoggingHook(str, level);
};
}

var repeat = function(element, n) {
return Array.apply(null, Array(n)).map(function() { return element; });
};

function consoleTablePolyfill(rows) {
// convert object -> array
if (!Array.isArray(rows)) {
var data = rows;
rows = [];
for (var key in data) {
if (data.hasOwnProperty(key)) {
var row = data[key];
row[OBJECT_COLUMN_NAME] = key;
rows.push(row);
}
}
}
if (rows.length === 0) {
global.nativeLoggingHook('', LOG_LEVELS.log);
return;
}

var columns = Object.keys(rows[0]).sort();
var stringRows = [];
var columnWidths = [];

// Convert each cell to a string. Also
// figure out max cell width for each column
columns.forEach(function(k, i) {
columnWidths[i] = k.length;
for (var j = 0; j < rows.length; j++) {
var cellStr = rows[j][k].toString();
stringRows[j] = stringRows[j] || [];
stringRows[j][i] = cellStr;
columnWidths[i] = Math.max(columnWidths[i], cellStr.length);
}
});

// Join all elements in the row into a single string with | separators
// (appends extra spaces to each cell to make separators | alligned)
var joinRow = function(row, space) {
var cells = row.map(function(cell, i) {
var extraSpaces = repeat(' ', columnWidths[i] - cell.length).join('');
return cell + extraSpaces;
});
space = space || ' ';
return cells.join(space + '|' + space);
};

var separators = columnWidths.map(function(columnWidth) {
return repeat('-', columnWidth).join('');
});
var separatorRow = joinRow(separators, '-');
var header = joinRow(columns);
var table = [header, separatorRow];

for (var i = 0; i < rows.length; i++) {
table.push(joinRow(stringRows[i]));
}

// Notice extra empty line at the beginning.
// Native logging hook adds "RCTLog >" at the front of every
// logged string, which would shift the header and screw up
// the table
global.nativeLoggingHook('\n' + table.join('\n'), LOG_LEVELS.log);
}

global.console = {
error: getNativeLogFunction(LOG_LEVELS.error),
info: getNativeLogFunction(LOG_LEVELS.info),
log: getNativeLogFunction(LOG_LEVELS.log),
warn: getNativeLogFunction(LOG_LEVELS.warn),
trace: getNativeLogFunction(LOG_LEVELS.trace),
table: consoleTablePolyfill
};

// If available, also call the original `console` method since that is
// sometimes useful, Ex. On OS X, this will let you see rich output to
// the Safari REPL console
Object.keys(global.console).forEach(methodName => {
var reactNativeMethod = global.console[methodName];
global.console[methodName] = function() {
originalConsole[methodName].apply(originalConsole, arguments);
reactNativeMethod.apply(global.console, arguments);
}
});

}

if (typeof module !== 'undefined') {
module.exports = setupConsole;
} else {
setupConsole(global);
}

})(this);