Skip to content

Instantly share code, notes, and snippets.

@billywhizz
Last active January 7, 2026 20:45
Show Gist options
  • Select an option

  • Save billywhizz/b090d3108c5ce5fc87c4c984ae1200f8 to your computer and use it in GitHub Desktop.

Select an option

Save billywhizz/b090d3108c5ce5fc87c4c984ae1200f8 to your computer and use it in GitHub Desktop.
{
  type: "log",
  args: [
    {
      type: "string",
      value: "something interesting"
    }
  ],
  executionContextId: 1,
  timestamp: 1767748381415.399,
  stackTrace: {
    callFrames: [
      {
        functionName: <empty>,
        scriptId: "86",
        url: "file:///dev/shm/omg.js",
        lineNumber: 14,
        columnNumber: 10
      },
      {
        functionName: "listOnTimeout",
        scriptId: "10",
        url: "node:internal/timers",
        lineNumber: 604,
        columnNumber: 16
      },
      {
        functionName: "processTimers",
        scriptId: "10",
        url: "node:internal/timers",
        lineNumber: 540,
        columnNumber: 6
      }
    ]
  }
}{
  type: "debug",
  args: [
    {
      type: "object",
      className: "Object",
      description: "Object",
      objectId: "3316659157331527570.1.1",
      preview: {
        type: "object",
        description: "Object",
        overflow: false,
        properties: []
      }
    }
  ],
  executionContextId: 1,
  timestamp: 1767748381416.805,
  stackTrace: {
    callFrames: [
      {
        functionName: <empty>,
        scriptId: "86",
        url: "file:///dev/shm/omg.js",
        lineNumber: 15,
        columnNumber: 10
      },
      {
        functionName: "listOnTimeout",
        scriptId: "10",
        url: "node:internal/timers",
        lineNumber: 604,
        columnNumber: 16
      },
      {
        functionName: "processTimers",
        scriptId: "10",
        url: "node:internal/timers",
        lineNumber: 540,
        columnNumber: 6
      }
    ]
  }
}{
  type: "dir",
  args: [
    {
      type: "object",
      className: "Object",
      description: "Object",
      objectId: "3316659157331527570.1.2",
      preview: {
        type: "object",
        description: "Object",
        overflow: true,
        properties: [
          {
            name: "node",
            type: "string",
            value: "24.12.0"
          },
          {
            name: "acorn",
            type: "string",
            value: "8.15.0"
          },
          {
            name: "ada",
            type: "string",
            value: "3.3.0"
          },
          {
            name: "amaro",
            type: "string",
            value: "1.1.5"
          },
          {
            name: "ares",
            type: "string",
            value: "1.34.5"
          }
        ]
      }
    }
  ],
  executionContextId: 1,
  timestamp: 1767748381417.297,
  stackTrace: {
    callFrames: [
      {
        functionName: <empty>,
        scriptId: "86",
        url: "file:///dev/shm/omg.js",
        lineNumber: 16,
        columnNumber: 10
      },
      {
        functionName: "listOnTimeout",
        scriptId: "10",
        url: "node:internal/timers",
        lineNumber: 604,
        columnNumber: 16
      },
      {
        functionName: "processTimers",
        scriptId: "10",
        url: "node:internal/timers",
        lineNumber: 540,
        columnNumber: 6
      }
    ]
  }
}{
  type: "info",
  args: [
    {
      type: "string",
      value: "cats are cool"
    }
  ],
  executionContextId: 1,
  timestamp: 1767748381417.812,
  stackTrace: {
    callFrames: [
      {
        functionName: <empty>,
        scriptId: "86",
        url: "file:///dev/shm/omg.js",
        lineNumber: 17,
        columnNumber: 10
      },
      {
        functionName: "listOnTimeout",
        scriptId: "10",
        url: "node:internal/timers",
        lineNumber: 604,
        columnNumber: 16
      },
      {
        functionName: "processTimers",
        scriptId: "10",
        url: "node:internal/timers",
        lineNumber: 540,
        columnNumber: 6
      }
    ]
  }
}{
  type: "table",
  args: [
    {
      type: "object",
      subtype: "array",
      className: "Array",
      description: "Array(4)",
      objectId: "3316659157331527570.1.3",
      preview: {
        type: "object",
        subtype: "array",
        description: "Array(4)",
        overflow: false,
        properties: [
          {
            name: "0",
            type: "number",
            value: "0"
          },
          {
            name: "1",
            type: "number",
            value: "1"
          },
          {
            name: "2",
            type: "number",
            value: "2"
          },
          {
            name: "3",
            type: "number",
            value: "3"
          }
        ]
      }
    }
  ],
  executionContextId: 1,
  timestamp: 1767748381417.977,
  stackTrace: {
    callFrames: [
      {
        functionName: <empty>,
        scriptId: "86",
        url: "file:///dev/shm/omg.js",
        lineNumber: 18,
        columnNumber: 10
      },
      {
        functionName: "listOnTimeout",
        scriptId: "10",
        url: "node:internal/timers",
        lineNumber: 604,
        columnNumber: 16
      },
      {
        functionName: "processTimers",
        scriptId: "10",
        url: "node:internal/timers",
        lineNumber: 540,
        columnNumber: 6
      }
    ]
  }
}{
  type: "log",
  args: [
    {
      type: "string",
      value: "┌─────────┬────────┐\n│ (index) │ Values │\n├─────────┼────────┤\n│ 0       │ 0      │\n│ 1       │ 1      │\n│ 2       │ 2      │\n│ 3       │ 3      │\n└─────────┴────────┘"
    }
  ],
  executionContextId: 1,
  timestamp: 1767748381418.795,
  stackTrace: {
    callFrames: [
      {
        functionName: "final",
        scriptId: "55",
        url: "node:internal/console/constructor",
        lineNumber: 561,
        columnNumber: 33
      },
      {
        functionName: "table",
        scriptId: "55",
        url: "node:internal/console/constructor",
        lineNumber: 663,
        columnNumber: 11
      },
      {
        functionName: <empty>,
        scriptId: "86",
        url: "file:///dev/shm/omg.js",
        lineNumber: 18,
        columnNumber: 10
      },
      {
        functionName: "listOnTimeout",
        scriptId: "10",
        url: "node:internal/timers",
        lineNumber: 604,
        columnNumber: 16
      },
      {
        functionName: "processTimers",
        scriptId: "10",
        url: "node:internal/timers",
        lineNumber: 540,
        columnNumber: 6
      }
    ]
  }
}{
  type: "warning",
  args: [
    {
      type: "string",
      value: "it's not going to be good"
    }
  ],
  executionContextId: 1,
  timestamp: 1767748381419.215,
  stackTrace: {
    callFrames: [
      {
        functionName: <empty>,
        scriptId: "86",
        url: "file:///dev/shm/omg.js",
        lineNumber: 20,
        columnNumber: 10
      },
      {
        functionName: "listOnTimeout",
        scriptId: "10",
        url: "node:internal/timers",
        lineNumber: 604,
        columnNumber: 16
      },
      {
        functionName: "processTimers",
        scriptId: "10",
        url: "node:internal/timers",
        lineNumber: 540,
        columnNumber: 6
      }
    ]
  }
}{
  type: "error",
  args: [
    {
      type: "object",
      subtype: "error",
      className: "Error",
      description: "Error: The bad thing happened\n    at Timeout._onTimeout (file:///dev/shm/omg.js:22:17)\n    at listOnTimeout (node:internal/timers:605:17)\n    at process.processTimers (node:internal/timers:541:7)",
      objectId: "3316659157331527570.1.4",
      preview: {
        type: "object",
        subtype: "error",
        description: "Error: The bad thing happened\n    at Timeout._onTimeout (file:///dev/shm/omg.js:22:17)\n    at listOnTimeout (node:internal/timers:605:17)\n    at process.processTimers (node:internal/timers:541:7)",
        overflow: false,
        properties: [
          {
            name: "stack",
            type: "string",
            value: "Error: The bad thing happened\n    at Timeout._onTi…rocess.processTimers (node:internal/timers:541:7)"
          },
          {
            name: "message",
            type: "string",
            value: "The bad thing happened"
          }
        ]
      }
    }
  ],
  executionContextId: 1,
  timestamp: 1767748381419.422,
  stackTrace: {
    callFrames: [
      {
        functionName: <empty>,
        scriptId: "86",
        url: "file:///dev/shm/omg.js",
        lineNumber: 21,
        columnNumber: 10
      },
      {
        functionName: "listOnTimeout",
        scriptId: "10",
        url: "node:internal/timers",
        lineNumber: 604,
        columnNumber: 16
      },
      {
        functionName: "processTimers",
        scriptId: "10",
        url: "node:internal/timers",
        lineNumber: 540,
        columnNumber: 6
      }
    ]
  }
}
#include "env.h"
#include "inspector/inspector_agent.h"
#include "inspector/inspector_session.h"
using node::Environment;
using node::inspector::Agent;
using node::inspector::InspectorSession;
class MyInspectorDelegate : public InspectorSession::Delegate {
public:
void SendMessageToFrontend(
const v8_inspector::StringView& message) override {
// write message to unix socket
}
void Disconnect() override {
// handle disconnect
}
};
void StartInspectorBridge(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Agent* agent = env->inspector_agent();
if (!agent) return;
auto delegate = std::make_unique<MyInspectorDelegate>();
auto session = agent->Connect(std::move(delegate));
}
#include <node.h>
#include <env.h>
#include <inspector/inspector_agent.h>
#include <inspector/inspector_session.h>
#include <v8-inspector.h>
#include <fstream>
#include <memory>
#include <string>
using node::Environment;
using node::inspector::Agent;
using node::inspector::InspectorSession;
using v8_inspector::StringView;
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Value;
namespace {
std::unique_ptr<InspectorSession> g_session;
std::ofstream g_log;
/**
* Inspector delegate: receives ALL protocol messages
*/
class MyInspectorDelegate : public InspectorSession::Delegate {
public:
void SendMessageToFrontend(const StringView& message) override {
// Convert StringView → std::string
std::string json;
if (message.is8Bit()) {
json.assign(reinterpret_cast<const char*>(message.characters8()),
message.length());
} else {
const uint16_t* chars = message.characters16();
for (size_t i = 0; i < message.length(); ++i) {
json.push_back(static_cast<char>(chars[i]));
}
}
// VERY IMPORTANT:
// This receives *everything* (events + responses)
//
// We filter only Runtime.consoleAPICalled here,
// just like your JS example.
if (json.find("\"method\":\"Runtime.consoleAPICalled\"") != std::string::npos) {
g_log << json << std::endl;
g_log.flush();
}
}
void Disconnect() override {
g_log << "Inspector disconnected\n";
g_log.flush();
}
};
std::unique_ptr<MyInspectorDelegate> g_delegate;
/**
* Helper: send raw CDP JSON to inspector
*/
void SendProtocolMessage(const std::string& json) {
if (!g_session) return;
StringView view(reinterpret_cast<const uint8_t*>(json.data()), json.size());
g_session->DispatchProtocolMessage(view);
}
/**
* JS-callable: startInspector("out.log")
*/
void StartInspector(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
Environment* env = Environment::GetCurrent(isolate);
if (!env || !env->inspector_agent()) return;
const std::string logfile = "out.log";
g_log.open(logfile, std::ios::out | std::ios::app);
Agent* agent = env->inspector_agent();
g_delegate = std::make_unique<MyInspectorDelegate>();
g_session = agent->Connect(std::move(g_delegate));
// Equivalent of:
// session.post('Runtime.enable')
SendProtocolMessage(R"({
"id": 1,
"method": "Runtime.enable"
})");
// Equivalent of:
// session.post('Runtime.runIfWaitingForDebugger')
SendProtocolMessage(R"({
"id": 2,
"method": "Runtime.runIfWaitingForDebugger"
})");
}
/**
* JS-callable: stopInspector()
*/
void StopInspector(const FunctionCallbackInfo<Value>& args) {
g_session.reset();
g_delegate.reset();
if (g_log.is_open()) {
g_log.close();
}
}
} // namespace
NODE_MODULE_INIT(/* exports, module, context */) {
NODE_SET_METHOD(exports, "startInspector", StartInspector);
NODE_SET_METHOD(exports, "stopInspector", StopInspector);
}
import inspector from 'node:inspector';
import * as fs from 'node:fs';
import { stringify } from './stringify.js';
const logger = await fs.createWriteStream('out.log');
const session = new inspector.Session();
session.connect();
session.post('Runtime.enable');
session.post('Runtime.runIfWaitingForDebugger');
session.on('Runtime.consoleAPICalled', msg => {
logger.write(stringify(msg.params));
});
setInterval(() => {
console.log('something interesting');
console.debug({});
console.dir(process.versions);
console.info('cats are cool');
console.table([0, 1, 2, 3]);
console.time();
console.warn('it\'s not going to be good');
console.error(new Error('The bad thing happened'));
}, 5000);
const AD = '\u001b[0m' // ANSI Default
const AG = '\u001b[32m' // ANSI Green
const AY = '\u001b[33m' // ANSI Yellow
const AB = '\u001b[34m' // ANSI Blue
const AM = '\u001b[35m' // ANSI Magenta
const AC = '\u001b[36m' // ANSI Cyan
function replacer (k, v) {
if (typeof v === 'bigint') {
return Number(v)
}
if (!v) {
if (typeof v !== 'boolean' && typeof v !== 'number') return '<empty>'
}
if (v.constructor && v.constructor.name === 'Error') {
return { message: v.message, stack: v.stack }
}
if (v.constructor && v.constructor.name === 'ArrayBuffer') {
return 'ArrayBuffer ' + v.byteLength
}
if (v.constructor && v.constructor.name === 'Uint8Array') {
return 'Uint8Array ' + v.length
}
if (v.constructor && v.constructor.name === 'Uint32Array') {
return 'Uint32Array ' + v.length
}
if (v.constructor && v.constructor.name === 'DataView') {
return 'DataView ' + v.byteLength
}
if (v.constructor && v.constructor.name === 'Function') {
return `${v.name} (...${v.length})`
}
if (v.constructor && v.constructor.name === 'AsyncFunction') {
return `async ${v.name} (...${v.length})`
}
return v
}
const stringify = (o, sp = ' ') => {
const text = JSON.stringify(o, replacer, sp)
if (!text) return
return text
.replace(/([{}])/g, `${AM}$1${AD}`)
.replace(/\[(.+)\]/g, `${AG}[${AD}$1${AG}]${AD}`)
.replace(/\s{8}"(.+)":/g, ` ${AB}$1${AD}:`)
.replace(/\s{6}"(.+)":/g, ` ${AC}$1${AD}:`)
.replace(/\s{4}"(.+)":/g, ` ${AG}$1${AD}:`)
.replace(/\s\s"(.+)":/g, ` ${AY}$1${AD}:`)
.replace(/"<empty>"/g, `${AC}<empty>${AD}`)
.replace(/"<repeat>"/g, `${AC}<repeat>${AD}`)
}
export { stringify }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment