Spaces:
Runtime error
Runtime error
File size: 6,059 Bytes
a6b96c2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | "use strict";
/**
* DispatchLogger interface + default implementation β issue #177 (ADR-0174 P1.3).
*
* Interface:
* { onEvent(event: DispatchEvent): void }
*
* Default behaviour (createDefaultLogger):
* 1. Silent on success β no stdout/stderr when result.kind === 'ok'.
* 2. Structured JSON to stderr on error β one line per dispatch error.
* 3. Opt-in audit file β when GSD_AUDIT=1 OR config.audit.enabled===true,
* appends every event (success + error) as one JSON line to
* .planning/.gsd-trace.jsonl relative to `cwd`. Creates .planning/ if absent.
* 4. Args redaction β args omitted by default; included when GSD_AUDIT_ARGS=1.
*
* No-op logger (createNoOpLogger):
* Silent on all events. Used as the Hub default when no logger is injected.
*
* ADR-457 build-at-publish: the hand-written bin/lib/observability/logger.cjs
* collapsed to a TypeScript source of truth. Behaviour is preserved
* byte-for-behaviour from the prior hand-written .cjs; only types are added.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const redaction_cjs_1 = require("./redaction.cjs");
const AUDIT_FILE_NAME = '.gsd-trace.jsonl';
const PLANNING_DIR = '.planning';
// βββ helpers βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
/**
* Safely serialise a value to JSON, falling back to a placeholder on circular refs.
*/
function _safeStringify(value) {
try {
return JSON.stringify(value);
}
catch {
return JSON.stringify({ _serializationError: true });
}
}
/**
* Determine whether the audit file should be written to.
*/
function _isAuditEnabled(config) {
if (process.env['GSD_AUDIT'] === '1')
return true;
if (config && config.audit && config.audit.enabled === true)
return true;
return false;
}
/**
* Build the redacted plain object for the audit file.
* Preserves the full DispatchEvent structure.
*/
function _toAuditRecord(event) {
return (0, redaction_cjs_1.redactEvent)(event);
}
/**
* Build the flattened stderr error line.
*
* Per ADR-0174 P1.3 contract: { "kind": "<variant>", "traceId": "<uuid>", ...typedPayload }
* The result's kind is promoted to top-level and the typed payload fields are spread in.
* The `result` wrapper is removed.
*/
function _toStderrRecord(event) {
const redacted = (0, redaction_cjs_1.redactEvent)(event);
const { result, ...eventWithoutResult } = redacted;
// Flatten: top-level gets kind + typed payload fields from result
const resultObj = result;
const { kind, ...typedPayload } = resultObj;
return Object.assign({}, eventWithoutResult, { kind }, typedPayload);
}
/**
* Append one JSON line to the audit file.
* Creates .planning/ directory if it does not exist.
*
* Uses synchronous fs API (crash-safe for v1 β dispatch is synchronous).
*/
function _appendAuditLine(cwd, event) {
const planningDir = node_path_1.default.join(cwd, PLANNING_DIR);
// Ensure the directory exists
if (!node_fs_1.default.existsSync(planningDir)) {
node_fs_1.default.mkdirSync(planningDir, { recursive: true });
}
const auditPath = node_path_1.default.join(planningDir, AUDIT_FILE_NAME);
node_fs_1.default.appendFileSync(auditPath, _safeStringify(event) + '\n', 'utf8');
}
/**
* Create a no-op logger. All events are silently dropped.
* This is the Hub's default when no logger is injected by the caller.
*/
function createNoOpLogger() {
return {
onEvent(_event) {
// intentionally empty
},
};
}
/**
* Create the default DispatchLogger.
*/
function createDefaultLogger({ cwd = process.cwd(), config } = {}) {
return {
/**
* @param event - A DispatchEvent from the Hub.
*/
onEvent(event) {
const resultObj = event && event['result'];
const isOk = resultObj && resultObj['kind'] === 'ok';
// ββ Audit file (both ok and error) ββββββββββββββββββββββββββββββββββββ
if (_isAuditEnabled(config)) {
try {
const auditRecord = _toAuditRecord(event);
_appendAuditLine(cwd, auditRecord);
}
catch (auditErr) {
// Audit errors must not surface to callers
process.stderr.write(_safeStringify({
level: 'warn',
source: 'DispatchLogger',
message: 'audit file write failed: ' + String(auditErr?.message ?? auditErr),
}) + '\n');
}
}
// ββ Stderr on error βββββββββββββββββββββββββββββββββββββββββββββββββββ
if (!isOk) {
try {
const stderrRecord = _toStderrRecord(event);
process.stderr.write(_safeStringify(stderrRecord) + '\n');
}
catch (stderrErr) {
// Last-resort: we cannot throw from the logger
process.stderr.write(_safeStringify({
level: 'warn',
source: 'DispatchLogger',
message: 'stderr emit failed: ' + String(stderrErr?.message ?? stderrErr),
}) + '\n');
}
}
// ββ Silent on success (no else branch needed) βββββββββββββββββββββββββ
},
};
}
module.exports = { createDefaultLogger, createNoOpLogger };
|