debughub 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -0
- package/dist/cli.js +54 -0
- package/dist/commands/clear.js +65 -0
- package/dist/commands/doctor.js +96 -0
- package/dist/commands/install.js +88 -0
- package/dist/commands/search.js +106 -0
- package/dist/commands/server.js +94 -0
- package/dist/commands/start.js +123 -0
- package/dist/commands/stop.js +68 -0
- package/dist/commands/tail.js +97 -0
- package/dist/commands/verify.js +91 -0
- package/dist/vendor/1.0.0/MANIFEST.json +13 -0
- package/dist/vendor/1.0.0/README.md +26 -0
- package/dist/vendor/1.0.0/csharp/DebugProbe.cs +97 -0
- package/dist/vendor/1.0.0/go/debug_probe.go +81 -0
- package/dist/vendor/1.0.0/http/EVENT_CONTRACT.md +54 -0
- package/dist/vendor/1.0.0/http/event.example.json +13 -0
- package/dist/vendor/1.0.0/java/DebugProbe.java +114 -0
- package/dist/vendor/1.0.0/php/DebugProbe.php +68 -0
- package/dist/vendor/1.0.0/python/debugProbe.py +54 -0
- package/dist/vendor/1.0.0/rust/debug_probe.rs +194 -0
- package/dist/vendor/1.0.0/ts/debugProbe.browser.ts +45 -0
- package/dist/vendor/1.0.0/ts/debugProbe.ts +63 -0
- package/docs/DEBUG_MODE_PROMPT.md +24 -0
- package/docs/INSTALLING_INTO_A_REPO.md +52 -0
- package/docs/RUNTIME_PREREQS.md +67 -0
- package/package.json +47 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.stop = stop;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
function stop() {
|
|
40
|
+
const cwd = process.cwd();
|
|
41
|
+
const stateFile = path.join(cwd, '.debughub', 'state.json');
|
|
42
|
+
if (!fs.existsSync(stateFile)) {
|
|
43
|
+
console.log('No DebugHub collector is currently running (state.json not found).');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const state = JSON.parse(fs.readFileSync(stateFile, 'utf-8'));
|
|
48
|
+
if (state.pid) {
|
|
49
|
+
try {
|
|
50
|
+
process.kill(state.pid);
|
|
51
|
+
console.log(`Stopped collector process ${state.pid}.`);
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
if (e.code === 'ESRCH') {
|
|
55
|
+
console.log(`Process ${state.pid} was not running.`);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
console.error(`Failed to stop process ${state.pid}:`, e.message);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
console.log('Could not read state.json or parse PID.');
|
|
65
|
+
}
|
|
66
|
+
fs.unlinkSync(stateFile);
|
|
67
|
+
console.log('Cleared state.');
|
|
68
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.tail = tail;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
function tail(options) {
|
|
40
|
+
const cwd = process.cwd();
|
|
41
|
+
const debughubDir = path.join(cwd, '.debughub');
|
|
42
|
+
const maxLines = parseInt(options.n, 10);
|
|
43
|
+
let sessionId = options.session;
|
|
44
|
+
if (!sessionId) {
|
|
45
|
+
const stateFile = path.join(debughubDir, 'state.json');
|
|
46
|
+
if (fs.existsSync(stateFile)) {
|
|
47
|
+
try {
|
|
48
|
+
const state = JSON.parse(fs.readFileSync(stateFile, 'utf-8'));
|
|
49
|
+
sessionId = state.session;
|
|
50
|
+
}
|
|
51
|
+
catch (e) { }
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (!sessionId) {
|
|
55
|
+
console.error('Could not determine active session ID. Provide --session or run `debughub start`.');
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
const outFile = path.join(debughubDir, 'out', `${sessionId}.jsonl`);
|
|
59
|
+
if (!fs.existsSync(outFile)) {
|
|
60
|
+
console.log(`No events yet in session ${sessionId}`);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
// Quick read logic (assumes file is small enough for memory for a debug tool)
|
|
64
|
+
const content = fs.readFileSync(outFile, 'utf-8');
|
|
65
|
+
const lines = content.trim().split('\n').filter(Boolean);
|
|
66
|
+
if (lines.length === 0) {
|
|
67
|
+
console.log(`No events yet in session ${sessionId}`);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const toShow = lines.slice(-maxLines);
|
|
71
|
+
if (options.json) {
|
|
72
|
+
toShow.forEach(l => console.log(l));
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
toShow.forEach(l => {
|
|
76
|
+
try {
|
|
77
|
+
const parsed = JSON.parse(l);
|
|
78
|
+
const ts = new Date(parsed.ts).toLocaleTimeString();
|
|
79
|
+
const levelColorStr = parsed.level === 'error' ? '\x1b[31m[ERROR]\x1b[0m' :
|
|
80
|
+
parsed.level === 'warn' ? '\x1b[33m[WARN]\x1b[0m' :
|
|
81
|
+
'\x1b[36m[INFO]\x1b[0m';
|
|
82
|
+
let meta = '';
|
|
83
|
+
if (parsed.hypothesisId)
|
|
84
|
+
meta += ` [H:${parsed.hypothesisId}]`;
|
|
85
|
+
if (parsed.loc)
|
|
86
|
+
meta += ` [L:${parsed.loc}]`;
|
|
87
|
+
console.log(`[${ts}] ${levelColorStr} ${parsed.label}${meta}`);
|
|
88
|
+
if (parsed.data !== null) {
|
|
89
|
+
console.dir(parsed.data, { depth: 4, colors: true });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (e) {
|
|
93
|
+
console.log(l);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.verify = verify;
|
|
37
|
+
exports.verifyVendorFiles = verifyVendorFiles;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const crypto = __importStar(require("crypto"));
|
|
41
|
+
function verify() {
|
|
42
|
+
const cwd = process.cwd();
|
|
43
|
+
console.log('Verifying DebugHub installation files...');
|
|
44
|
+
const isValid = verifyVendorFiles(cwd);
|
|
45
|
+
if (isValid) {
|
|
46
|
+
console.log('✅ Integrity check passed: all installed vendor files match the official manifest.');
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
console.error('❌ Integrity check failed: one or more files have been modified or are missing.');
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function verifyVendorFiles(cwd) {
|
|
54
|
+
const currentLink = path.join(cwd, '.debughub', 'vendor', 'current');
|
|
55
|
+
if (!fs.existsSync(currentLink)) {
|
|
56
|
+
console.error(`Not found: ${currentLink}. Is DebugHub installed in this repo?`);
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
const version = fs.readFileSync(currentLink, 'utf-8').trim();
|
|
60
|
+
const vendorDir = path.join(cwd, '.debughub', 'vendor', version);
|
|
61
|
+
const manifestFile = path.join(vendorDir, 'MANIFEST.json');
|
|
62
|
+
if (!fs.existsSync(manifestFile)) {
|
|
63
|
+
console.error(`Missing MANIFEST.json in ${vendorDir}`);
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
let manifest;
|
|
67
|
+
try {
|
|
68
|
+
manifest = JSON.parse(fs.readFileSync(manifestFile, 'utf-8'));
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
console.error('Failed to parse MANIFEST.json');
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
let isValid = true;
|
|
75
|
+
for (const [relPath, expectedHash] of Object.entries(manifest)) {
|
|
76
|
+
// relPath in manifest uses forward slashes
|
|
77
|
+
const targetFile = path.resolve(vendorDir, relPath);
|
|
78
|
+
if (!fs.existsSync(targetFile)) {
|
|
79
|
+
console.error(`File missing: ${relPath}`);
|
|
80
|
+
isValid = false;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const content = fs.readFileSync(targetFile);
|
|
84
|
+
const actualHash = crypto.createHash('sha256').update(content).digest('hex');
|
|
85
|
+
if (actualHash !== expectedHash) {
|
|
86
|
+
console.error(`Checksum mismatch in: ${relPath}`);
|
|
87
|
+
isValid = false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return isValid;
|
|
91
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"README.md": "d02d9b1b96e05f6592dfda76694665b71617eb997cebed61cedc407cf73a757d",
|
|
3
|
+
"csharp/DebugProbe.cs": "da0dad4e3fa65a57b68a37fe14313ced4ae9f32545f9a74ec15b86ac003bcf58",
|
|
4
|
+
"go/debug_probe.go": "c808270aced80c895b3145851bbdd36deee85920f9d599c8540531be43fe6e51",
|
|
5
|
+
"http/EVENT_CONTRACT.md": "b60e71b95723a19dd370b67bc440945a8d3bedaf5dfbacf512973893bce1030c",
|
|
6
|
+
"http/event.example.json": "e14bfc9dbde2025137ac247d6f466dc0f6185ba12a216c3970f32016b1e8da01",
|
|
7
|
+
"java/DebugProbe.java": "8777f46131f7c1148ec1a78f59d5477b6f4591fcef4d7f5d5e0eec97fae1534a",
|
|
8
|
+
"php/DebugProbe.php": "4b1c391cf3e2c7a32c1115504f54411906b14509011d294724d22b869d0275c6",
|
|
9
|
+
"python/debugProbe.py": "fbe195d941f456a9eada52e54a09adbbb7dd132623e3476e5cb69f865ed1ea5f",
|
|
10
|
+
"rust/debug_probe.rs": "0385485602e9e058cff577a62f6caa2e3f2fb6c0d2cb089927884ab1b6a66edc",
|
|
11
|
+
"ts/debugProbe.browser.ts": "ba82e5abfac8d6105e5d6831f24fada36bf00af9824028f793ba12ccac290977",
|
|
12
|
+
"ts/debugProbe.ts": "a79a3a1483ac246f0ffd3a2b32938e97ae4ce227bab42a6f5feccf57bd8e6788"
|
|
13
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# DebugHub Helpers
|
|
2
|
+
|
|
3
|
+
These files are provided by [DebugHub](https://github.com/debughub/debughub), an agent-agnostic debugging proxy.
|
|
4
|
+
|
|
5
|
+
## Supported Runtimes
|
|
6
|
+
|
|
7
|
+
- TypeScript/Node: `helpers/ts/debugProbe.ts`
|
|
8
|
+
- Browser TS/JS: `helpers/ts/debugProbe.browser.ts`
|
|
9
|
+
- Java: `helpers/java/DebugProbe.java`
|
|
10
|
+
- Python: `helpers/python/debugProbe.py`
|
|
11
|
+
- Rust: `helpers/rust/debug_probe.rs`
|
|
12
|
+
- PHP: `helpers/php/DebugProbe.php`
|
|
13
|
+
- Go: `helpers/go/debug_probe.go`
|
|
14
|
+
- C#: `helpers/csharp/DebugProbe.cs`
|
|
15
|
+
|
|
16
|
+
## HTTP Contract
|
|
17
|
+
|
|
18
|
+
All helpers follow the shared HTTP contract in `helpers/http/EVENT_CONTRACT.md`.
|
|
19
|
+
|
|
20
|
+
At a high level:
|
|
21
|
+
|
|
22
|
+
- Helpers no-op unless `DEBUGHUB_ENABLED=1` and `DEBUGHUB_SESSION` is set.
|
|
23
|
+
- If `DEBUGHUB_ENDPOINT` is set, helpers do best-effort `POST` to `{endpoint}/event`.
|
|
24
|
+
- Helpers never throw into host application code.
|
|
25
|
+
|
|
26
|
+
See `helpers/http/event.example.json` for a reference payload.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Collections.Generic;
|
|
3
|
+
using System.Net.Http;
|
|
4
|
+
using System.Text;
|
|
5
|
+
using System.Text.Json;
|
|
6
|
+
|
|
7
|
+
public static class DebugProbe
|
|
8
|
+
{
|
|
9
|
+
private static readonly HttpClient Client = new HttpClient
|
|
10
|
+
{
|
|
11
|
+
Timeout = TimeSpan.FromSeconds(2)
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
public static void Probe(string label, object? data = null, Dictionary<string, object?>? meta = null)
|
|
15
|
+
{
|
|
16
|
+
try
|
|
17
|
+
{
|
|
18
|
+
if (Environment.GetEnvironmentVariable("DEBUGHUB_ENABLED") != "1")
|
|
19
|
+
{
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
var sessionId = Environment.GetEnvironmentVariable("DEBUGHUB_SESSION");
|
|
24
|
+
if (string.IsNullOrWhiteSpace(sessionId))
|
|
25
|
+
{
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
var endpoint = Environment.GetEnvironmentVariable("DEBUGHUB_ENDPOINT");
|
|
30
|
+
if (string.IsNullOrWhiteSpace(endpoint))
|
|
31
|
+
{
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
var level = "info";
|
|
36
|
+
string? hypothesisId = null;
|
|
37
|
+
string? loc = null;
|
|
38
|
+
object? tags = null;
|
|
39
|
+
|
|
40
|
+
if (meta != null)
|
|
41
|
+
{
|
|
42
|
+
if (meta.TryGetValue("level", out var levelObj) && levelObj is string levelStr && IsAllowedLevel(levelStr))
|
|
43
|
+
{
|
|
44
|
+
level = levelStr;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (meta.TryGetValue("hypothesisId", out var hypothesisObj) && hypothesisObj is string h)
|
|
48
|
+
{
|
|
49
|
+
hypothesisId = h;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (meta.TryGetValue("loc", out var locObj) && locObj is string l)
|
|
53
|
+
{
|
|
54
|
+
loc = l;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (meta.TryGetValue("tags", out var tagsObj))
|
|
58
|
+
{
|
|
59
|
+
tags = tagsObj;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
var payloadObject = new Dictionary<string, object?>
|
|
64
|
+
{
|
|
65
|
+
["ts"] = DateTime.UtcNow.ToString("o"),
|
|
66
|
+
["sessionId"] = sessionId,
|
|
67
|
+
["label"] = label,
|
|
68
|
+
["data"] = data,
|
|
69
|
+
["hypothesisId"] = hypothesisId,
|
|
70
|
+
["loc"] = loc,
|
|
71
|
+
["level"] = level,
|
|
72
|
+
["tags"] = tags,
|
|
73
|
+
["runtime"] = "csharp"
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
var payload = JsonSerializer.Serialize(payloadObject);
|
|
77
|
+
var target = endpoint.TrimEnd('/') + "/event";
|
|
78
|
+
|
|
79
|
+
using var content = new StringContent(payload, Encoding.UTF8, "application/json");
|
|
80
|
+
Client.PostAsync(target, content).GetAwaiter().GetResult();
|
|
81
|
+
}
|
|
82
|
+
catch
|
|
83
|
+
{
|
|
84
|
+
// Never throw from helper
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public static void DebugProbeEvent(string label, object? data = null, Dictionary<string, object?>? meta = null)
|
|
89
|
+
{
|
|
90
|
+
Probe(label, data, meta);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private static bool IsAllowedLevel(string level)
|
|
94
|
+
{
|
|
95
|
+
return level == "info" || level == "warn" || level == "error";
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"encoding/json"
|
|
6
|
+
"net/http"
|
|
7
|
+
"os"
|
|
8
|
+
"strings"
|
|
9
|
+
"time"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
type DebugProbeMeta struct {
|
|
13
|
+
HypothesisID *string `json:"hypothesisId"`
|
|
14
|
+
Loc *string `json:"loc"`
|
|
15
|
+
Level string `json:"level"`
|
|
16
|
+
Tags map[string]string `json:"tags"`
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
func DebugProbe(label string, data interface{}, meta *DebugProbeMeta) {
|
|
20
|
+
defer func() {
|
|
21
|
+
_ = recover()
|
|
22
|
+
}()
|
|
23
|
+
|
|
24
|
+
if os.Getenv("DEBUGHUB_ENABLED") != "1" {
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
sessionID := os.Getenv("DEBUGHUB_SESSION")
|
|
29
|
+
if sessionID == "" {
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
endpoint := os.Getenv("DEBUGHUB_ENDPOINT")
|
|
34
|
+
if endpoint == "" {
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
level := "info"
|
|
39
|
+
var hypothesisID *string
|
|
40
|
+
var loc *string
|
|
41
|
+
var tags map[string]string
|
|
42
|
+
if meta != nil {
|
|
43
|
+
if meta.Level == "info" || meta.Level == "warn" || meta.Level == "error" {
|
|
44
|
+
level = meta.Level
|
|
45
|
+
}
|
|
46
|
+
hypothesisID = meta.HypothesisID
|
|
47
|
+
loc = meta.Loc
|
|
48
|
+
tags = meta.Tags
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
event := map[string]interface{}{
|
|
52
|
+
"ts": time.Now().UTC().Format(time.RFC3339Nano),
|
|
53
|
+
"sessionId": sessionID,
|
|
54
|
+
"label": label,
|
|
55
|
+
"data": data,
|
|
56
|
+
"hypothesisId": hypothesisID,
|
|
57
|
+
"loc": loc,
|
|
58
|
+
"level": level,
|
|
59
|
+
"tags": tags,
|
|
60
|
+
"runtime": "go",
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
payload, err := json.Marshal(event)
|
|
64
|
+
if err != nil {
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
target := strings.TrimRight(endpoint, "/") + "/event"
|
|
69
|
+
req, err := http.NewRequest(http.MethodPost, target, bytes.NewReader(payload))
|
|
70
|
+
if err != nil {
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
req.Header.Set("Content-Type", "application/json")
|
|
74
|
+
|
|
75
|
+
client := &http.Client{Timeout: 2 * time.Second}
|
|
76
|
+
resp, err := client.Do(req)
|
|
77
|
+
if err != nil {
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
defer resp.Body.Close()
|
|
81
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# DebugHub HTTP Event Contract
|
|
2
|
+
|
|
3
|
+
This contract defines the minimum behavior all runtime helpers must follow.
|
|
4
|
+
|
|
5
|
+
## Environment Variables
|
|
6
|
+
|
|
7
|
+
- `DEBUGHUB_ENABLED`: must be exactly `1` to emit events.
|
|
8
|
+
- `DEBUGHUB_SESSION`: required non-empty session id.
|
|
9
|
+
- `DEBUGHUB_ENDPOINT`: required base endpoint (for example `http://127.0.0.1:7777`).
|
|
10
|
+
|
|
11
|
+
If any required value is missing or invalid, helpers must no-op.
|
|
12
|
+
|
|
13
|
+
## Transport
|
|
14
|
+
|
|
15
|
+
- Method: `POST`
|
|
16
|
+
- URL: `{DEBUGHUB_ENDPOINT}/event` (append `/event` after trimming any trailing slash)
|
|
17
|
+
- Content-Type: `application/json`
|
|
18
|
+
- Delivery mode: best-effort, fire-and-forget behavior from the application perspective
|
|
19
|
+
- Failure handling: helper must never throw into host application code
|
|
20
|
+
|
|
21
|
+
## Payload Schema
|
|
22
|
+
|
|
23
|
+
Required keys:
|
|
24
|
+
|
|
25
|
+
- `ts`: string
|
|
26
|
+
- `sessionId`: string
|
|
27
|
+
- `label`: string
|
|
28
|
+
- `data`: any JSON value or `null`
|
|
29
|
+
- `hypothesisId`: string or `null`
|
|
30
|
+
- `loc`: string or `null`
|
|
31
|
+
- `level`: one of `info`, `warn`, `error`
|
|
32
|
+
- `tags`: object or `null`
|
|
33
|
+
- `runtime`: string
|
|
34
|
+
|
|
35
|
+
Defaults:
|
|
36
|
+
|
|
37
|
+
- `data`: `null`
|
|
38
|
+
- `hypothesisId`: `null`
|
|
39
|
+
- `loc`: `null`
|
|
40
|
+
- `level`: `info`
|
|
41
|
+
- `tags`: `null`
|
|
42
|
+
|
|
43
|
+
## Runtime Field Values
|
|
44
|
+
|
|
45
|
+
Helpers should use a stable runtime name:
|
|
46
|
+
|
|
47
|
+
- `node`
|
|
48
|
+
- `browser`
|
|
49
|
+
- `java`
|
|
50
|
+
- `python`
|
|
51
|
+
- `rust`
|
|
52
|
+
- `php`
|
|
53
|
+
- `go`
|
|
54
|
+
- `csharp`
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
package debughub;
|
|
2
|
+
|
|
3
|
+
import java.net.URI;
|
|
4
|
+
import java.net.http.HttpClient;
|
|
5
|
+
import java.net.http.HttpRequest;
|
|
6
|
+
import java.net.http.HttpResponse;
|
|
7
|
+
import java.time.Duration;
|
|
8
|
+
import java.time.Instant;
|
|
9
|
+
import java.util.Map;
|
|
10
|
+
|
|
11
|
+
public final class DebugProbe {
|
|
12
|
+
|
|
13
|
+
private static final HttpClient client = HttpClient.newBuilder()
|
|
14
|
+
.connectTimeout(Duration.ofSeconds(2))
|
|
15
|
+
.build();
|
|
16
|
+
|
|
17
|
+
public static void probe(String label, Object data, Map<String, String> meta) {
|
|
18
|
+
sendEvent(label, data, meta);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public static void probe(String label) {
|
|
22
|
+
sendEvent(label, null, null);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public static void debugProbe(String label, Object data, Map<String, String> meta) {
|
|
26
|
+
sendEvent(label, data, meta);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public static void debugProbe(String label) {
|
|
30
|
+
sendEvent(label, null, null);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private static void sendEvent(String label, Object data, Map<String, String> meta) {
|
|
34
|
+
try {
|
|
35
|
+
String isEnabled = System.getenv("DEBUGHUB_ENABLED");
|
|
36
|
+
String sessionId = System.getenv("DEBUGHUB_SESSION");
|
|
37
|
+
|
|
38
|
+
if (!"1".equals(isEnabled) || sessionId == null || sessionId.isEmpty()) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
String endpoint = System.getenv("DEBUGHUB_ENDPOINT");
|
|
43
|
+
if (endpoint == null || endpoint.isEmpty()) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
String level = "info";
|
|
48
|
+
String hypothesisId = null;
|
|
49
|
+
String loc = null;
|
|
50
|
+
String tagsJson = null;
|
|
51
|
+
if (meta != null) {
|
|
52
|
+
if (meta.get("level") != null && !meta.get("level").isEmpty()) {
|
|
53
|
+
level = meta.get("level");
|
|
54
|
+
}
|
|
55
|
+
hypothesisId = meta.get("hypothesisId");
|
|
56
|
+
loc = meta.get("loc");
|
|
57
|
+
tagsJson = meta.get("tags");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
StringBuilder json = new StringBuilder();
|
|
61
|
+
json.append("{");
|
|
62
|
+
json.append("\"ts\":\"").append(escapeJson(Instant.now().toString())).append("\",");
|
|
63
|
+
json.append("\"sessionId\":\"").append(escapeJson(sessionId)).append("\",");
|
|
64
|
+
json.append("\"label\":\"").append(escapeJson(label)).append("\",");
|
|
65
|
+
if (data == null) {
|
|
66
|
+
json.append("\"data\":null,");
|
|
67
|
+
} else {
|
|
68
|
+
json.append("\"data\":\"").append(escapeJson(data.toString())).append("\",");
|
|
69
|
+
}
|
|
70
|
+
json.append("\"hypothesisId\":").append(asJsonStringOrNull(hypothesisId)).append(",");
|
|
71
|
+
json.append("\"loc\":").append(asJsonStringOrNull(loc)).append(",");
|
|
72
|
+
json.append("\"level\":\"").append(escapeJson(level)).append("\",");
|
|
73
|
+
if (tagsJson == null || tagsJson.isEmpty()) {
|
|
74
|
+
json.append("\"tags\":null,");
|
|
75
|
+
} else {
|
|
76
|
+
json.append("\"tags\":").append(tagsJson).append(",");
|
|
77
|
+
}
|
|
78
|
+
json.append("\"runtime\":\"java\",");
|
|
79
|
+
json.append("\"thread\":\"").append(escapeJson(Thread.currentThread().getName())).append("\"");
|
|
80
|
+
json.append("}");
|
|
81
|
+
|
|
82
|
+
String targetUrl = endpoint.endsWith("/") ? endpoint + "event" : endpoint + "/event";
|
|
83
|
+
|
|
84
|
+
HttpRequest request = HttpRequest.newBuilder()
|
|
85
|
+
.uri(URI.create(targetUrl))
|
|
86
|
+
.header("Content-Type", "application/json")
|
|
87
|
+
.timeout(Duration.ofSeconds(2))
|
|
88
|
+
.POST(HttpRequest.BodyPublishers.ofString(json.toString()))
|
|
89
|
+
.build();
|
|
90
|
+
|
|
91
|
+
client.send(request, HttpResponse.BodyHandlers.discarding());
|
|
92
|
+
} catch (Exception e) {
|
|
93
|
+
// Never throw from helper
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private static String asJsonStringOrNull(String value) {
|
|
98
|
+
if (value == null) {
|
|
99
|
+
return "null";
|
|
100
|
+
}
|
|
101
|
+
return "\"" + escapeJson(value) + "\"";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private static String escapeJson(String input) {
|
|
105
|
+
if (input == null) return "";
|
|
106
|
+
return input.replace("\\", "\\\\")
|
|
107
|
+
.replace("\"", "\\\"")
|
|
108
|
+
.replace("\b", "\\b")
|
|
109
|
+
.replace("\f", "\\f")
|
|
110
|
+
.replace("\n", "\\n")
|
|
111
|
+
.replace("\r", "\\r")
|
|
112
|
+
.replace("\t", "\\t");
|
|
113
|
+
}
|
|
114
|
+
}
|