rn-boat-vconsole
Version:
###Description show log in your rn app ^_^
390 lines (354 loc) • 12 kB
JavaScript
import { AsyncStorage, AppState, NetInfo } from "react-native";
import moment from "moment";
import InMemory from "./adapters/in-memory";
import timers from "./timers";
import debounce from "debounce";
import stacktraceParser from "stacktrace-parser";
import stringify from "json-stringify-safe";
import StringifyDataWriter from "./data-writers/stringify-data-writer";
function guid() {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
return (
s4() +
s4() +
"-" +
s4() +
"-" +
s4() +
"-" +
s4() +
"-" +
s4() +
s4() +
s4()
);
}
const APP_START_LOG_MESSAGE = {
id: guid(),
lengthAtInsertion: 0,
level: "seperator",
message: "APP START",
timeStamp: moment(),
color: "#FFF",
};
class DebugService {
currentLevel = null;
constructor() {
this.previousConnectionTypes = [];
this.logRows = [];
this.store = new StringifyDataWriter(new InMemory());
this.listners = [];
this.options = {
logToConsole: true,
logRNErrors: false,
maxNumberToRender: 0,
maxNumberToPersist: 0,
rowInsertDebounceMs: 200,
};
AppState.addEventListener(
"change",
this._handleAppStateChange.bind(this)
);
NetInfo.addEventListener(
"connectionChange",
this._handleConnectivityTypeChange.bind(this)
);
}
_handleConnectivityTypeChange(connectionInfo) {
let { type, effectiveType } = connectionInfo;
if (type === "none") {
this.hasBeenDisconnected = true;
this.seperator(`DISCONNECTED FROM INTERNET`);
} else {
const buildConnectionString = () => {
return `${type.toUpperCase()}${effectiveType === "unknown"
? ""
: ` - ${effectiveType}`}`;
};
if (this.hasBeenDisconnected) {
this.seperator(
`[NETINFO] RECONNECTED TO ${buildConnectionString()}`
);
} else {
if (this.connectionHasBeenEstablished) {
this.seperator(
`[NETINFO] CHANGED TO ${buildConnectionString()}`
);
} else {
this.seperator(
`[NETINFO] CONNECTION TO ${buildConnectionString()}`
);
}
}
}
this.connectionHasBeenEstablished = true;
}
_handleAppStateChange(currentAppState) {
this.seperator(`APP STATE: ${currentAppState.toUpperCase()}`);
}
setupRNErrorLogging() {
if (ErrorUtils) {
const defaultHandler =
ErrorUtils.getGlobalHandler && ErrorUtils.getGlobalHandler();
if (defaultHandler) {
const parseErrorStack = error => {
if (!error || !error.stack) {
return [];
}
return Array.isArray(error.stack)
? error.stack
: stacktraceParser.parse(error.stack);
};
ErrorUtils.setGlobalHandler((error, isFatal) => {
const stack = parseErrorStack(error);
this.rnerror(isFatal, error.message, stack);
defaultHandler && defaultHandler(error, isFatal);
});
}
}
}
async init(storageAdapter, options) {
//this.clear();
this.options = {
...this.options,
...options,
};
if (this.options.customDataWriter) {
this.store = this.options.customDataWriter;
} else {
this.store = new StringifyDataWriter(
storageAdapter || new InMemory()
);
}
if (this.options.logRNErrors) {
this.setupRNErrorLogging();
}
this._initalGet();
return this.insertAppStartMessage();
}
async insertAppStartMessage() {
if (!this.appStartRendered) {
await this._appendToLog([APP_START_LOG_MESSAGE]);
this.appStartRendered = true;
}
}
async insertStoreRows(rows) {
if (this.store.readOnly) {
return;
}
this.rowsToInsert = this.rowsToInsert || [];
this.rowsToInsert = this.rowsToInsert.concat(rows);
if (!this.debouncedInsert) {
this.debouncedInsert = debounce(() => {
if (this.store && this.store.insertRows) {
const insertArray = this.rowsToInsert;
this.rowsToInsert = [];
return this.store.insertRows(
insertArray,
this.getEmittableData(this.logRows)
);
}
}, this.options.rowInsertDebounceMs);
}
this.debouncedInsert();
}
async _initalGet() {
this.initPromise = this.store.getRows();
const rows = await this.initPromise;
this.store.setReadOnly(false);
const newRows = this.logRows;
this.logRows = this.logRows.concat(rows);
this.sortLogRows();
await this.insertStoreRows(newRows);
if (this.store.initalDataRead) {
await this.store.initalDataRead(this.logRows);
}
return this.emitDebugRowsChanged(this.logRows);
}
sortLogRows() {
this.logRows.sort((left, right) => {
let dateDiff = moment
.utc(right.timeStamp)
.diff(moment.utc(left.timeStamp));
if (dateDiff === 0) {
return right.lengthAtInsertion - left.lengthAtInsertion;
}
return dateDiff;
});
}
async _getAndEmit() {
const rows = await this.store.getRows();
return this.emitDebugRowsChanged(rows);
}
async getLog(level) {
this.currentLevel = level;
const rows = await this.store.getRows(level);
return this.emitDebugRowsChanged(rows);
}
stopTimer(name) {
timers.stop(name);
timers.remove(name);
}
async startTimer(name) {
let timer = timers.start(name);
return await this._log("start-timer", "#54d7df", name);
}
async logTime(name) {
let timer = timers.stop(name);
return await this._log(
"end-timer",
"#54d7df",
`${name}-timer delta: ${timer.delta}ms`
);
}
async clear() {
this.logRows = [];
await this.store.clear();
return await this._getAndEmit();
}
log(...logRows) {
return this._log("log", "#000", ...logRows);
}
debug(...logRows) {
return this._log("debug", "#daa520", ...logRows);
}
info(...logRows) {
return this._log("info", "#6a5acd", ...logRows);
}
error(...logRows) {
return this._log("error", "#dc143c", ...logRows);
}
fatal(...logRows) {
return this._log("fatal", "rgb(255, 0, 0)", ...logRows);
}
success(...logRows) {
return this._log("success", "#54df72", ...logRows);
}
rnerror(fatal, message, stackTrace) {
let errorString = `ERROR: ${message} \nSTACKSTRACE:\n`;
if (stackTrace && Array.isArray(stackTrace)) {
const stackStrings = stackTrace.map(stackTraceItem => {
let methodName = "-";
let lineNumber = "-";
let column = "-";
if (stackTraceItem.methodName) {
methodName =
stackTraceItem.methodName === "<unknown>"
? "-"
: stackTraceItem.methodName;
}
if (stackTraceItem.lineNumber !== undefined) {
lineNumber = stackTraceItem.lineNumber.toString();
}
if (stackTraceItem.column !== undefined) {
column = stackTraceItem.column.toString();
}
return `Method: ${methodName}, LineNumber: ${lineNumber}, Column: ${column}\n`;
});
errorString += stackStrings.join("\n");
}
if (fatal) {
return this._log("RNFatal", "rgb(255, 0, 0)", errorString);
} else {
return this._log("RNError", "#df5454", errorString);
}
}
seperator(name) {
return this._log("seperator", "#FFF", name);
}
async _log(level, color, ...logRows) {
// this.logToConsole(level, color, ...logRows);
let formattedRows = logRows.map((logRow, idx) => ({
id: guid(),
lengthAtInsertion: this.logRows.length + idx,
level,
message: this._parseDataToString(logRow),
timeStamp: moment(),
color,
}));
if (this.options.appendToLogRow) {
formattedRows = formattedRows.map(logRow =>
this.options.appendToLogRow(logRow)
);
}
if (!this.logRows.length) {
await this.insertAppStartMessage();
}
if (this.store.logRowCreated) {
formattedRows.forEach(logRow => this.store.logRowCreated(logRow));
}
await this._appendToLog(formattedRows);
if (!this.initPromise) {
await this._initalGet();
}
}
logToConsole(level, color, ...logRows) {
if (
this.options.logToConsole &&
(!this.options.disableLevelToConsole ||
!this.options.disableLevelToConsole.some(
disabledLevel => disabledLevel === level
))
) {
let avaliableConsoleLogs = ["log", "info", "debug", "error"];
let consoleLogFunc =
avaliableConsoleLogs.find(avCL => avCL === level) || "log";
console[consoleLogFunc](...logRows);
}
}
_parseDataToString(data) {
if (typeof data === "string" || data instanceof String) {
return data;
} else {
let dataAsString = stringify(data, null, " "); //FYI: spaces > tabs
if (dataAsString && dataAsString.length > 12000) {
dataAsString =
dataAsString.substring(0, 12000) +
"...(Cannot display more RN-device-log)";
}
if (dataAsString == undefined) {
return data + '';
}
return dataAsString;
}
}
_appendToLog(logRows) {
this.logRows = logRows.concat(this.logRows);
this.insertStoreRows(logRows);
if (this.currentLevel && this.currentLevel.toLowerCase() != "all") {
let newRows = this.logRows.filter((row) => {
if (row.level == this.currentLevel.toLowerCase())
return row;
else
return false;
})
this.emitDebugRowsChanged(newRows);
}
else
this.emitDebugRowsChanged(this.logRows);
}
onDebugRowsChanged(cb) {
this.listners.push(cb);
cb(this.getEmittableData(this.logRows));
return () => {
var i = this.listners.indexOf(cb);
if (i !== -1) {
this.listners.splice(i, 1);
}
};
}
emitDebugRowsChanged(data) {
this.listners.forEach(cb => cb(this.getEmittableData(data)));
}
getEmittableData(rows) {
if (this.options.maxNumberToRender !== 0) {
return rows.slice(0, this.options.maxNumberToRender);
}
return rows;
}
}
export default new DebugService();