Introduction
One of the first tasks I was assigned to at my new workplace ( ✨Deep BlueDot ✨) was to find a way to fetch console logs (info, errors, warnings, asserts etc) and HAR network logs of a webpage and access it through chrome extension.
These logs give the context of what was going on in the background which can be useful information for a developer to understand when a bug occurs.
Visit
Devign
here: The following article explains the logic behind how the Devign Chrome Extension captures console log information by dispatching custom events to content script.
Manifest
In order to use theÂ
chrome.scripting
API, you need to specify a "manifest_version"
of 3
or higher and include the "scripting"
 permission in your manifest file.Usage
chrome.scripting
API is used to inject JavaScript and CSS into websites. This is similar to what you can do with content scripts, but by using the chrome.scripting
API, extensions can make decisions at runtime.Injection target
TheÂ
target
 parameter is used to specify a target to inject JavaScript or CSS into.export const setLogScript = (tabId: number) => { chrome.scripting.executeScript({ target: { tabId }, func: setLogFunc, world: 'MAIN', }); };
Injected code
Extensions can specify the code to be injected either via an external file or a runtime variable.
The following code is the code that we wish to inject at runtime.
const setLogFunc = () => { let console: any = window.console; if (console.everything === undefined) { console.defaultLog = console.log.bind(console); console.log = function () { const data = { type: 'log', datetime: Date().toLocaleString(), value: Array.from(arguments), }; document.dispatchEvent(new CustomEvent('log', { detail: data })); console.defaultLog.apply(console, arguments); }; console.defaultError = console.error.bind(console); console.error = function () { const data = { type: 'error', datetime: Date().toLocaleString(), value: Array.from(arguments), }; document.dispatchEvent(new CustomEvent('log', { detail: data })); console.defaultError.apply(console, arguments); }; console.defaultWarn = console.warn.bind(console); console.warn = function () { const data = { type: 'warn', datetime: Date().toLocaleString(), value: Array.from(arguments), }; document.dispatchEvent(new CustomEvent('log', { detail: data })); console.defaultWarn.apply(console, arguments); }; console.defaultDebug = console.debug.bind(console); console.debug = function () { const data = { type: 'debug', datetime: Date().toLocaleString(), value: Array.from(arguments), }; document.dispatchEvent(new CustomEvent('log', { detail: data })); console.defaultDebug.apply(console, arguments); }; console.defaultInfo = console.info.bind(console); console.info = function () { const data = { type: 'info', datetime: Date().toLocaleString(), value: Array.from(arguments), }; document.dispatchEvent(new CustomEvent('log', { detail: data })); console.defaultInfo.apply(console, arguments); }; console.defaultGroupCollapsed = console.groupCollapsed.bind(console); console.groupCollapsed = function () { const data = { type: 'groupCollapsed', datetime: Date().toLocaleString(), value: Array.from(arguments), }; document.dispatchEvent(new CustomEvent('log', { detail: data })); console.defaultGroupCollapsed.apply(console, arguments); }; console.defaultTrace = console.trace.bind(console); console.trace = function () { const data = { type: 'trace', datetime: Date().toLocaleString(), value: Array.from(arguments), }; document.dispatchEvent(new CustomEvent('log', { detail: data })); console.defaultTrace.apply(console, arguments); }; } window.addEventListener('error', function (e) { const windowError = { type: 'windowError', datetime: Date().toLocaleString(), value: [e.error.message, e.error.stack], }; document.dispatchEvent(new CustomEvent('log', { detail: windowError })); }); window.addEventListener('unhandledrejection', function (e) { const rejection = { type: 'unhandledrejection', datetime: Date().toLocaleString(), value: [e.reason.message, e.reason.stack], }; document.dispatchEvent(new CustomEvent('log', { detail: rejection })); }); };
Dispatching Custom Events
What the code above is doing - programmatically creating and dispatching events using
dispatchEvent()
method.To generate an event programmatically, you follow these steps:
- First, create a newÂ
CustomEvent
 object.
- Then, trigger the event usingÂ
element.dispatchEvent()
 method.
We are creating a custom event called ‘log’ to capture console logs and passing the log data as a parameter.
src/content/index.tsx
(in content scripts)
Send message to the service worker requesting the log data
document.addEventListener('log', (e: any) => { chrome.runtime.sendMessage({ setConsoleLog: true, log: e.detail }); });
src/background/index.ts
(in service workers)
import { setLogScript } from './log'; async function messageHandler( message: any, sender: chrome.runtime.MessageSender, sendResponse: (response?: any) => void, ) { if (message.setLogScript) { if (!sender?.tab?.id) return; setLogScript(sender.tab.id); } }
Here’s what it looks like in the Devign Dashboard
Once the console log data has been stored in the backend server, it can be parsed and displayed as shown below.
Â
References:
Â
const setLogFunc = () => { let console: any = window.console; if (console.everything === undefined) { console.defaultLog = console.log.bind(console); console.log = function () { const data = { type: 'log', datetime: Date().toLocaleString(), value: Array.from(arguments), }; document.dispatchEvent(new CustomEvent('log', { detail: data })); console.defaultLog.apply(console, arguments); }; console.defaultError = console.error.bind(console); console.error = function () { const data = { type: 'error', datetime: Date().toLocaleString(), value: Array.from(arguments), }; document.dispatchEvent(new CustomEvent('log', { detail: data })); console.defaultError.apply(console, arguments); }; console.defaultWarn = console.warn.bind(console); console.warn = function () { const data = { type: 'warn', datetime: Date().toLocaleString(), value: Array.from(arguments), }; document.dispatchEvent(new CustomEvent('log', { detail: data })); console.defaultWarn.apply(console, arguments); }; console.defaultDebug = console.debug.bind(console); console.debug = function () { const data = { type: 'debug', datetime: Date().toLocaleString(), value: Array.from(arguments), }; document.dispatchEvent(new CustomEvent('log', { detail: data })); console.defaultDebug.apply(console, arguments); }; console.defaultInfo = console.info.bind(console); console.info = function () { const data = { type: 'info', datetime: Date().toLocaleString(), value: Array.from(arguments), }; document.dispatchEvent(new CustomEvent('log', { detail: data })); console.defaultInfo.apply(console, arguments); }; console.defaultGroupCollapsed = console.groupCollapsed.bind(console); console.groupCollapsed = function () { const data = { type: 'groupCollapsed', datetime: Date().toLocaleString(), value: Array.from(arguments), }; document.dispatchEvent(new CustomEvent('log', { detail: data })); console.defaultGroupCollapsed.apply(console, arguments); }; console.defaultTrace = console.trace.bind(console); console.trace = function () { const data = { type: 'trace', datetime: Date().toLocaleString(), value: Array.from(arguments), }; document.dispatchEvent(new CustomEvent('log', { detail: data })); console.defaultTrace.apply(console, arguments); }; } window.addEventListener('error', function (e) { const windowError = { type: 'windowError', datetime: Date().toLocaleString(), value: [e.error.message, e.error.stack], }; document.dispatchEvent(new CustomEvent('log', { detail: windowError })); }); window.addEventListener('unhandledrejection', function (e) { const rejection = { type: 'unhandledrejection', datetime: Date().toLocaleString(), value: [e.reason.message, e.reason.stack], }; document.dispatchEvent(new CustomEvent('log', { detail: rejection })); }); };