// ==UserScript== // @name CloudWatch Helper // @namespace https://github.com/MishaKav/userscripts/cloudwatch-helper // @version 1.1.2 // @description A userscript that adds ability to Hide Noise, Highlight the log level, Format the date and show message inside sns in json format // @author Misha Kav // @copyright 2021, Misha Kav // @match https://*.console.aws.amazon.com/cloudwatch/* // @icon https://s3.amazonaws.com/cloudwatch-console-static-content-s3/1.0/images/favicon.ico // @grant GM_addStyle // @grant GM_registerMenuCommand // @run-at document-end // @require file:///Users/misha/Downloads/GithubSamples/userscripts/cloudwatch-helper.user.js // @require https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js // @updateURL https://raw.githubusercontent.com/MishaKav/userscripts/main/cloudwatch-helper.user.js // @downloadURL https://raw.githubusercontent.com/MishaKav/userscripts/main/cloudwatch-helper.user.js // @supportURL https://github.com/MishaKav/userscripts/issues // ==/UserScript== // @updateURL https://raw.githubusercontent.com/MishaKav/userscripts/main/cloudwatch-helper.user.js // @downloadURL https://raw.githubusercontent.com/MishaKav/userscripts/main/cloudwatch-helper.user.js (function () { 'use strict'; // for local debug // @require file:///Users/misha/Downloads/GithubSamples/userscripts/cloudwatch-helper.user.js const DATE_FORMAT = 'DD/MM/YY HH:mm:ss.SSS'; const SELECTORS = { LOG_GROUP: { PANEL_ACTIONS: '.awsui-util-action-stripe-group', DATES: '.logs__log-events-table__timestamp-cell', LOG_ROW: 'tr span[data-testid=logs__log-events-table__message]', LOG_ROW_CLOSEST: 'tr', MESSAGE: "[data-testid='logs__log-events-table__message']", ALL_LOG_ROWS: '.awsui-table-row', EXPANDED_ROW: 'awsui-table-row-selected', MESSAGE_IN_LOG: '.logs__events__json-key', KEY_TO_PARSE: `"Message":`, }, LOGS_INSIGHTS: { PANEL_ACTIONS: '.awsui-form-actions', DATES: '.logs-table__body-cell', HEADER_ROW: '.logs-table__header-cell', TABLE_ROW: '.logs-table__body-row .flex', TABLE_CELL: '.logs-table__body-cell', LOG_ROW_CLOSEST: 'div.flex', MESSAGE: '.logs-table__body-row .flex', ALL_LOG_ROWS: '.logs-table__body-row .flex', EXPANDED_ROW: 'logs-insights-expanded-row', MESSAGE_IN_LOG: 'td', KEY_TO_PARSE: 'Records.0.Sns.Message', }, }; let LOG_SELECTOR = SELECTORS.LOG_GROUP; const LOG_LEVELS = [ { level: ['[TRACE]', 'TRACE'], color: '#888888' }, { level: ['[DEBUG]', 'DEBUG'], color: '#4DC3FF' }, { level: ['[INFO]', 'INFO'], color: '#4BB4BB' }, { level: ['[WARNING]', '[WARN]', 'WARNING', 'WARN'], color: '#EFBC5F' }, { level: ['[ERROR]', 'ERROR'], color: '#DE8686' }, { level: ['[FATAL]', 'FATAL'], color: '#ff0000' }, ]; const NOISE_CONTENT = [ 'START RequestId', 'END RequestId', 'REPORT RequestId', 'LOGS Name: otel-extension State', // serverless 'EXTENSION Name: otel-extension State', // serverless ]; const isLogGroupPage = () => location.href.includes('/log-group/'); const isLogsInsightsPage = () => location.href.includes(':logs-insights'); const getElements = (doc, selector) => [...doc.querySelectorAll(selector)]; const getElement = (doc, selector) => getElements(doc, selector)[0]; // some json beautify // https://gist.github.com/JGaudette/1ac2201c8e425fd41edc const prettyJson = (obj) => { let maxDepth = 250, depth = 0, root = true, sp = ' '; let objProp = (prop) => { if (prop === null) { return "null,\n"; } let t = (typeof prop + '').toLowerCase(); if ( Object.prototype.toString.apply(prop) === '[object Object]' || Object.prototype.toString.apply(prop) === '[object Array]' ) { return branch(prop).replace(/\n/gim, '\n' + sp) + ',\n'; } else if (t === 'function') { return ( "" + (prop + '') .replace('<', '<') .replace('>', '>') .replace( /function\s?\(/, 'function ' + ((prop.constructor && prop.constructor.name) || prop.name || '') + '(' ) .replace(/\n/gim, '\n ') + ',\n' ); } else if (t === 'string') { return '"' + prop + '",\n'; } else if (t === 'number') { return "" + prop + ',\n'; } else if (t === 'boolean') { return ( "" + (prop === true ? 'true' : 'false') + ',\n' ); } return ( (prop.toSource || prop.toString)().replace(/^\((new\s)?(.+)\)$/, '$2') + ',\n' ); }; let branch = (what) => { let wasRoot = root === true, x, dig, text = '', m = 0; root = false; if (depth > maxDepth) { return "[Maximum Depth Reached]"; } depth++; if (Object.prototype.toString.apply(what) === '[object Array]') { text = "
[\n";
for (x = 0; x < what.length; x++) {
dig = (x + '# ').length;
text +=
sp.substring(0, 4 - dig) +
"#" +
x +
' ' +
objProp(what[x]);
}
return text.replace(/\,\n$/, '\n') + ']';
} else if (Object.prototype.toString.apply(what) === '[object Object]') {
text =
"{\n";
for (x in what) {
if (what.hasOwnProperty(x)) {
m = Math.max(m, (x + '').length);
}
}
m += 1;
for (x in what) {
if (what.hasOwnProperty(x)) {
text +=
sp +
"" +
x +
'' +
new Array(m - (x + '').length).join(' ') +
' : ' +
objProp(what[x]);
}
}
return text.replace(/\,\n$/, '\n') + '}';
}
};
let r = branch(obj);
sp = root = branch = objProp = null;
return r;
};
const getInsightsMessages = (doc) => {
const messageIndex = [
...getElements(doc, LOG_SELECTOR.HEADER_ROW),
].findIndex((c) => c.innerText.includes('@message'));
if (messageIndex) {
return [...getElements(doc, LOG_SELECTOR.TABLE_ROW)].flatMap((row) =>
[...row.querySelectorAll(LOG_SELECTOR.TABLE_CELL)].filter(
(_, i) => i === messageIndex
)
);
}
return [];
};
const getDates = (doc) => {
if (isLogGroupPage()) {
return getElements(doc, LOG_SELECTOR.DATES);
}
if (isLogsInsightsPage()) {
const tsIndex = [...getElements(doc, LOG_SELECTOR.HEADER_ROW)].findIndex(
(c) => c.innerText.includes('@timestamp')
);
if (tsIndex) {
return [...getElements(doc, LOG_SELECTOR.TABLE_ROW)].flatMap((row) =>
[...row.querySelectorAll(LOG_SELECTOR.TABLE_CELL)].filter(
(_, i) => i === tsIndex
)
);
}
}
return [];
};
const shortDates = (doc) => {
const dates = getDates(doc);
dates.forEach((d) => {
const originalDate = d.innerText;
d.setAttribute('original-date', originalDate);
d.innerText = moment(originalDate).format(DATE_FORMAT);
});
};
const originalDates = (doc) => {
const dates = getDates(doc);
dates.forEach((d) => {
d.innerText = d.getAttribute('original-date');
});
};
const getLogRows = (doc) => {
if (isLogGroupPage()) {
return getElements(doc, LOG_SELECTOR.LOG_ROW);
}
if (isLogsInsightsPage()) {
return getInsightsMessages(doc);
}
return [];
};
const toggleNoise = (doc, hide = true) => {
const noise = getLogRows(doc);
noise.forEach((node) => {
const shouldHide = NOISE_CONTENT.some((n) =>
node.innerText.startsWith(n)
);
if (shouldHide) {
node.closest(LOG_SELECTOR.LOG_ROW_CLOSEST).style.display = hide
? 'none'
: '';
}
});
};
const getMessagesRows = (doc) => {
if (isLogGroupPage()) {
return getElements(doc, LOG_SELECTOR.MESSAGE);
}
if (isLogsInsightsPage()) {
return getInsightsMessages(doc);
}
return [];
};
const highlightDebug = (doc, highlight = true) => {
const messages = getMessagesRows(doc);
messages.forEach((d) => {
const msg = d.innerText;
const log = LOG_LEVELS.find((l) =>
l.level.find((ll) => msg.includes(ll))
);
let text;
if (log) {
const splitByLog = log.level.find((l) => msg.includes(l));
if (highlight) {
text = msg
.split(splitByLog)
.join(`${splitByLog}`);
} else {
text = msg
.split(`${splitByLog}`)
.join(splitByLog);
}
d.innerHTML = text;
}
});
};
const initButtonsPanel = (doc) => {
const cwPanel = doc.getElementById('cw-panel');
// already initialise
if (cwPanel) {
return;
}
const awsPanel = getElement(doc, LOG_SELECTOR.PANEL_ACTIONS);
const panel = document.createElement('div');
panel.innerHTML = `