Build a Console Logger Chrome Extension: Complete Developer Guide
Console logging is the foundation of JavaScript debugging. Every developer uses console.log(), console.error(), and other console methods to understand how their code executes and identify issues. However, the built-in browser console has limitations—it clears on page reload, lacks persistent history across sessions, and provides no way to filter or search through logs effectively. Building a custom console logger Chrome extension solves these problems and unlocks powerful debugging capabilities.
This comprehensive guide walks you through creating a production-ready console logger Chrome extension using Manifest V3. You will learn to intercept console methods, capture logs from web pages, display them in a beautiful extension popup, implement persistent storage, and add advanced features like log filtering, export functionality, and real-time updates.
Why Build a Console Logger Extension
The default Chrome DevTools console serves basic debugging needs, but it falls short in several important ways. A custom console logger extension provides significant advantages that enhance your development workflow.
Persistent Log History is perhaps the most valuable feature. The standard console clears whenever you reload a page, making it impossible to compare logs from different sessions. A custom logger can store logs in Chrome’s storage API, preserving valuable debugging information across page reloads, browser sessions, and even browser restarts. This persistence proves invaluable when debugging intermittent issues that require comparing behavior across multiple sessions.
Advanced Filtering and Search transforms how you work with logs. Instead of scrolling through thousands of console entries, you can filter by log level, search by text, filter by source page, or create custom filter rules. This capability dramatically reduces the time spent finding relevant information in complex applications.
Cross-Page Logging allows you to capture console output from multiple pages simultaneously. If you are debugging a complex web application with multiple tabs or iframes, having a centralized log viewer provides a unified view of all console activity across your entire browsing session.
Log Export and Sharing enables you to save debugging sessions to files, share logs with team members, or include console output in bug reports. This feature streamlines collaboration and helps communicate issues more effectively.
Extension Architecture Overview
Before diving into code, let us understand the architecture of our console logger extension. A well-designed extension separates concerns across different components, each handling a specific responsibility.
The Content Script runs in the context of web pages and intercepts console methods. It captures all console output before it reaches the browser’s native console and forwards these messages to the extension’s background service worker or stores them for retrieval.
The Background Service Worker acts as the central hub, managing communication between content scripts and the extension popup. It also handles long-running tasks and coordinates data flow.
The Popup UI provides the visual interface where users view, filter, and manage captured logs. This is what users see when they click the extension icon in the toolbar.
Storage Layer uses Chrome’s storage API to persist logs across sessions, ensuring valuable debugging data survives browser restarts.
This architecture follows Chrome extension best practices and ensures your extension remains responsive while handling large volumes of console output.
Project Setup and Manifest Configuration
Every Chrome extension begins with the manifest file. Create a new directory for your extension and add the manifest.json file with the following configuration optimized for Manifest V3.
{
"manifest_version": 3,
"name": "Console Logger Pro",
"version": "1.0.0",
"description": "Advanced console logging extension with persistent storage and powerful filtering",
"permissions": [
"storage",
"activeTab",
"scripting"
],
"host_permissions": [
"<all_urls>"
],
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"],
"run_at": "document_start"
}
],
"background": {
"service_worker": "background.js"
}
}
The manifest configures several critical permissions. The storage permission enables persistent log storage. The scripting permission allows the extension to inject content scripts. The host_permissions with <all_urls> grants the extension access to console output from all websites, which is essential for a universal console logger.
Intercepting Console Methods in Content Scripts
The heart of our extension lies in the content script that intercepts console methods. This technique, sometimes called “monkey patching,” involves replacing the native console methods with our custom implementations that capture log data while preserving the original functionality.
Create the content.js file with the following implementation:
// Store original console methods
const originalConsole = {
log: console.log.bind(console),
error: console.error.bind(console),
warn: console.warn.bind(console),
info: console.info.bind(console),
debug: console.debug.bind(console),
table: console.table.bind(console)
};
// Capture log entry data
function captureLog(level, args) {
const entry = {
id: Date.now() + Math.random().toString(36).substr(2, 9),
timestamp: new Date().toISOString(),
level: level,
message: args.map(arg => {
try {
if (typeof arg === 'object') {
return JSON.stringify(arg, null, 2);
}
return String(arg);
} catch (e) {
return '[Circular or Non-serializable Object]';
}
}).join(' '),
page: window.location.href,
pageTitle: document.title
};
// Send to background script
chrome.runtime.sendMessage({
type: 'CONSOLE_LOG',
payload: entry
});
// Call original method to preserve normal behavior
originalConsole[level](...args);
}
// Override console methods
console.log = (...args) => captureLog('log', args);
console.error = (...args) => captureLog('error', args);
console.warn = (...args) => captureLog('warn', args);
console.info = (...args) => captureLog('info', args);
console.debug = (...args) => captureLog('debug', args);
// Handle console.table specially
console.table = (...args) => {
captureLog('table', args);
originalConsole.table(...args);
};
// Intercept uncaught errors
window.addEventListener('error', (event) => {
captureLog('error', [`Uncaught Error: ${event.message} at ${event.filename}:${event.lineno}`]);
});
// Intercept unhandled promise rejections
window.addEventListener('unhandledrejection', (event) => {
captureLog('error', [`Unhandled Promise Rejection: ${event.reason}`]);
});
This content script preserves the original console functionality while capturing all log entries. It serializes objects to JSON for storage, captures the page URL and title for context, and handles both regular errors and unhandled promise rejections.
Background Service Worker Implementation
The background service worker serves as the bridge between content scripts and the popup interface. It receives log entries from content scripts, stores them, and manages communication with the popup.
Create background.js with this implementation:
// Store logs in memory for quick access
let logs = [];
const MAX_LOGS = 10000;
// Handle messages from content scripts
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'CONSOLE_LOG') {
const logEntry = {
...message.payload,
tabId: sender.tab?.id
};
// Add to memory
logs.unshift(logEntry);
// Trim if exceeding limit
if (logs.length > MAX_LOGS) {
logs = logs.slice(0, MAX_LOGS);
}
// Persist to storage
chrome.storage.local.set({ consoleLogs: logs });
// Notify popup if open
chrome.runtime.sendMessage({
type: 'NEW_LOG',
payload: logEntry
}).catch(() => {
// Popup not open, ignore
});
}
if (message.type === 'GET_LOGS') {
sendResponse({ logs: logs });
}
if (message.type === 'CLEAR_LOGS') {
logs = [];
chrome.storage.local.set({ consoleLogs: [] });
sendResponse({ success: true });
}
return true;
});
// Load logs from storage on startup
chrome.storage.local.get('consoleLogs', (result) => {
if (result.consoleLogs) {
logs = result.consoleLogs;
}
});
This background script maintains an in-memory cache of logs for fast access while also persisting them to Chrome’s storage API. The in-memory cache provides instant loading when the popup opens, while storage ensures logs survive browser restarts.
Building the Popup Interface
The popup provides the user interface for viewing and managing captured logs. This HTML file displays the log viewer with filtering capabilities.
Create popup.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Console Logger Pro</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
width: 500px;
height: 600px;
background: #1e1e1e;
color: #d4d4d4;
}
.header {
padding: 12px 16px;
background: #2d2d2d;
border-bottom: 1px solid #3e3e3e;
display: flex;
justify-content: space-between;
align-items: center;
}
.title {
font-size: 14px;
font-weight: 600;
color: #569cd6;
}
.controls {
display: flex;
gap: 8px;
padding: 8px 16px;
background: #252526;
border-bottom: 1px solid #3e3e3e;
}
.filter-input {
flex: 1;
padding: 6px 10px;
background: #3c3c3c;
border: 1px solid #3e3e3e;
color: #d4d4d4;
border-radius: 4px;
font-size: 12px;
}
.filter-input:focus {
outline: none;
border-color: #569cd6;
}
.btn {
padding: 6px 12px;
background: #0e639c;
border: none;
color: white;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
}
.btn:hover {
background: #1177bb;
}
.btn-clear {
background: #c586c0;
}
.btn-clear:hover {
background: #d69fd6;
}
.log-list {
height: 480px;
overflow-y: auto;
padding: 8px;
}
.log-entry {
padding: 8px;
margin-bottom: 4px;
background: #2d2d2d;
border-radius: 4px;
font-size: 12px;
border-left: 3px solid #569cd6;
}
.log-entry.error {
border-left-color: #f14c4c;
}
.log-entry.warn {
border-left-color: #cca700;
}
.log-entry.info {
border-left-color: #3794ff;
}
.log-entry.debug {
border-left-color: #b5cea8;
}
.log-timestamp {
color: #858585;
font-size: 10px;
margin-bottom: 4px;
}
.log-message {
white-space: pre-wrap;
word-break: break-all;
}
.log-page {
color: #4ec9b0;
font-size: 10px;
margin-top: 4px;
}
.level-filters {
display: flex;
gap: 4px;
margin-bottom: 8px;
}
.level-btn {
padding: 4px 8px;
font-size: 10px;
background: #3c3c3c;
border: none;
color: #858585;
border-radius: 4px;
cursor: pointer;
}
.level-btn.active {
color: #d4d4d4;
}
.level-btn.log.active { background: #569cd6; }
.level-btn.error.active { background: #f14c4c; }
.level-btn.warn.active { background: #cca700; color: #1e1e1e; }
.level-btn.info.active { background: #3794ff; }
</style>
</head>
<body>
<div class="header">
<span class="title">Console Logger Pro</span>
<span id="logCount" style="font-size: 11px; color: #858585;">0 logs</span>
</div>
<div class="controls">
<input type="text" class="filter-input" id="filterInput" placeholder="Search logs...">
<button class="btn btn-clear" id="clearBtn">Clear</button>
<button class="btn" id="exportBtn">Export</button>
</div>
<div style="padding: 8px 16px;">
<div class="level-filters">
<button class="level-btn log active" data-level="log">Log</button>
<button class="level-btn error active" data-level="error">Error</button>
<button class="level-btn warn active" data-level="warn">Warn</button>
<button class="level-btn info active" data-level="info">Info</button>
<button class="level-btn debug active" data-level="debug">Debug</button>
</div>
</div>
<div class="log-list" id="logList"></div>
<script src="popup.js"></script>
</body>
</html>
The popup provides a dark-themed interface matching VS Code aesthetics. It includes search filtering, level filtering buttons, log entry display with timestamps and source page information, and clear/export functionality.
Popup JavaScript Logic
The popup JavaScript handles rendering logs, filtering, and user interactions. Create popup.js:
let allLogs = [];
let activeLevels = new Set(['log', 'error', 'warn', 'info', 'debug']);
let filterText = '';
// Initialize popup
document.addEventListener('DOMContentLoaded', async () => {
// Request logs from background
const response = await chrome.runtime.sendMessage({ type: 'GET_LOGS' });
allLogs = response.logs || [];
renderLogs();
// Listen for new logs
chrome.runtime.onMessage.addListener((message) => {
if (message.type === 'NEW_LOG') {
allLogs.unshift(message.payload);
renderLogs();
}
});
// Setup filter input
document.getElementById('filterInput').addEventListener('input', (e) => {
filterText = e.target.value.toLowerCase();
renderLogs();
});
// Setup clear button
document.getElementById('clearBtn').addEventListener('click', async () => {
await chrome.runtime.sendMessage({ type: 'CLEAR_LOGS' });
allLogs = [];
renderLogs();
});
// Setup export button
document.getElementById('exportBtn').addEventListener('click', exportLogs);
// Setup level filters
document.querySelectorAll('.level-btn').forEach(btn => {
btn.addEventListener('click', () => {
const level = btn.dataset.level;
if (activeLevels.has(level)) {
activeLevels.delete(level);
btn.classList.remove('active');
} else {
activeLevels.add(level);
btn.classList.add('active');
}
renderLogs();
});
});
});
function renderLogs() {
const logList = document.getElementById('logList');
const filteredLogs = allLogs.filter(log => {
// Filter by level
if (!activeLevels.has(log.level)) return false;
// Filter by text
if (filterText && !log.message.toLowerCase().includes(filtertext)) {
return false;
}
return true;
});
document.getElementById('logCount').textContent = `${filteredLogs.length} logs`;
logList.innerHTML = filteredLogs.map(log => `
<div class="log-entry ${log.level}">
<div class="log-timestamp">${new Date(log.timestamp).toLocaleString()}</div>
<div class="log-message">${escapeHtml(log.message)}</div>
<div class="log-page">${escapeHtml(log.page)}</div>
</div>
`).join('');
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function exportLogs() {
const data = JSON.stringify(allLogs, null, 2);
const blob = new Blob([data], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `console-logs-${new Date().toISOString()}.json`;
a.click();
URL.revokeObjectURL(url);
}
This JavaScript handles all the interactive features: filtering by log level, text search, clearing logs, and exporting to JSON. It also listens for real-time log updates from the background script.
Testing Your Extension
Now that you have built all the components, it is time to test your console logger extension. Follow these steps to load your extension in Chrome:
First, open Chrome and navigate to chrome://extensions/. Enable Developer Mode using the toggle in the top right corner. Click the “Load unpacked” button and select your extension directory. Your extension icon should appear in the Chrome toolbar.
Visit any website and open the browser console—you will see your extension capturing all console output. Click the extension icon to open the popup and view captured logs in your custom interface. Try triggering various console methods like console.log(), console.error(), or console.warn() to see them appear in your extension.
Test the filtering functionality by typing in the search box or clicking the level filter buttons. Verify that logs persist after reloading the page. Finally, try the export feature to save logs as a JSON file.
Advanced Features to Consider
While the basic console logger extension is fully functional, several enhancements can make it even more powerful. Consider adding these features as you continue development.
Real-time WebSocket Logging extends the interceptor to capture messages sent over WebSocket connections, providing visibility into network communication that console.log cannot capture.
Performance Metrics can track timing information for console operations, helping identify performance bottlenecks in your debugging workflow.
Custom Log Markers allow you to add special markers or labels to logs, making it easier to identify important entries during lengthy debugging sessions.
Log Comparison enables comparing logs from different sessions or time periods, which proves invaluable for debugging intermittent issues.
Cloud Sync could synchronize logs across devices using Chrome’s sync storage, though this requires additional implementation complexity.
Conclusion
Building a console logger Chrome extension demonstrates fundamental concepts in Chrome extension development while creating a genuinely useful tool for your development workflow. You have learned how to intercept JavaScript console methods in content scripts, communicate between extension components using the message passing API, persist data using Chrome’s storage system, and build a responsive popup interface.
This extension provides persistent logging, powerful filtering, and export capabilities that significantly enhance your debugging experience compared to the default browser console. The architecture is extensible—you can easily add new features like log comparison, cloud sync, or integration with external logging services.
The skills you have developed in this guide apply broadly to Chrome extension development. These same patterns appear in countless extensions, from simple utilities to complex enterprise tools. With this foundation, you are well-prepared to build additional Chrome extensions that solve your specific development challenges.
Additional Resources
To continue learning and developing your extension further, consult these valuable resources. The official Chrome Extension Documentation provides comprehensive information on extension APIs, best practices, and security considerations. The Chrome Extension Samples repository contains numerous example extensions demonstrating various features and patterns.
Manifest V3 migration guides help you understand the differences from Manifest V2 and ensure your extensions comply with current standards. Developer forums and communities provide support and inspiration for tackling specific challenges.
Remember to test your extension thoroughly across different websites and scenarios before distributing it to users. Pay attention to performance, especially when capturing large volumes of logs, and implement appropriate throttling or sampling if needed.