@descryy/runtime-remote-log-ingestion 0.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/dist/correlator.d.ts +67 -0
- package/dist/correlator.d.ts.map +1 -0
- package/dist/correlator.js +89 -0
- package/dist/correlator.js.map +1 -0
- package/dist/file-log-adapter.d.ts +42 -0
- package/dist/file-log-adapter.d.ts.map +1 -0
- package/dist/file-log-adapter.js +45 -0
- package/dist/file-log-adapter.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/log-line.d.ts +51 -0
- package/dist/log-line.d.ts.map +1 -0
- package/dist/log-line.js +101 -0
- package/dist/log-line.js.map +1 -0
- package/dist/log-source-config.d.ts +65 -0
- package/dist/log-source-config.d.ts.map +1 -0
- package/dist/log-source-config.js +20 -0
- package/dist/log-source-config.js.map +1 -0
- package/package.json +31 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Correlation between a captured browser/proxy request and an ingested
|
|
3
|
+
* remote log line (DEC-271's remote side). Follows the same pattern
|
|
4
|
+
* `backend-observation/correlator.ts`'s `correlateBackendEvidence` already
|
|
5
|
+
* establishes -- prefer a real shared identifier, never fall back to timing
|
|
6
|
+
* on its own -- rather than inventing a second correlation shape.
|
|
7
|
+
*
|
|
8
|
+
* Reuses `@descryy/runtime-contracts`' `Correlation`/`createCorrelation`
|
|
9
|
+
* directly: `method` is drawn from the same closed `CorrelationMethod`
|
|
10
|
+
* union everywhere else in this repo uses, so `isConfirmed` is derived the
|
|
11
|
+
* same way (`isStructuralCorrelation` -- true for `trace-id`/`request-id`,
|
|
12
|
+
* false for `timing-context-fallback`) and a reader who already knows that
|
|
13
|
+
* contract does not have to learn a second one for the remote case.
|
|
14
|
+
*/
|
|
15
|
+
import type { Correlation } from "@descryy/runtime-contracts";
|
|
16
|
+
import type { IngestedLogLine } from "./log-line.ts";
|
|
17
|
+
/**
|
|
18
|
+
* A captured browser/proxy request, reduced to the fields correlation
|
|
19
|
+
* needs. `timestamp` and `path` are always available -- every captured
|
|
20
|
+
* request has both; `traceId`/`requestId` are populated only when the
|
|
21
|
+
* capturing side actually found them (real headers, W3C `traceparent` --
|
|
22
|
+
* see `external-service-observation/header-correlation.ts`), null
|
|
23
|
+
* otherwise. `id` is caller-assigned (an `Evidence.evidenceId`, typically)
|
|
24
|
+
* -- this module never generates one, mirroring
|
|
25
|
+
* `correlateBackendEvidence`'s use of pre-existing `evidenceId`s rather
|
|
26
|
+
* than minting its own.
|
|
27
|
+
*/
|
|
28
|
+
export interface CapturedRequestRef {
|
|
29
|
+
readonly id: string;
|
|
30
|
+
readonly executionId: string;
|
|
31
|
+
readonly timestamp: string;
|
|
32
|
+
readonly path: string;
|
|
33
|
+
readonly traceId: string | null;
|
|
34
|
+
readonly requestId: string | null;
|
|
35
|
+
}
|
|
36
|
+
export interface CorrelateOptions {
|
|
37
|
+
/**
|
|
38
|
+
* Widest gap between the request's and the line's timestamp still
|
|
39
|
+
* considered for a timestamp+path match, in milliseconds. Default 5000 --
|
|
40
|
+
* generous enough for real network and log-flush latency between a
|
|
41
|
+
* request landing and its log line appearing, narrow enough not to pair
|
|
42
|
+
* unrelated activity in a busy log.
|
|
43
|
+
*/
|
|
44
|
+
readonly windowMs?: number;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Best-effort correlation between one captured request and one ingested log
|
|
48
|
+
* line, in DEC-271's stated preference order:
|
|
49
|
+
*
|
|
50
|
+
* 1. a shared trace id (`method: "trace-id"`, confidence 1) -- the same
|
|
51
|
+
* rule and confidence `correlateBackendEvidence` uses for the local case.
|
|
52
|
+
* 2. a shared request id (`method: "request-id"`, confidence 1).
|
|
53
|
+
* 3. the request's timestamp within `windowMs` of the line's **and** the
|
|
54
|
+
* request's `path` appearing in the line's (already-redacted) text --
|
|
55
|
+
* `method: "timing-context-fallback"`, confidence
|
|
56
|
+
* `TIMESTAMP_PATH_CONFIDENCE`, `isConfirmed: false`. Both signals are
|
|
57
|
+
* required together; timestamp proximity alone is not treated as
|
|
58
|
+
* evidence of anything, per DEC-271's warning against inferring from
|
|
59
|
+
* timing on its own.
|
|
60
|
+
*
|
|
61
|
+
* Returns `null` -- DEC-271's "not observable here" for this one
|
|
62
|
+
* request/line pair -- when none of the above hold: no shared identifier,
|
|
63
|
+
* and either the line carries no parseable timestamp or the path/window
|
|
64
|
+
* test fails. Never fabricates a match to fill the gap.
|
|
65
|
+
*/
|
|
66
|
+
export declare function correlateLogLine(request: CapturedRequestRef, line: IngestedLogLine, options?: CorrelateOptions): Correlation | null;
|
|
67
|
+
//# sourceMappingURL=correlator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"correlator.d.ts","sourceRoot":"","sources":["../src/correlator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAG9D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,gBAAgB;IAC/B;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAmBD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,kBAAkB,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,GAAE,gBAAqB,GAAG,WAAW,GAAG,IAAI,CAqCvI"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Correlation between a captured browser/proxy request and an ingested
|
|
3
|
+
* remote log line (DEC-271's remote side). Follows the same pattern
|
|
4
|
+
* `backend-observation/correlator.ts`'s `correlateBackendEvidence` already
|
|
5
|
+
* establishes -- prefer a real shared identifier, never fall back to timing
|
|
6
|
+
* on its own -- rather than inventing a second correlation shape.
|
|
7
|
+
*
|
|
8
|
+
* Reuses `@descryy/runtime-contracts`' `Correlation`/`createCorrelation`
|
|
9
|
+
* directly: `method` is drawn from the same closed `CorrelationMethod`
|
|
10
|
+
* union everywhere else in this repo uses, so `isConfirmed` is derived the
|
|
11
|
+
* same way (`isStructuralCorrelation` -- true for `trace-id`/`request-id`,
|
|
12
|
+
* false for `timing-context-fallback`) and a reader who already knows that
|
|
13
|
+
* contract does not have to learn a second one for the remote case.
|
|
14
|
+
*/
|
|
15
|
+
import { randomUUID } from "node:crypto";
|
|
16
|
+
import { createCorrelation } from "@descryy/runtime-contracts";
|
|
17
|
+
const DEFAULT_WINDOW_MS = 5000;
|
|
18
|
+
/**
|
|
19
|
+
* Confidence assigned to every `"timing-context-fallback"` match. Fixed
|
|
20
|
+
* rather than scaled by how close the two timestamps are -- DEC-271 asks
|
|
21
|
+
* for an honestly-stated tier, not a manufactured precision this signal
|
|
22
|
+
* doesn't actually have: there is no identifier behind it, only two
|
|
23
|
+
* independent weak cues (a clock and a substring search), so one
|
|
24
|
+
* deliberately-below-"confirmed" number stands for the whole tier rather
|
|
25
|
+
* than implying a false gradation within it. The structural distinction
|
|
26
|
+
* from a confirmed match (`isConfirmed: false`, via `isStructuralCorrelation`)
|
|
27
|
+
* is what actually matters; this number exists only so two
|
|
28
|
+
* `Correlation`s are never rendered as equally certain by a reader who
|
|
29
|
+
* looks at `confidence` alone.
|
|
30
|
+
*/
|
|
31
|
+
const TIMESTAMP_PATH_CONFIDENCE = 0.4;
|
|
32
|
+
/**
|
|
33
|
+
* Best-effort correlation between one captured request and one ingested log
|
|
34
|
+
* line, in DEC-271's stated preference order:
|
|
35
|
+
*
|
|
36
|
+
* 1. a shared trace id (`method: "trace-id"`, confidence 1) -- the same
|
|
37
|
+
* rule and confidence `correlateBackendEvidence` uses for the local case.
|
|
38
|
+
* 2. a shared request id (`method: "request-id"`, confidence 1).
|
|
39
|
+
* 3. the request's timestamp within `windowMs` of the line's **and** the
|
|
40
|
+
* request's `path` appearing in the line's (already-redacted) text --
|
|
41
|
+
* `method: "timing-context-fallback"`, confidence
|
|
42
|
+
* `TIMESTAMP_PATH_CONFIDENCE`, `isConfirmed: false`. Both signals are
|
|
43
|
+
* required together; timestamp proximity alone is not treated as
|
|
44
|
+
* evidence of anything, per DEC-271's warning against inferring from
|
|
45
|
+
* timing on its own.
|
|
46
|
+
*
|
|
47
|
+
* Returns `null` -- DEC-271's "not observable here" for this one
|
|
48
|
+
* request/line pair -- when none of the above hold: no shared identifier,
|
|
49
|
+
* and either the line carries no parseable timestamp or the path/window
|
|
50
|
+
* test fails. Never fabricates a match to fill the gap.
|
|
51
|
+
*/
|
|
52
|
+
export function correlateLogLine(request, line, options = {}) {
|
|
53
|
+
if (request.traceId !== null && request.traceId === line.traceId) {
|
|
54
|
+
return createCorrelation({
|
|
55
|
+
correlationId: randomUUID(),
|
|
56
|
+
executionId: request.executionId,
|
|
57
|
+
method: "trace-id",
|
|
58
|
+
confidence: 1,
|
|
59
|
+
evidenceIds: [request.id, line.lineId],
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
if (request.requestId !== null && request.requestId === line.requestId) {
|
|
63
|
+
return createCorrelation({
|
|
64
|
+
correlationId: randomUUID(),
|
|
65
|
+
executionId: request.executionId,
|
|
66
|
+
method: "request-id",
|
|
67
|
+
confidence: 1,
|
|
68
|
+
evidenceIds: [request.id, line.lineId],
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
// No clock on this line to window against -- not a weaker match, no
|
|
72
|
+
// match at all. Claiming one would be a guess this module refuses.
|
|
73
|
+
if (line.timestamp === null)
|
|
74
|
+
return null;
|
|
75
|
+
const windowMs = options.windowMs ?? DEFAULT_WINDOW_MS;
|
|
76
|
+
const deltaMs = Math.abs(new Date(request.timestamp).getTime() - new Date(line.timestamp).getTime());
|
|
77
|
+
if (deltaMs > windowMs)
|
|
78
|
+
return null;
|
|
79
|
+
if (!line.raw.includes(request.path))
|
|
80
|
+
return null;
|
|
81
|
+
return createCorrelation({
|
|
82
|
+
correlationId: randomUUID(),
|
|
83
|
+
executionId: request.executionId,
|
|
84
|
+
method: "timing-context-fallback",
|
|
85
|
+
confidence: TIMESTAMP_PATH_CONFIDENCE,
|
|
86
|
+
evidenceIds: [request.id, line.lineId],
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=correlator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"correlator.js","sourceRoot":"","sources":["../src/correlator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAmC/D,MAAM,iBAAiB,GAAG,IAAI,CAAC;AAE/B;;;;;;;;;;;;GAYG;AACH,MAAM,yBAAyB,GAAG,GAAG,CAAC;AAEtC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAA2B,EAAE,IAAqB,EAAE,UAA4B,EAAE;IACjH,IAAI,OAAO,CAAC,OAAO,KAAK,IAAI,IAAI,OAAO,CAAC,OAAO,KAAK,IAAI,CAAC,OAAO,EAAE,CAAC;QACjE,OAAO,iBAAiB,CAAC;YACvB,aAAa,EAAE,UAAU,EAAE;YAC3B,WAAW,EAAE,OAAO,CAAC,WAAW;YAChC,MAAM,EAAE,UAAU;YAClB,UAAU,EAAE,CAAC;YACb,WAAW,EAAE,CAAC,OAAO,CAAC,EAAE,EAAE,IAAI,CAAC,MAAM,CAAC;SACvC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,OAAO,CAAC,SAAS,KAAK,IAAI,IAAI,OAAO,CAAC,SAAS,KAAK,IAAI,CAAC,SAAS,EAAE,CAAC;QACvE,OAAO,iBAAiB,CAAC;YACvB,aAAa,EAAE,UAAU,EAAE;YAC3B,WAAW,EAAE,OAAO,CAAC,WAAW;YAChC,MAAM,EAAE,YAAY;YACpB,UAAU,EAAE,CAAC;YACb,WAAW,EAAE,CAAC,OAAO,CAAC,EAAE,EAAE,IAAI,CAAC,MAAM,CAAC;SACvC,CAAC,CAAC;IACL,CAAC;IAED,oEAAoE;IACpE,mEAAmE;IACnE,IAAI,IAAI,CAAC,SAAS,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAEzC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,iBAAiB,CAAC;IACvD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;IACrG,IAAI,OAAO,GAAG,QAAQ;QAAE,OAAO,IAAI,CAAC;IACpC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAElD,OAAO,iBAAiB,CAAC;QACvB,aAAa,EAAE,UAAU,EAAE;QAC3B,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,MAAM,EAAE,yBAAyB;QACjC,UAAU,EAAE,yBAAyB;QACrC,WAAW,EAAE,CAAC,OAAO,CAAC,EAAE,EAAE,IAAI,CAAC,MAAM,CAAC;KACvC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Concrete adapter #1 for DEC-271's remote side: a log **file** the user
|
|
3
|
+
* points us at.
|
|
4
|
+
*
|
|
5
|
+
* **Chosen over a command-stdout adapter** (`RemoteLogLocation`'s other
|
|
6
|
+
* named-and-typed option) because it is the simpler case to build correctly
|
|
7
|
+
* and verify in this pass: deterministic content on disk, no child-process
|
|
8
|
+
* lifecycle, no question of how a running command's stdout encoding or
|
|
9
|
+
* buffering interacts with line splitting. A command adapter is the natural
|
|
10
|
+
* next one -- it would reuse `buildIngestedLogLine` unchanged and differ
|
|
11
|
+
* only in where the lines come from -- not built this pass.
|
|
12
|
+
*
|
|
13
|
+
* **One-shot, not a continuous tail.** This reads the file's contents as of
|
|
14
|
+
* the call; it does not follow appended writes. Stated rather than silently
|
|
15
|
+
* partial, per this repo's "honest degradation" rule: a caller that needs
|
|
16
|
+
* live tailing does not get it from this function today.
|
|
17
|
+
*/
|
|
18
|
+
import type { RedactionPolicy } from "@descryy/runtime-evidence-store";
|
|
19
|
+
import { type IngestedLogLine } from "./log-line.ts";
|
|
20
|
+
import type { RemoteLogSourceConfig } from "./log-source-config.ts";
|
|
21
|
+
export interface FileLogSourceConfig extends RemoteLogSourceConfig {
|
|
22
|
+
readonly location: {
|
|
23
|
+
readonly kind: "file";
|
|
24
|
+
readonly path: string;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export interface FileLogAdapterOptions {
|
|
28
|
+
readonly config: FileLogSourceConfig;
|
|
29
|
+
readonly redactionPolicy?: RedactionPolicy;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Reads `config.location.path` and returns one `IngestedLogLine` per
|
|
33
|
+
* non-empty line, in file order. Every line has already been through
|
|
34
|
+
* `redact()` by the time it is returned -- see `log-line.ts`.
|
|
35
|
+
*
|
|
36
|
+
* A file that cannot be read (missing, permission denied) throws with the
|
|
37
|
+
* path and the underlying error in the message -- "not observable here" for
|
|
38
|
+
* this source, surfaced as a rejection a caller must handle, not a silent
|
|
39
|
+
* empty result indistinguishable from an empty log.
|
|
40
|
+
*/
|
|
41
|
+
export declare function ingestFileLog(options: FileLogAdapterOptions): Promise<readonly IngestedLogLine[]>;
|
|
42
|
+
//# sourceMappingURL=file-log-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-log-adapter.d.ts","sourceRoot":"","sources":["../src/file-log-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAEvE,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAC3E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAEpE,MAAM,WAAW,mBAAoB,SAAQ,qBAAqB;IAChE,QAAQ,CAAC,QAAQ,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CACrE;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;IACrC,QAAQ,CAAC,eAAe,CAAC,EAAE,eAAe,CAAC;CAC5C;AAED;;;;;;;;;GASG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,SAAS,eAAe,EAAE,CAAC,CAcvG"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Concrete adapter #1 for DEC-271's remote side: a log **file** the user
|
|
3
|
+
* points us at.
|
|
4
|
+
*
|
|
5
|
+
* **Chosen over a command-stdout adapter** (`RemoteLogLocation`'s other
|
|
6
|
+
* named-and-typed option) because it is the simpler case to build correctly
|
|
7
|
+
* and verify in this pass: deterministic content on disk, no child-process
|
|
8
|
+
* lifecycle, no question of how a running command's stdout encoding or
|
|
9
|
+
* buffering interacts with line splitting. A command adapter is the natural
|
|
10
|
+
* next one -- it would reuse `buildIngestedLogLine` unchanged and differ
|
|
11
|
+
* only in where the lines come from -- not built this pass.
|
|
12
|
+
*
|
|
13
|
+
* **One-shot, not a continuous tail.** This reads the file's contents as of
|
|
14
|
+
* the call; it does not follow appended writes. Stated rather than silently
|
|
15
|
+
* partial, per this repo's "honest degradation" rule: a caller that needs
|
|
16
|
+
* live tailing does not get it from this function today.
|
|
17
|
+
*/
|
|
18
|
+
import { readFile } from "node:fs/promises";
|
|
19
|
+
import { buildIngestedLogLine } from "./log-line.js";
|
|
20
|
+
/**
|
|
21
|
+
* Reads `config.location.path` and returns one `IngestedLogLine` per
|
|
22
|
+
* non-empty line, in file order. Every line has already been through
|
|
23
|
+
* `redact()` by the time it is returned -- see `log-line.ts`.
|
|
24
|
+
*
|
|
25
|
+
* A file that cannot be read (missing, permission denied) throws with the
|
|
26
|
+
* path and the underlying error in the message -- "not observable here" for
|
|
27
|
+
* this source, surfaced as a rejection a caller must handle, not a silent
|
|
28
|
+
* empty result indistinguishable from an empty log.
|
|
29
|
+
*/
|
|
30
|
+
export async function ingestFileLog(options) {
|
|
31
|
+
const { config, redactionPolicy } = options;
|
|
32
|
+
let content;
|
|
33
|
+
try {
|
|
34
|
+
content = await readFile(config.location.path, "utf8");
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
38
|
+
throw new Error(`remote-log-ingestion: could not read log file "${config.location.path}": ${detail}`);
|
|
39
|
+
}
|
|
40
|
+
return content
|
|
41
|
+
.split(/\r?\n/)
|
|
42
|
+
.filter((line) => line.length > 0)
|
|
43
|
+
.map((line) => buildIngestedLogLine(line, config, redactionPolicy));
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=file-log-adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-log-adapter.js","sourceRoot":"","sources":["../src/file-log-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAG5C,OAAO,EAAE,oBAAoB,EAAwB,MAAM,eAAe,CAAC;AAY3E;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,OAA8B;IAChE,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC;IAC5C,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACzD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,MAAM,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACtE,MAAM,IAAI,KAAK,CAAC,kDAAkD,MAAM,CAAC,QAAQ,CAAC,IAAI,MAAM,MAAM,EAAE,CAAC,CAAC;IACxG,CAAC;IAED,OAAO,OAAO;SACX,KAAK,CAAC,OAAO,CAAC;SACd,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;SACjC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC;AACxE,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type { RemoteLogLocation, RemoteLogFormat, RemoteLogSourceConfig } from "./log-source-config.ts";
|
|
2
|
+
export { REMOTE_LOG_FORMATS } from "./log-source-config.ts";
|
|
3
|
+
export type { IngestedLogLine } from "./log-line.ts";
|
|
4
|
+
export { buildIngestedLogLine } from "./log-line.ts";
|
|
5
|
+
export type { FileLogSourceConfig, FileLogAdapterOptions } from "./file-log-adapter.ts";
|
|
6
|
+
export { ingestFileLog } from "./file-log-adapter.ts";
|
|
7
|
+
export type { CapturedRequestRef, CorrelateOptions } from "./correlator.ts";
|
|
8
|
+
export { correlateLogLine } from "./correlator.ts";
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,iBAAiB,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AACxG,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAE5D,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAErD,YAAY,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AACxF,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEtD,YAAY,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAC5E,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAG5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAGrD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAGtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One ingested log line, redacted before it is ever handed back to a
|
|
3
|
+
* caller (DEC-271: "log content is redacted before it reaches the model,
|
|
4
|
+
* not just before display -- the exposure is the read, not the render").
|
|
5
|
+
*
|
|
6
|
+
* Reuses `@descryy/runtime-evidence-store`'s `redact()` rather than
|
|
7
|
+
* reimplementing key-name/body/PII/command scanning -- the same reuse
|
|
8
|
+
* discipline `database-observation`'s and `browser`'s collectors already
|
|
9
|
+
* follow for evidence payloads. `redact()`'s own body-bearing-key
|
|
10
|
+
* convention (`raw`, documented there as the `LogCollector`'s payload key
|
|
11
|
+
* for captured text) is reused verbatim: every line is wrapped as
|
|
12
|
+
* `{ raw: line }` before redaction, so a JSON-shaped line gets `redact()`'s
|
|
13
|
+
* full key-name + text-scan treatment (parsed, walked, redacted key by
|
|
14
|
+
* key), and a plain-text line gets its text scanned for emails, card
|
|
15
|
+
* numbers and long tokens -- the same two paths `redactBodyString` already
|
|
16
|
+
* implements, not a new one.
|
|
17
|
+
*/
|
|
18
|
+
import type { RedactionStatus } from "@descryy/runtime-contracts";
|
|
19
|
+
import { type RedactionPolicy } from "@descryy/runtime-evidence-store";
|
|
20
|
+
import type { RemoteLogSourceConfig } from "./log-source-config.ts";
|
|
21
|
+
export interface IngestedLogLine {
|
|
22
|
+
readonly lineId: string;
|
|
23
|
+
/** ISO 8601, or null when no timestamp could be read from the line -- a real absence, not a parse failure hidden as "now". */
|
|
24
|
+
readonly timestamp: string | null;
|
|
25
|
+
/** Post-redaction text. There is no path out of `buildIngestedLogLine` that returns pre-redaction content. */
|
|
26
|
+
readonly raw: string;
|
|
27
|
+
readonly traceId: string | null;
|
|
28
|
+
readonly requestId: string | null;
|
|
29
|
+
readonly redactionStatus: RedactionStatus;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Builds one `IngestedLogLine` from one raw text line plus the user's
|
|
33
|
+
* declared `RemoteLogSourceConfig`.
|
|
34
|
+
*
|
|
35
|
+
* Identifier extraction always runs against the **pre-redaction** text --
|
|
36
|
+
* a trace/request id is evidence metadata, not payload content, the same
|
|
37
|
+
* distinction `Evidence.traceId`/`requestId` draw by living outside
|
|
38
|
+
* `Evidence.payload` and therefore outside `redact()`'s reach entirely.
|
|
39
|
+
* Extracting after redaction would try to read an id out of a line that
|
|
40
|
+
* may already read `[REDACTED]` where the id was.
|
|
41
|
+
*
|
|
42
|
+
* Priority, weakest signal overridden by the strongest available, per
|
|
43
|
+
* DEC-271's "ask, don't guess": `extractCorrelationIds`'s generic
|
|
44
|
+
* `trace_id=`/`traceparent`/`request_id=` heuristic runs first as a
|
|
45
|
+
* baseline; a JSON line's own `requestId`/`request_id` field overrides it
|
|
46
|
+
* when present; and the user's declared `traceIdField`, when it names a
|
|
47
|
+
* real value on this line, wins over both -- it is the one signal the user
|
|
48
|
+
* actually told us about rather than one this module inferred.
|
|
49
|
+
*/
|
|
50
|
+
export declare function buildIngestedLogLine(rawLine: string, config: Pick<RemoteLogSourceConfig, "format" | "traceIdField">, redactionPolicy?: RedactionPolicy): IngestedLogLine;
|
|
51
|
+
//# sourceMappingURL=log-line.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log-line.d.ts","sourceRoot":"","sources":["../src/log-line.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,EAAU,KAAK,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAG/E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAEpE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,8HAA8H;IAC9H,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,8GAA8G;IAC9G,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAC;CAC3C;AAkCD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,IAAI,CAAC,qBAAqB,EAAE,QAAQ,GAAG,cAAc,CAAC,EAC9D,eAAe,GAAE,eAAoB,GACpC,eAAe,CAoCjB"}
|
package/dist/log-line.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One ingested log line, redacted before it is ever handed back to a
|
|
3
|
+
* caller (DEC-271: "log content is redacted before it reaches the model,
|
|
4
|
+
* not just before display -- the exposure is the read, not the render").
|
|
5
|
+
*
|
|
6
|
+
* Reuses `@descryy/runtime-evidence-store`'s `redact()` rather than
|
|
7
|
+
* reimplementing key-name/body/PII/command scanning -- the same reuse
|
|
8
|
+
* discipline `database-observation`'s and `browser`'s collectors already
|
|
9
|
+
* follow for evidence payloads. `redact()`'s own body-bearing-key
|
|
10
|
+
* convention (`raw`, documented there as the `LogCollector`'s payload key
|
|
11
|
+
* for captured text) is reused verbatim: every line is wrapped as
|
|
12
|
+
* `{ raw: line }` before redaction, so a JSON-shaped line gets `redact()`'s
|
|
13
|
+
* full key-name + text-scan treatment (parsed, walked, redacted key by
|
|
14
|
+
* key), and a plain-text line gets its text scanned for emails, card
|
|
15
|
+
* numbers and long tokens -- the same two paths `redactBodyString` already
|
|
16
|
+
* implements, not a new one.
|
|
17
|
+
*/
|
|
18
|
+
import { randomUUID } from "node:crypto";
|
|
19
|
+
import { redact } from "@descryy/runtime-evidence-store";
|
|
20
|
+
import { extractCorrelationIds } from "@descryy/runtime-backend-observation";
|
|
21
|
+
const ISO_TIMESTAMP = /\b\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?\b/;
|
|
22
|
+
function parseTimestamp(text) {
|
|
23
|
+
const match = ISO_TIMESTAMP.exec(text);
|
|
24
|
+
if (match === null)
|
|
25
|
+
return null;
|
|
26
|
+
const parsed = new Date(match[0].replace(" ", "T"));
|
|
27
|
+
return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString();
|
|
28
|
+
}
|
|
29
|
+
function tryParseJsonObject(text) {
|
|
30
|
+
try {
|
|
31
|
+
const parsed = JSON.parse(text);
|
|
32
|
+
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function extractJsonField(record, field) {
|
|
39
|
+
const value = record[field];
|
|
40
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
41
|
+
}
|
|
42
|
+
function escapeRegExp(text) {
|
|
43
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
44
|
+
}
|
|
45
|
+
function namedFieldFromText(text, field) {
|
|
46
|
+
const pattern = new RegExp(`\\b${escapeRegExp(field)}["']?\\s*[:=]\\s*["']?([0-9a-zA-Z-]{6,})["']?`, "i");
|
|
47
|
+
return pattern.exec(text)?.[1] ?? null;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Builds one `IngestedLogLine` from one raw text line plus the user's
|
|
51
|
+
* declared `RemoteLogSourceConfig`.
|
|
52
|
+
*
|
|
53
|
+
* Identifier extraction always runs against the **pre-redaction** text --
|
|
54
|
+
* a trace/request id is evidence metadata, not payload content, the same
|
|
55
|
+
* distinction `Evidence.traceId`/`requestId` draw by living outside
|
|
56
|
+
* `Evidence.payload` and therefore outside `redact()`'s reach entirely.
|
|
57
|
+
* Extracting after redaction would try to read an id out of a line that
|
|
58
|
+
* may already read `[REDACTED]` where the id was.
|
|
59
|
+
*
|
|
60
|
+
* Priority, weakest signal overridden by the strongest available, per
|
|
61
|
+
* DEC-271's "ask, don't guess": `extractCorrelationIds`'s generic
|
|
62
|
+
* `trace_id=`/`traceparent`/`request_id=` heuristic runs first as a
|
|
63
|
+
* baseline; a JSON line's own `requestId`/`request_id` field overrides it
|
|
64
|
+
* when present; and the user's declared `traceIdField`, when it names a
|
|
65
|
+
* real value on this line, wins over both -- it is the one signal the user
|
|
66
|
+
* actually told us about rather than one this module inferred.
|
|
67
|
+
*/
|
|
68
|
+
export function buildIngestedLogLine(rawLine, config, redactionPolicy = {}) {
|
|
69
|
+
const parsedRecord = config.format === "json-lines" ? tryParseJsonObject(rawLine) : null;
|
|
70
|
+
const generic = extractCorrelationIds(rawLine);
|
|
71
|
+
let traceId = generic.traceId;
|
|
72
|
+
let requestId = generic.requestId;
|
|
73
|
+
let timestamp = parseTimestamp(rawLine);
|
|
74
|
+
if (parsedRecord !== null) {
|
|
75
|
+
const tsField = extractJsonField(parsedRecord, "timestamp") ?? extractJsonField(parsedRecord, "time") ?? extractJsonField(parsedRecord, "@timestamp");
|
|
76
|
+
if (tsField !== null) {
|
|
77
|
+
const parsedDate = new Date(tsField);
|
|
78
|
+
if (!Number.isNaN(parsedDate.getTime()))
|
|
79
|
+
timestamp = parsedDate.toISOString();
|
|
80
|
+
}
|
|
81
|
+
const jsonRequestId = extractJsonField(parsedRecord, "requestId") ?? extractJsonField(parsedRecord, "request_id");
|
|
82
|
+
if (jsonRequestId !== null)
|
|
83
|
+
requestId = jsonRequestId;
|
|
84
|
+
}
|
|
85
|
+
if (config.traceIdField) {
|
|
86
|
+
const named = parsedRecord !== null ? extractJsonField(parsedRecord, config.traceIdField) : namedFieldFromText(rawLine, config.traceIdField);
|
|
87
|
+
if (named !== null)
|
|
88
|
+
traceId = named;
|
|
89
|
+
}
|
|
90
|
+
const { payload, redactionStatus } = redact({ raw: rawLine }, redactionPolicy);
|
|
91
|
+
const redactedRaw = payload.raw;
|
|
92
|
+
return {
|
|
93
|
+
lineId: randomUUID(),
|
|
94
|
+
timestamp,
|
|
95
|
+
raw: redactedRaw,
|
|
96
|
+
traceId,
|
|
97
|
+
requestId,
|
|
98
|
+
redactionStatus,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=log-line.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log-line.js","sourceRoot":"","sources":["../src/log-line.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The "ask, don't guess" input contract for remote log ingestion (DEC-271).
|
|
3
|
+
*
|
|
4
|
+
* For local/booted execution Descry injects its own proxy and needs to ask
|
|
5
|
+
* the user nothing. For remote environments (staging/preview/production,
|
|
6
|
+
* DEC-266) Descry does not own the infra and cannot inject anything -- the
|
|
7
|
+
* only mechanism left is ingesting whatever the target already has, and
|
|
8
|
+
* DEC-271's own "rejected: one universal mechanism" clause says that target
|
|
9
|
+
* is heterogeneous by nature (Datadog, CloudWatch, plain stdout, full
|
|
10
|
+
* OpenTelemetry, or nothing at all). So setup has to ask: where are the
|
|
11
|
+
* logs, what shape are they in, and is there a field that already carries a
|
|
12
|
+
* trace/request id.
|
|
13
|
+
*
|
|
14
|
+
* This file defines that shape only -- it is filled in by whatever asks the
|
|
15
|
+
* user (a CLI prompt, a UI form, elsewhere), never inferred or defaulted
|
|
16
|
+
* here. The logic that consumes it lives in the adapters
|
|
17
|
+
* (`file-log-adapter.ts` today) and in `correlator.ts`.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* What the user says they have. `file` and `command` are both plausible
|
|
21
|
+
* ("here's our log file" / "here's how we tail it") and named so a future
|
|
22
|
+
* adapter for either has an unambiguous slot; `url` covers a log-query API
|
|
23
|
+
* or a signed export link. Only `file` has a concrete adapter this pass --
|
|
24
|
+
* see `file-log-adapter.ts`'s own doc comment for why it was chosen over
|
|
25
|
+
* `command`, and why `url` isn't built at all yet.
|
|
26
|
+
*/
|
|
27
|
+
export type RemoteLogLocation = {
|
|
28
|
+
readonly kind: "file";
|
|
29
|
+
readonly path: string;
|
|
30
|
+
} | {
|
|
31
|
+
readonly kind: "command";
|
|
32
|
+
readonly command: string;
|
|
33
|
+
readonly args?: readonly string[];
|
|
34
|
+
} | {
|
|
35
|
+
readonly kind: "url";
|
|
36
|
+
readonly url: string;
|
|
37
|
+
};
|
|
38
|
+
export declare const REMOTE_LOG_FORMATS: readonly ["plain-text", "json-lines"];
|
|
39
|
+
/**
|
|
40
|
+
* The two shapes this pass knows how to read. Not a claim these are the
|
|
41
|
+
* only shapes a real target log ever takes (a named convention such as
|
|
42
|
+
* "Heroku router format" or "Rails tagged logging" is a real future case)
|
|
43
|
+
* -- narrowed to what `buildIngestedLogLine` actually parses, honestly,
|
|
44
|
+
* rather than accepting a value it would silently mishandle.
|
|
45
|
+
*/
|
|
46
|
+
export type RemoteLogFormat = (typeof REMOTE_LOG_FORMATS)[number];
|
|
47
|
+
export interface RemoteLogSourceConfig {
|
|
48
|
+
readonly location: RemoteLogLocation;
|
|
49
|
+
readonly format: RemoteLogFormat;
|
|
50
|
+
/**
|
|
51
|
+
* The field the user says already carries a trace/request id -- a JSON
|
|
52
|
+
* key for `"json-lines"`, or a `name=`/`name:` field for `"plain-text"`.
|
|
53
|
+
*
|
|
54
|
+
* `null`/absent when the user doesn't have one (or doesn't know), in
|
|
55
|
+
* which case correlation falls back first to `extractCorrelationIds`'s
|
|
56
|
+
* generic key-shape heuristic and, failing that, to a timestamp+path
|
|
57
|
+
* window -- both weaker than a declared field, and both disclosed via
|
|
58
|
+
* `Correlation.method`/`confidence` on the result, never silently
|
|
59
|
+
* promoted to look as certain as a field the user actually named. This is
|
|
60
|
+
* the same "ask rather than guess" principle DEC-266 applies to target
|
|
61
|
+
* URLs, applied here to trace identity.
|
|
62
|
+
*/
|
|
63
|
+
readonly traceIdField?: string | null;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=log-source-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log-source-config.d.ts","sourceRoot":"","sources":["../src/log-source-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,GACzB;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GACzF;IAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnD,eAAO,MAAM,kBAAkB,uCAAwC,CAAC;AACxE;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAElE,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IACjC;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The "ask, don't guess" input contract for remote log ingestion (DEC-271).
|
|
3
|
+
*
|
|
4
|
+
* For local/booted execution Descry injects its own proxy and needs to ask
|
|
5
|
+
* the user nothing. For remote environments (staging/preview/production,
|
|
6
|
+
* DEC-266) Descry does not own the infra and cannot inject anything -- the
|
|
7
|
+
* only mechanism left is ingesting whatever the target already has, and
|
|
8
|
+
* DEC-271's own "rejected: one universal mechanism" clause says that target
|
|
9
|
+
* is heterogeneous by nature (Datadog, CloudWatch, plain stdout, full
|
|
10
|
+
* OpenTelemetry, or nothing at all). So setup has to ask: where are the
|
|
11
|
+
* logs, what shape are they in, and is there a field that already carries a
|
|
12
|
+
* trace/request id.
|
|
13
|
+
*
|
|
14
|
+
* This file defines that shape only -- it is filled in by whatever asks the
|
|
15
|
+
* user (a CLI prompt, a UI form, elsewhere), never inferred or defaulted
|
|
16
|
+
* here. The logic that consumes it lives in the adapters
|
|
17
|
+
* (`file-log-adapter.ts` today) and in `correlator.ts`.
|
|
18
|
+
*/
|
|
19
|
+
export const REMOTE_LOG_FORMATS = ["plain-text", "json-lines"];
|
|
20
|
+
//# sourceMappingURL=log-source-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log-source-config.js","sourceRoot":"","sources":["../src/log-source-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAeH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,YAAY,EAAE,YAAY,CAAU,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@descryy/runtime-remote-log-ingestion",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Remote-environment log ingestion (DEC-271): for staging/preview/production, where Descry cannot inject a proxy into infra it doesn't own, ingest whatever the target already has -- logs, and trace ids if a field is declared -- and correlate best-effort against a captured browser/proxy request, with confidence disclosed rather than assumed.",
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=22.5"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"registry": "https://registry.npmjs.org",
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc -b"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@descryy/runtime-contracts": "0.0.0",
|
|
28
|
+
"@descryy/runtime-backend-observation": "0.0.0",
|
|
29
|
+
"@descryy/runtime-evidence-store": "0.0.0"
|
|
30
|
+
}
|
|
31
|
+
}
|