{
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
}
]
}
}
Last active
January 7, 2026 20:45
-
-
Save billywhizz/b090d3108c5ce5fc87c4c984ae1200f8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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)); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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