Skip to content

Allow hooking into the console #252

@remcohaszing

Description

@remcohaszing

What problem are you trying to solve?

There are several tools and integrations that patch the global console object. Arguably, some of those are actually good use cases. However, patching the console definitely has downsides. Notably, the logging location information is replaced. Some examples of tools that do this are Sentry for error tracking and I’m not sure why React Developer Tools patches the console.

Another problem is that an application may want full control over the output. This is especially often true for CLI applications. For example, language servers can communicate over stdio. If a language server has a plugin system, a plugin could corrupt the communication using console output. I worked around this in microsoft/vscode-languageserver-node#1301 by patching console, but I consider it quite a hacky solution.

What solutions exist today?

  1. Patching the console.
  2. Using a custom logging library. However, that doesn’t solve the problems above.

How would you solve it?

I would add a new type of event, let’s call it log. Inspired by FetchEvent, an event handler can intercept, cancel, and modify console events.

I imagine the event could look something like this:

interface LogEvent extends Event {
  /** Call this to stop the message from being logged */
  preventDefault(): undefined

  /** The logging level that was used */
  level: 'debug' | 'log' | 'warn' | 'error'

  /** The message that was logged */
  message: unknown

  /** The formatting of the logging message */
  format: 'dir' | 'table' | undefined

  /** The real URL of the source from which the message was logged */
  url: string

  /** The real line number of the source from which the message was logged */
  line: number

  /** The URL of the source from which the message was logged after applying source maps */
  originalUrl?: string

  /** The line number of the source from which the message was logged after applying source maps */
  originalLine?: number

  /** The trace if available */
  trace?: string
}

For example, I imagine a Sentry integration could look something like this:

console.addEventListener('log', (event) => {
  captureLogEvent(event)
})

Silence logging:

console.addEventListener('log', (event) => {
  event.preventDefault()
})

An alternative custom formatter in Node.js:

import { fileURLToPath } from 'node:url'

console.addEventListener('log', (event) => {
  event.respondWith({
    ...event,
    message: `${event.level} ${fileURLToPath(event.url)}:${event.line}: ${event.message}`
  })
})

An alternative custom formatter in Node.js:

import { fileURLToPath } from 'node:url'

console.addEventListener('log', (event) => {
  event.preventDefault()
  process.stdout.write(`${event.level} ${fileURLToPath(event.url)}:${event.line}: ${event.message}`)
})

Anything else?

Related issues:

Metadata

Metadata

Assignees

No one assigned

    Labels

    addition/proposalNew features or enhancementsneeds implementer interestMoving the issue forward requires implementers to express interest

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions