Chrome Extension Logging & Debugging — Developer Guide

6 min read

Extension Logging Guide

Introduction

Logging in Chrome extensions is challenging because logs are spread across multiple DevTools windows — background service worker, popup, options page, and content scripts each have their own console. This guide covers structured logging patterns that work reliably across all extension contexts.

1. Structured Log Format {#1-structured-log-format}

Use a consistent format across all contexts so logs are searchable and parseable:

type LogLevel = 'debug' | 'info' | 'warn' | 'error';

interface LogEntry {
  timestamp: string;    // ISO 8601 format
  context: string;      // 'background', 'popup', 'content', 'options'
  level: LogLevel;
  message: string;
  data?: unknown;      // Optional structured data
}

const LOG_LEVELS: Record<LogLevel, number> = {
  debug: 0,
  info: 1,
  warn: 2,
  error: 3,
};

2. Conditional Log Levels {#2-conditional-log-levels}

Only output logs at or above the configured level to reduce noise:

let currentLevel: LogLevel = 'info';

function setLogLevel(level: LogLevel) {
  currentLevel = level;
}

function log(level: LogLevel, message: string, data?: unknown) {
  if (LOG_LEVELS[level] >= LOG_LEVELS[currentLevel]) {
    const entry: LogEntry = {
      timestamp: new Date().toISOString(),
      context: 'background', // Set appropriately per context
      level,
      message,
      data,
    };
    console.log(JSON.stringify(entry));
  }
}

3. Console Styling with Context Labels {#3-console-styling-with-context-labels}

Use %c formatting to add colored context labels that make logs scannable:

const CONTEXT_STYLES: Record<string, string> = {
  background: 'background: #222; color: #bada55',
  popup: 'background: #222; color: #61dafb',
  content: 'background: #222; color: #ff6b6b',
  options: 'background: #222; color: #ffd93d',
};

function styledLog(context: string, message: string, ...args: unknown[]) {
  console.log(`%c${context}%c ${message}`, CONTEXT_STYLES[context] || '', ...args);
}

4. Centralizing Logs to Background {#4-centralizing-logs-to-background}

Content scripts and popup logs are in separate DevTools windows. Send logs to the background service worker for unified viewing:

// In content script or popup
function centralizeLog(entry: LogEntry) {
  chrome.runtime.sendMessage({ type: 'LOG_ENTRY', payload: entry });
}

// In background service worker
chrome.runtime.onMessage.addListener((message) => {
  if (message.type === 'LOG_ENTRY') {
    console.log(JSON.stringify(message.payload));
  }
});

5. Persistent Log Storage with Circular Buffer {#5-persistent-log-storage-with-circular-buffer}

Store recent logs in chrome.storage.session so they’re available even after service worker restarts:

const MAX_LOG_ENTRIES = 500;

async function persistLog(entry: LogEntry) {
  const { logs = [] } = await chrome.storage.session.get('logs');
  logs.push(entry);
  if (logs.length > MAX_LOG_ENTRIES) {
    logs.shift(); // Remove oldest
  }
  await chrome.storage.session.set({ logs });
}

6. Debug Mode Toggle {#6-debug-mode-toggle}

Enable verbose logging only when needed to diagnose issues:

let isDebugMode = false;

async function initLogging() {
  const { debugMode } = await chrome.storage.local.get('debugMode');
  isDebugMode = debugMode ?? false;
  setLogLevel(isDebugMode ? 'debug' : 'info');
}

chrome.storage.onChanged.addListener((changes) => {
  if (changes.debugMode) {
    isDebugMode = changes.debugMode.newValue;
    setLogLevel(isDebugMode ? 'debug' : 'info');
  }
});

7. Log Export for Bug Reports {#7-log-export-for-bug-reports}

Allow users to export logs as a JSON file for debugging issues:

async function exportLogs() {
  const { logs } = await chrome.storage.session.get('logs');
  const blob = new Blob([JSON.stringify(logs, null, 2)], { type: 'application/json' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = `extension-logs-${Date.now()}.json`;
  a.click();
}

8. Production Logging: Strip Debug Logs {#8-production-logging-strip-debug-logs}

Use build tools to remove debug logs in production. With esbuild:

// esbuild.config.js
require('esbuild').build({
  entryPoints: ['src/index.ts'],
  bundle: true,
  minify: true,
  define: {
    'process.env.NODE_ENV': '"production"',
  },
  // After minification, console.debug calls disappear
}).catch(() => process.exit(1));

Or use a custom plugin to strip all logging calls in production builds.

9. Viewing Logs Across Contexts {#9-viewing-logs-across-contexts}

10. Performance Considerations {#10-performance-considerations}

Part of the Chrome Extension Guide by theluckystrike. Built at zovo.one.