Skip to content

Instantly share code, notes, and snippets.

@peaBerberian
Last active November 10, 2023 15:58
Show Gist options
  • Save peaBerberian/fa3d5068e5178144190a429953d7095b to your computer and use it in GitHub Desktop.
Save peaBerberian/fa3d5068e5178144190a429953d7095b to your computer and use it in GitHub Desktop.
// ================================ HTML-LOGGER ================================
//
// This code will display a semi-opaque div HTMLElement on the top of the screen
// containing log produced through `console` methods (and optionally logging
// requests).
//
// You can configure it by updating the variables below.
/**
* To set to `true` to also show HTTP(S) requests made through either the
* `XMLHttpRequest` or `fetch` API.
*
* Note that by enabling it you risk showing a lot of logs on the screen.
*/
var SHOULD_LOG_REQUESTS = false;
/** Maximum amounts of logs that can be shown on the screen at any given time. */
var MAX_NUMBER_OF_LOGS = 50;
/**
* Maximum number UTF-16 code units a shown log string can have.
* Logs exceeding that size will be shortened.
*/
var MAX_LOG_LENGTH = 2e3;
/**
* Console methods patched by this tool.
*
* You can remove elements to only keep those you feel are important if doing so
* on the application-side is not straightforward.
*/
var PATCHED_LOG_FUNCTIONS = [
"log",
"error",
"info",
"warn",
"debug"
];
/** Style of the whole div on which logs will be appended. */
var LOG_CONTAINER_ELT_STYLE = {
position: "fixed",
top: "0px",
left: "0px",
width: "100%",
fontSize: "12px",
fontFamily: "monospace mono",
zIndex: "999999999999999999",
backgroundColor: "#00000099",
color: "#ffffff",
display: "flex",
flexDirection: "column-reverse",
pointerEvents: "none",
padding: "20px"
};
/**
* Style of a single log appended to the log container.
*
* Note: the `color` style will be overwritten depending on the type of logs and
* can be configured below.
*/
var LOG_ELT_STYLE = {
backgroundColor: "transparent",
margin: "0px",
padding: "0px",
};
/** Color of logs about network requests. */
var LOG_COLOR_NETWORK = "#ff77ff";
/** Color of "error" logs. */
var LOG_COLOR_ERROR = "#ff9999";
/** Color of "warning" logs. */
var LOG_COLOR_WARNING = "#ffff66";
/** Color of "info" logs. */
var LOG_COLOR_INFO = "#66ffff";
/** Color of other logs not categorized previously. */
var LOG_COLOR_OTHERS = "#ffffff";
// END OF CONFIGURATION
// =============================================================================
if (MAX_NUMBER_OF_LOGS <= 0) {
throw new Error("HTML-LOGGER: Invalid maximum number of logs");
}
if (MAX_LOG_LENGTH <= 0) {
throw new Error("HTML-LOGGER: Invalid maximum log length");
}
var loggerElt = document.createElement("div");
var styleKeys = Object.keys(LOG_CONTAINER_ELT_STYLE);
for (var ctnrStyleIdx = 0; ctnrStyleIdx < styleKeys.length; ctnrStyleIdx++) {
var styleProp = styleKeys[ctnrStyleIdx];
var styleValue = LOG_CONTAINER_ELT_STYLE[styleProp];
loggerElt.style[styleProp] = styleValue;
}
if (document.body != null) {
document.body.appendChild(loggerElt);
} else {
// TODO is this scenario possible?
document.addEventListener("DOMContentLoaded", function () {
document.body.appendChild(loggerElt);
});
}
var spyRemovers = PATCHED_LOG_FUNCTIONS.map(function (meth) {
var oldConsoleFn = console[meth];
console[meth] = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
var argStr = args.map(processLogArg).join(" ");
addLog(meth, argStr);
return oldConsoleFn.apply(this, args);
};
return function () {
console[meth] = oldConsoleFn;
};
});
if (SHOULD_LOG_REQUESTS) {
var originalXhrOpen_1 = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function () {
var method = arguments[0];
var url = arguments[1];
if (typeof method !== "string" || typeof url !== "string") {
return originalXhrOpen_1.apply(this, arguments);
}
this.addEventListener("load", function () {
addLog("Network", "Loaded ".concat(method, " XHR from: ").concat(url, " (status: ").concat(this.status, ")"));
});
this.addEventListener("error", function () {
addLog("Network", "Errored ".concat(method, " XHR from: ").concat(url));
});
this.abort = function () {
addLog("Network", "Aborted ".concat(method, " XHR from: ").concat(url));
return XMLHttpRequest.prototype.abort.apply(this, arguments);
};
this.send = function () {
addLog("Network", "Sending ".concat(method, " XHR to: ").concat(url));
return XMLHttpRequest.prototype.send.apply(this, arguments);
};
return originalXhrOpen_1.apply(this, arguments);
};
spyRemovers.push(function () {
XMLHttpRequest.prototype.open = originalXhrOpen_1;
});
var originalFetch_1 = window.fetch;
window.fetch = function () {
var url;
var method;
if (arguments[0] == null) {
url = void 0;
}
else if (typeof arguments[0] === "string") {
url = arguments[0];
}
else if (arguments[0] instanceof URL) {
url = arguments[0].href;
}
else if (typeof arguments[0].url === "string") {
url = arguments[0].url;
}
else {
try {
url = arguments[0].toString();
}
catch (_) {
}
}
if (arguments[0] == null) {
method = "GET";
}
else if (typeof arguments[0].method === "string") {
method = arguments[0].method;
}
else if (arguments[1] != null && typeof arguments[1].method === "string") {
method = arguments[0].method;
}
else {
method = "GET";
}
addLog("Network", "Sending ".concat(method, " fetch to: ").concat(url));
var realFetch = originalFetch_1.apply(this, arguments);
return realFetch.then(function (res) {
addLog("Network", "Loaded ".concat(method, " fetch from: ").concat(url, " (status: ").concat(res.status, ")"));
return res;
}, function (err) {
addLog("Network", "Errored/Aborted ".concat(method, " fetch from: ").concat(url));
throw err;
});
};
spyRemovers.push(function () {
window.fetch = originalFetch_1;
});
}
function addLog(level, str) {
var logElt = document.createElement("pre");
var preStyleKeys = Object.keys(LOG_ELT_STYLE);
for (var preStyleIdx = 0; preStyleIdx < preStyleKeys.length; preStyleIdx++) {
var styleProp = preStyleKeys[preStyleIdx];
var styleValue = LOG_ELT_STYLE[styleProp];
logElt.style[styleProp] = styleValue;
}
logElt.style.color =
level === "Network" ? LOG_COLOR_NETWORK :
level === "error" ? LOG_COLOR_ERROR :
level === "warning" ? LOG_COLOR_WARNING :
level === "info" ? LOG_COLOR_INFO :
LOG_COLOR_OTHERS;
var date = new Date();
var strDate =
String(date.getHours()) +
":" +
String(date.getMinutes()) +
":" +
String(date.getSeconds());
logElt.textContent = strDate + " [" + level + "] " + str;
while (loggerElt.children.length >= MAX_NUMBER_OF_LOGS) {
loggerElt.removeChild(loggerElt.children[0]);
}
loggerElt.appendChild(logElt);
return;
}
function processLogArg(arg2) {
var processed;
switch (typeof arg2) {
case "function":
case "symbol":
case "bigint":
processed = "";
break;
case "string":
case "number":
case "boolean":
case "undefined":
processed = arg2;
break;
case "object":
if (arg2 === null) {
processed = "null";
}
else if (arg2 instanceof Error) {
processed = "NAME: " + String(arg2.name) + " ~ CODE: " + String(arg2.code) + " ~ MESSAGE: " + String(arg2.message);
}
else {
processed = "{}";
}
break;
default:
processed = "";
break;
}
if (typeof processed === "string" && processed.length > MAX_LOG_LENGTH) {
return processed.substring(0, MAX_LOG_LENGTH - 1) + "\u2026";
}
return processed;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment