@entifix/tooling 0.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/LICENSE +21 -0
- package/README.md +24 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/logging/create-logger.d.ts +24 -0
- package/dist/logging/create-logger.d.ts.map +1 -0
- package/dist/logging/create-logger.js +70 -0
- package/dist/logging/create-logger.js.map +1 -0
- package/dist/logging/index.d.ts +7 -0
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/logging/index.js +8 -0
- package/dist/logging/index.js.map +1 -0
- package/dist/logging/level.d.ts +13 -0
- package/dist/logging/level.d.ts.map +1 -0
- package/dist/logging/level.js +22 -0
- package/dist/logging/level.js.map +1 -0
- package/dist/logging/redact.d.ts +14 -0
- package/dist/logging/redact.d.ts.map +1 -0
- package/dist/logging/redact.js +31 -0
- package/dist/logging/redact.js.map +1 -0
- package/dist/logging/sinks/otlp-http-log-sink.d.ts +37 -0
- package/dist/logging/sinks/otlp-http-log-sink.d.ts.map +1 -0
- package/dist/logging/sinks/otlp-http-log-sink.js +128 -0
- package/dist/logging/sinks/otlp-http-log-sink.js.map +1 -0
- package/dist/logging/sinks/stdout-json-sink.d.ts +14 -0
- package/dist/logging/sinks/stdout-json-sink.d.ts.map +1 -0
- package/dist/logging/sinks/stdout-json-sink.js +16 -0
- package/dist/logging/sinks/stdout-json-sink.js.map +1 -0
- package/dist/logging/types.d.ts +59 -0
- package/dist/logging/types.d.ts.map +1 -0
- package/dist/logging/types.js +8 -0
- package/dist/logging/types.js.map +1 -0
- package/dist/tracking/index.d.ts +3 -0
- package/dist/tracking/index.d.ts.map +1 -0
- package/dist/tracking/index.js +4 -0
- package/dist/tracking/index.js.map +1 -0
- package/dist/tracking/noop-tracker.d.ts +8 -0
- package/dist/tracking/noop-tracker.d.ts.map +1 -0
- package/dist/tracking/noop-tracker.js +11 -0
- package/dist/tracking/noop-tracker.js.map +1 -0
- package/dist/tracking/types.d.ts +27 -0
- package/dist/tracking/types.d.ts.map +1 -0
- package/dist/tracking/types.js +13 -0
- package/dist/tracking/types.js.map +1 -0
- package/package.json +76 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 r10c Technologies
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# @entifix/tooling
|
|
2
|
+
|
|
3
|
+
Framework-free platform tooling built on the OpenTelemetry standard. One leaf
|
|
4
|
+
package, two independent modules (no shared interface between them):
|
|
5
|
+
|
|
6
|
+
- **`@entifix/tooling/logging`** — a leveled logging facade
|
|
7
|
+
(`createLogger`) over a pluggable `LogSink`. Stamps every record with the
|
|
8
|
+
service name, an OTel `SeverityNumber`, and — when a span is active — the
|
|
9
|
+
active `trace_id`/`span_id`, so logs join their traces in Grafana. Ships three
|
|
10
|
+
sinks: `makeStdoutJsonSink` (backend/prod → tailed by the Collector's filelog
|
|
11
|
+
receiver), `makeOtlpHttpLogSink` (dev → OTLP to `grafana/otel-lgtm`), and the
|
|
12
|
+
`LogSink` interface for anything else (browser `/api/telemetry`, test doubles).
|
|
13
|
+
`redactRecord` masks the canonical secret keys before any sink sees them.
|
|
14
|
+
|
|
15
|
+
- **`@entifix/tooling/tracking`** — the product-analytics facade: the
|
|
16
|
+
`Tracker` interface (`track`/`identify`/`flag`) plus a `NoopTracker`. The real
|
|
17
|
+
vendor adapter lives in `@entifix/posthog`; `Tracker` is
|
|
18
|
+
provided per environment behind `TrackerTag` (Effect `Context.Tag`).
|
|
19
|
+
|
|
20
|
+
Why framework-free (not an Effect wrap): the browser and Next server are not
|
|
21
|
+
Effect, so only an OTel-standard facade can serve every runtime from one package.
|
|
22
|
+
Effect backends adapt it in ~10 lines at their composition root (`Logger.replace`).
|
|
23
|
+
|
|
24
|
+
The package depends only on `@opentelemetry/api`.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from './logging/index.js';\nexport * from './tracking/index.js';\n"],"names":[],"mappings":"AAAA,cAAc,qBAAqB;AACnC,cAAc,sBAAsB"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Attributes, Logger, LogLevel, LogRecord, LogSink } from './types.js';
|
|
2
|
+
/** Options for {@link createLogger}. */
|
|
3
|
+
export interface CreateLoggerOptions {
|
|
4
|
+
/** Emitting service name (`service.name`). */
|
|
5
|
+
readonly service: string;
|
|
6
|
+
/** Minimum level; anything below is dropped before the sink. */
|
|
7
|
+
readonly level: LogLevel;
|
|
8
|
+
/** Where records go (stdout JSON, OTLP, HTTP proxy, in-memory test double). */
|
|
9
|
+
readonly sink: LogSink;
|
|
10
|
+
/** Record transform applied before emit. Defaults to {@link redactRecord}. */
|
|
11
|
+
readonly redact?: (record: LogRecord) => LogRecord;
|
|
12
|
+
/** Attributes stamped on every record (used by {@link Logger.child}). */
|
|
13
|
+
readonly bindings?: Attributes;
|
|
14
|
+
/** Clock seam for deterministic tests. Defaults to `() => new Date()`. */
|
|
15
|
+
readonly now?: () => Date;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Build a {@link Logger} over a {@link LogSink}. Framework-free: it stamps
|
|
19
|
+
* timestamp, service, severity number and (when present) trace/span ids, applies
|
|
20
|
+
* redaction, and hands the record to the sink. Transport and backend are chosen
|
|
21
|
+
* entirely by which sink is passed, so the same call site works in every runtime.
|
|
22
|
+
*/
|
|
23
|
+
export declare const createLogger: (options: CreateLoggerOptions) => Logger;
|
|
24
|
+
//# sourceMappingURL=create-logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-logger.d.ts","sourceRoot":"","sources":["../../src/logging/create-logger.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,UAAU,EACV,MAAM,EACN,QAAQ,EACR,SAAS,EACT,OAAO,EAER,MAAM,YAAY,CAAC;AAEpB,wCAAwC;AACxC,MAAM,WAAW,mBAAmB;IAClC,8CAA8C;IAC9C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,gEAAgE;IAChE,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;IACzB,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,8EAA8E;IAC9E,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,SAAS,CAAC;IACnD,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,CAAC,EAAE,UAAU,CAAC;IAC/B,0EAA0E;IAC1E,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B;AAqBD;;;;;GAKG;AACH,eAAO,MAAM,YAAY,GAAI,SAAS,mBAAmB,KAAG,MAyC3D,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
import { trace } from '@opentelemetry/api';
|
|
3
|
+
import { SEVERITY_NUMBER, shouldLog } from './level.js';
|
|
4
|
+
import { redactRecord } from './redact.js';
|
|
5
|
+
const serializeError = (error)=>error instanceof Error ? {
|
|
6
|
+
name: error.name,
|
|
7
|
+
message: error.message,
|
|
8
|
+
stack: error.stack
|
|
9
|
+
} : {
|
|
10
|
+
name: 'NonError',
|
|
11
|
+
message: String(error)
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* The trace-correlation link: read the active OpenTelemetry span. When a span is
|
|
15
|
+
* active (e.g. an in-flight HTTP request under `@effect/opentelemetry`), its
|
|
16
|
+
* ids are stamped on the record so the log joins its trace in Grafana/Tempo.
|
|
17
|
+
*/ const activeSpanContext = ()=>{
|
|
18
|
+
const span = trace.getActiveSpan();
|
|
19
|
+
if (span === undefined) {
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
const context = span.spanContext();
|
|
23
|
+
return {
|
|
24
|
+
traceId: context.traceId,
|
|
25
|
+
spanId: context.spanId
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Build a {@link Logger} over a {@link LogSink}. Framework-free: it stamps
|
|
30
|
+
* timestamp, service, severity number and (when present) trace/span ids, applies
|
|
31
|
+
* redaction, and hands the record to the sink. Transport and backend are chosen
|
|
32
|
+
* entirely by which sink is passed, so the same call site works in every runtime.
|
|
33
|
+
*/ export const createLogger = (options)=>{
|
|
34
|
+
var _options_bindings, _options_redact, _options_now;
|
|
35
|
+
const { service, level, sink } = options;
|
|
36
|
+
const bindings = (_options_bindings = options.bindings) != null ? _options_bindings : {};
|
|
37
|
+
const redact = (_options_redact = options.redact) != null ? _options_redact : redactRecord;
|
|
38
|
+
const now = (_options_now = options.now) != null ? _options_now : ()=>new Date();
|
|
39
|
+
const write = (logLevel, message, attributes, error)=>{
|
|
40
|
+
if (!shouldLog(level, logLevel)) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const { traceId, spanId } = activeSpanContext();
|
|
44
|
+
const record = _extends({
|
|
45
|
+
timestamp: now().toISOString(),
|
|
46
|
+
level: logLevel,
|
|
47
|
+
severityNumber: SEVERITY_NUMBER[logLevel],
|
|
48
|
+
service,
|
|
49
|
+
message,
|
|
50
|
+
attributes: _extends({}, bindings, attributes)
|
|
51
|
+
}, traceId !== undefined ? {
|
|
52
|
+
traceId,
|
|
53
|
+
spanId
|
|
54
|
+
} : {}, error !== undefined ? {
|
|
55
|
+
error: serializeError(error)
|
|
56
|
+
} : {});
|
|
57
|
+
sink.emit(redact(record));
|
|
58
|
+
};
|
|
59
|
+
return {
|
|
60
|
+
debug: (message, attributes = {})=>write('debug', message, attributes),
|
|
61
|
+
info: (message, attributes = {})=>write('info', message, attributes),
|
|
62
|
+
warn: (message, attributes = {})=>write('warn', message, attributes),
|
|
63
|
+
error: (message, error, attributes = {})=>write('error', message, attributes, error),
|
|
64
|
+
child: (bindingsToAdd)=>createLogger(_extends({}, options, {
|
|
65
|
+
bindings: _extends({}, bindings, bindingsToAdd)
|
|
66
|
+
}))
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
//# sourceMappingURL=create-logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/logging/create-logger.ts"],"sourcesContent":["import { trace } from '@opentelemetry/api';\n\nimport { SEVERITY_NUMBER, shouldLog } from './level.js';\nimport { redactRecord } from './redact.js';\nimport type {\n Attributes,\n Logger,\n LogLevel,\n LogRecord,\n LogSink,\n SerializedError,\n} from './types.js';\n\n/** Options for {@link createLogger}. */\nexport interface CreateLoggerOptions {\n /** Emitting service name (`service.name`). */\n readonly service: string;\n /** Minimum level; anything below is dropped before the sink. */\n readonly level: LogLevel;\n /** Where records go (stdout JSON, OTLP, HTTP proxy, in-memory test double). */\n readonly sink: LogSink;\n /** Record transform applied before emit. Defaults to {@link redactRecord}. */\n readonly redact?: (record: LogRecord) => LogRecord;\n /** Attributes stamped on every record (used by {@link Logger.child}). */\n readonly bindings?: Attributes;\n /** Clock seam for deterministic tests. Defaults to `() => new Date()`. */\n readonly now?: () => Date;\n}\n\nconst serializeError = (error: unknown): SerializedError =>\n error instanceof Error\n ? { name: error.name, message: error.message, stack: error.stack }\n : { name: 'NonError', message: String(error) };\n\n/**\n * The trace-correlation link: read the active OpenTelemetry span. When a span is\n * active (e.g. an in-flight HTTP request under `@effect/opentelemetry`), its\n * ids are stamped on the record so the log joins its trace in Grafana/Tempo.\n */\nconst activeSpanContext = (): { traceId?: string; spanId?: string } => {\n const span = trace.getActiveSpan();\n if (span === undefined) {\n return {};\n }\n const context = span.spanContext();\n return { traceId: context.traceId, spanId: context.spanId };\n};\n\n/**\n * Build a {@link Logger} over a {@link LogSink}. Framework-free: it stamps\n * timestamp, service, severity number and (when present) trace/span ids, applies\n * redaction, and hands the record to the sink. Transport and backend are chosen\n * entirely by which sink is passed, so the same call site works in every runtime.\n */\nexport const createLogger = (options: CreateLoggerOptions): Logger => {\n const { service, level, sink } = options;\n const bindings = options.bindings ?? {};\n const redact = options.redact ?? redactRecord;\n const now = options.now ?? (() => new Date());\n\n const write = (\n logLevel: LogLevel,\n message: string,\n attributes: Attributes,\n error?: unknown,\n ): void => {\n if (!shouldLog(level, logLevel)) {\n return;\n }\n const { traceId, spanId } = activeSpanContext();\n const record: LogRecord = {\n timestamp: now().toISOString(),\n level: logLevel,\n severityNumber: SEVERITY_NUMBER[logLevel],\n service,\n message,\n attributes: { ...bindings, ...attributes },\n ...(traceId !== undefined ? { traceId, spanId } : {}),\n ...(error !== undefined ? { error: serializeError(error) } : {}),\n };\n sink.emit(redact(record));\n };\n\n return {\n debug: (message, attributes = {}) => write('debug', message, attributes),\n info: (message, attributes = {}) => write('info', message, attributes),\n warn: (message, attributes = {}) => write('warn', message, attributes),\n error: (message, error, attributes = {}) =>\n write('error', message, attributes, error),\n child: bindingsToAdd =>\n createLogger({\n ...options,\n bindings: { ...bindings, ...bindingsToAdd },\n }),\n };\n};\n"],"names":["trace","SEVERITY_NUMBER","shouldLog","redactRecord","serializeError","error","Error","name","message","stack","String","activeSpanContext","span","getActiveSpan","undefined","context","spanContext","traceId","spanId","createLogger","options","service","level","sink","bindings","redact","now","Date","write","logLevel","attributes","record","timestamp","toISOString","severityNumber","emit","debug","info","warn","child","bindingsToAdd"],"mappings":";AAAA,SAASA,KAAK,QAAQ,qBAAqB;AAE3C,SAASC,eAAe,EAAEC,SAAS,QAAQ,aAAa;AACxD,SAASC,YAAY,QAAQ,cAAc;AA0B3C,MAAMC,iBAAiB,CAACC,QACtBA,iBAAiBC,QACb;QAAEC,MAAMF,MAAME,IAAI;QAAEC,SAASH,MAAMG,OAAO;QAAEC,OAAOJ,MAAMI,KAAK;IAAC,IAC/D;QAAEF,MAAM;QAAYC,SAASE,OAAOL;IAAO;AAEjD;;;;CAIC,GACD,MAAMM,oBAAoB;IACxB,MAAMC,OAAOZ,MAAMa,aAAa;IAChC,IAAID,SAASE,WAAW;QACtB,OAAO,CAAC;IACV;IACA,MAAMC,UAAUH,KAAKI,WAAW;IAChC,OAAO;QAAEC,SAASF,QAAQE,OAAO;QAAEC,QAAQH,QAAQG,MAAM;IAAC;AAC5D;AAEA;;;;;CAKC,GACD,OAAO,MAAMC,eAAe,CAACC;QAEVA,mBACFA,iBACHA;IAHZ,MAAM,EAAEC,OAAO,EAAEC,KAAK,EAAEC,IAAI,EAAE,GAAGH;IACjC,MAAMI,YAAWJ,oBAAAA,QAAQI,QAAQ,YAAhBJ,oBAAoB,CAAC;IACtC,MAAMK,UAASL,kBAAAA,QAAQK,MAAM,YAAdL,kBAAkBjB;IACjC,MAAMuB,OAAMN,eAAAA,QAAQM,GAAG,YAAXN,eAAgB,IAAM,IAAIO;IAEtC,MAAMC,QAAQ,CACZC,UACArB,SACAsB,YACAzB;QAEA,IAAI,CAACH,UAAUoB,OAAOO,WAAW;YAC/B;QACF;QACA,MAAM,EAAEZ,OAAO,EAAEC,MAAM,EAAE,GAAGP;QAC5B,MAAMoB,SAAoB;YACxBC,WAAWN,MAAMO,WAAW;YAC5BX,OAAOO;YACPK,gBAAgBjC,eAAe,CAAC4B,SAAS;YACzCR;YACAb;YACAsB,YAAY,aAAKN,UAAaM;WAC1Bb,YAAYH,YAAY;YAAEG;YAASC;QAAO,IAAI,CAAC,GAC/Cb,UAAUS,YAAY;YAAET,OAAOD,eAAeC;QAAO,IAAI,CAAC;QAEhEkB,KAAKY,IAAI,CAACV,OAAOM;IACnB;IAEA,OAAO;QACLK,OAAO,CAAC5B,SAASsB,aAAa,CAAC,CAAC,GAAKF,MAAM,SAASpB,SAASsB;QAC7DO,MAAM,CAAC7B,SAASsB,aAAa,CAAC,CAAC,GAAKF,MAAM,QAAQpB,SAASsB;QAC3DQ,MAAM,CAAC9B,SAASsB,aAAa,CAAC,CAAC,GAAKF,MAAM,QAAQpB,SAASsB;QAC3DzB,OAAO,CAACG,SAASH,OAAOyB,aAAa,CAAC,CAAC,GACrCF,MAAM,SAASpB,SAASsB,YAAYzB;QACtCkC,OAAOC,CAAAA,gBACLrB,aAAa,aACRC;gBACHI,UAAU,aAAKA,UAAagB;;IAElC;AACF,EAAE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/logging/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,+BAA+B,CAAC;AAC9C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/logging/index.ts"],"sourcesContent":["export * from './create-logger.js';\nexport * from './level.js';\nexport * from './redact.js';\nexport * from './sinks/otlp-http-log-sink.js';\nexport * from './sinks/stdout-json-sink.js';\nexport * from './types.js';\n"],"names":[],"mappings":"AAAA,cAAc,qBAAqB;AACnC,cAAc,aAAa;AAC3B,cAAc,cAAc;AAC5B,cAAc,gCAAgC;AAC9C,cAAc,8BAA8B;AAC5C,cAAc,aAAa"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { LogLevel } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* OpenTelemetry `SeverityNumber` for each level (DEBUG=5, INFO=9, WARN=13,
|
|
4
|
+
* ERROR=17). Emitting the standard number lets any OTel backend filter/colour
|
|
5
|
+
* logs without knowing our string names.
|
|
6
|
+
*/
|
|
7
|
+
export declare const SEVERITY_NUMBER: Record<LogLevel, number>;
|
|
8
|
+
/**
|
|
9
|
+
* Whether a record at `level` passes a logger configured with minimum `min`.
|
|
10
|
+
* A DEBUG record on a WARN logger is dropped before it ever reaches a sink.
|
|
11
|
+
*/
|
|
12
|
+
export declare const shouldLog: (min: LogLevel, level: LogLevel) => boolean;
|
|
13
|
+
//# sourceMappingURL=level.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"level.d.ts","sourceRoot":"","sources":["../../src/logging/level.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C;;;;GAIG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAKpD,CAAC;AAUF;;;GAGG;AACH,eAAO,MAAM,SAAS,GAAI,KAAK,QAAQ,EAAE,OAAO,QAAQ,KAAG,OACnB,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenTelemetry `SeverityNumber` for each level (DEBUG=5, INFO=9, WARN=13,
|
|
3
|
+
* ERROR=17). Emitting the standard number lets any OTel backend filter/colour
|
|
4
|
+
* logs without knowing our string names.
|
|
5
|
+
*/ export const SEVERITY_NUMBER = {
|
|
6
|
+
debug: 5,
|
|
7
|
+
info: 9,
|
|
8
|
+
warn: 13,
|
|
9
|
+
error: 17
|
|
10
|
+
};
|
|
11
|
+
/** Ascending rank used to gate records below the configured minimum. */ const LEVEL_ORDER = {
|
|
12
|
+
debug: 0,
|
|
13
|
+
info: 1,
|
|
14
|
+
warn: 2,
|
|
15
|
+
error: 3
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Whether a record at `level` passes a logger configured with minimum `min`.
|
|
19
|
+
* A DEBUG record on a WARN logger is dropped before it ever reaches a sink.
|
|
20
|
+
*/ export const shouldLog = (min, level)=>LEVEL_ORDER[level] >= LEVEL_ORDER[min];
|
|
21
|
+
|
|
22
|
+
//# sourceMappingURL=level.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/logging/level.ts"],"sourcesContent":["import type { LogLevel } from './types.js';\n\n/**\n * OpenTelemetry `SeverityNumber` for each level (DEBUG=5, INFO=9, WARN=13,\n * ERROR=17). Emitting the standard number lets any OTel backend filter/colour\n * logs without knowing our string names.\n */\nexport const SEVERITY_NUMBER: Record<LogLevel, number> = {\n debug: 5,\n info: 9,\n warn: 13,\n error: 17,\n};\n\n/** Ascending rank used to gate records below the configured minimum. */\nconst LEVEL_ORDER: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n};\n\n/**\n * Whether a record at `level` passes a logger configured with minimum `min`.\n * A DEBUG record on a WARN logger is dropped before it ever reaches a sink.\n */\nexport const shouldLog = (min: LogLevel, level: LogLevel): boolean =>\n LEVEL_ORDER[level] >= LEVEL_ORDER[min];\n"],"names":["SEVERITY_NUMBER","debug","info","warn","error","LEVEL_ORDER","shouldLog","min","level"],"mappings":"AAEA;;;;CAIC,GACD,OAAO,MAAMA,kBAA4C;IACvDC,OAAO;IACPC,MAAM;IACNC,MAAM;IACNC,OAAO;AACT,EAAE;AAEF,sEAAsE,GACtE,MAAMC,cAAwC;IAC5CJ,OAAO;IACPC,MAAM;IACNC,MAAM;IACNC,OAAO;AACT;AAEA;;;CAGC,GACD,OAAO,MAAME,YAAY,CAACC,KAAeC,QACvCH,WAAW,CAACG,MAAM,IAAIH,WAAW,CAACE,IAAI,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Attributes, LogRecord } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* The canonical scrub list — the same keys the `/api/telemetry` proxy and the
|
|
4
|
+
* Collector gateway processor strip. Kept here so every enforcement point shares
|
|
5
|
+
* one source of truth: a secret never reaches a sink regardless of the path.
|
|
6
|
+
*/
|
|
7
|
+
export declare const DEFAULT_REDACT_KEYS: readonly string[];
|
|
8
|
+
/** The value a redacted attribute is replaced with. */
|
|
9
|
+
export declare const REDACTED = "***";
|
|
10
|
+
/** Returns a copy of `attributes` with any matching key's value masked. */
|
|
11
|
+
export declare const redactAttributes: (attributes: Attributes, keys?: readonly string[]) => Attributes;
|
|
12
|
+
/** Default record redactor: masks sensitive attribute values, keeps the rest. */
|
|
13
|
+
export declare const redactRecord: (record: LogRecord, keys?: readonly string[]) => LogRecord;
|
|
14
|
+
//# sourceMappingURL=redact.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../../src/logging/redact.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAExD;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAQhD,CAAC;AAEF,uDAAuD;AACvD,eAAO,MAAM,QAAQ,QAAQ,CAAC;AAS9B,2EAA2E;AAC3E,eAAO,MAAM,gBAAgB,GAC3B,YAAY,UAAU,EACtB,OAAM,SAAS,MAAM,EAAwB,KAC5C,UAMF,CAAC;AAEF,iFAAiF;AACjF,eAAO,MAAM,YAAY,GACvB,QAAQ,SAAS,EACjB,OAAM,SAAS,MAAM,EAAwB,KAC5C,SAGD,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* The canonical scrub list — the same keys the `/api/telemetry` proxy and the
|
|
4
|
+
* Collector gateway processor strip. Kept here so every enforcement point shares
|
|
5
|
+
* one source of truth: a secret never reaches a sink regardless of the path.
|
|
6
|
+
*/ export const DEFAULT_REDACT_KEYS = [
|
|
7
|
+
'authorization',
|
|
8
|
+
'cookie',
|
|
9
|
+
'set-cookie',
|
|
10
|
+
'entifix_at',
|
|
11
|
+
'entifix_sid',
|
|
12
|
+
'password',
|
|
13
|
+
'token'
|
|
14
|
+
];
|
|
15
|
+
/** The value a redacted attribute is replaced with. */ export const REDACTED = '***';
|
|
16
|
+
const isRedactedKey = (key, keys)=>{
|
|
17
|
+
const lower = key.toLowerCase();
|
|
18
|
+
return keys.some((candidate)=>candidate.toLowerCase() === lower);
|
|
19
|
+
};
|
|
20
|
+
/** Returns a copy of `attributes` with any matching key's value masked. */ export const redactAttributes = (attributes, keys = DEFAULT_REDACT_KEYS)=>{
|
|
21
|
+
const out = {};
|
|
22
|
+
for (const [key, value] of Object.entries(attributes)){
|
|
23
|
+
out[key] = isRedactedKey(key, keys) ? REDACTED : value;
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
};
|
|
27
|
+
/** Default record redactor: masks sensitive attribute values, keeps the rest. */ export const redactRecord = (record, keys = DEFAULT_REDACT_KEYS)=>_extends({}, record, {
|
|
28
|
+
attributes: redactAttributes(record.attributes, keys)
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
//# sourceMappingURL=redact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/logging/redact.ts"],"sourcesContent":["import type { Attributes, LogRecord } from './types.js';\n\n/**\n * The canonical scrub list — the same keys the `/api/telemetry` proxy and the\n * Collector gateway processor strip. Kept here so every enforcement point shares\n * one source of truth: a secret never reaches a sink regardless of the path.\n */\nexport const DEFAULT_REDACT_KEYS: readonly string[] = [\n 'authorization',\n 'cookie',\n 'set-cookie',\n 'entifix_at',\n 'entifix_sid',\n 'password',\n 'token',\n];\n\n/** The value a redacted attribute is replaced with. */\nexport const REDACTED = '***';\n\ntype AttributeValue = Attributes[string];\n\nconst isRedactedKey = (key: string, keys: readonly string[]): boolean => {\n const lower = key.toLowerCase();\n return keys.some(candidate => candidate.toLowerCase() === lower);\n};\n\n/** Returns a copy of `attributes` with any matching key's value masked. */\nexport const redactAttributes = (\n attributes: Attributes,\n keys: readonly string[] = DEFAULT_REDACT_KEYS,\n): Attributes => {\n const out: Record<string, AttributeValue> = {};\n for (const [key, value] of Object.entries(attributes)) {\n out[key] = isRedactedKey(key, keys) ? REDACTED : (value as AttributeValue);\n }\n return out;\n};\n\n/** Default record redactor: masks sensitive attribute values, keeps the rest. */\nexport const redactRecord = (\n record: LogRecord,\n keys: readonly string[] = DEFAULT_REDACT_KEYS,\n): LogRecord => ({\n ...record,\n attributes: redactAttributes(record.attributes, keys),\n});\n"],"names":["DEFAULT_REDACT_KEYS","REDACTED","isRedactedKey","key","keys","lower","toLowerCase","some","candidate","redactAttributes","attributes","out","value","Object","entries","redactRecord","record"],"mappings":";AAEA;;;;CAIC,GACD,OAAO,MAAMA,sBAAyC;IACpD;IACA;IACA;IACA;IACA;IACA;IACA;CACD,CAAC;AAEF,qDAAqD,GACrD,OAAO,MAAMC,WAAW,MAAM;AAI9B,MAAMC,gBAAgB,CAACC,KAAaC;IAClC,MAAMC,QAAQF,IAAIG,WAAW;IAC7B,OAAOF,KAAKG,IAAI,CAACC,CAAAA,YAAaA,UAAUF,WAAW,OAAOD;AAC5D;AAEA,yEAAyE,GACzE,OAAO,MAAMI,mBAAmB,CAC9BC,YACAN,OAA0BJ,mBAAmB;IAE7C,MAAMW,MAAsC,CAAC;IAC7C,KAAK,MAAM,CAACR,KAAKS,MAAM,IAAIC,OAAOC,OAAO,CAACJ,YAAa;QACrDC,GAAG,CAACR,IAAI,GAAGD,cAAcC,KAAKC,QAAQH,WAAYW;IACpD;IACA,OAAOD;AACT,EAAE;AAEF,+EAA+E,GAC/E,OAAO,MAAMI,eAAe,CAC1BC,QACAZ,OAA0BJ,mBAAmB,GAC9B,aACZgB;QACHN,YAAYD,iBAAiBO,OAAON,UAAU,EAAEN;OAC/C"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { LogRecord, LogSink } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* The dev sink: batches records and POSTs them as OTLP/HTTP JSON to a Collector
|
|
4
|
+
* (the local `grafana/otel-lgtm` at its `/v1/logs` endpoint). Hand-rolled on
|
|
5
|
+
* `fetch` so the framework-free leaf keeps a single runtime dep
|
|
6
|
+
* (`@opentelemetry/api`) instead of pulling the OTel logs SDK.
|
|
7
|
+
*/
|
|
8
|
+
export interface OtlpHttpLogSinkOptions {
|
|
9
|
+
/** Collector base URL, e.g. `http://127.0.0.1:30318`. `/v1/logs` is appended. */
|
|
10
|
+
readonly endpoint: string;
|
|
11
|
+
/** `service.name` stamped on the OTLP resource. */
|
|
12
|
+
readonly serviceName: string;
|
|
13
|
+
/** Flush once the buffer reaches this many records (default 20). */
|
|
14
|
+
readonly batchSize?: number;
|
|
15
|
+
/**
|
|
16
|
+
* Also flush on this interval (ms) so low-volume producers are not stuck below
|
|
17
|
+
* {@link batchSize}. Omit for size-only flushing. The timer is `unref`'d so it
|
|
18
|
+
* never keeps the process alive.
|
|
19
|
+
*/
|
|
20
|
+
readonly flushIntervalMs?: number;
|
|
21
|
+
/** `fetch` seam; defaults to the global. */
|
|
22
|
+
readonly fetchImpl?: typeof fetch;
|
|
23
|
+
/** Called when a flush fails (default: swallow — telemetry is best-effort). */
|
|
24
|
+
readonly onError?: (error: unknown) => void;
|
|
25
|
+
}
|
|
26
|
+
/** A {@link LogSink} that can be flushed on demand (e.g. before shutdown). */
|
|
27
|
+
export interface OtlpHttpLogSink extends LogSink {
|
|
28
|
+
flush(): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Encode records into the OTLP/HTTP JSON `logs` payload. Exported so the shape
|
|
32
|
+
* is unit-testable without a live Collector.
|
|
33
|
+
*/
|
|
34
|
+
export declare const toOtlpLogsPayload: (records: readonly LogRecord[], serviceName: string) => unknown;
|
|
35
|
+
/** Build a batching OTLP/HTTP log sink. */
|
|
36
|
+
export declare const makeOtlpHttpLogSink: (options: OtlpHttpLogSinkOptions) => OtlpHttpLogSink;
|
|
37
|
+
//# sourceMappingURL=otlp-http-log-sink.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"otlp-http-log-sink.d.ts","sourceRoot":"","sources":["../../../src/logging/sinks/otlp-http-log-sink.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,SAAS,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEtE;;;;;GAKG;AACH,MAAM,WAAW,sBAAsB;IACrC,iFAAiF;IACjF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,mDAAmD;IACnD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,oEAAoE;IACpE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,4CAA4C;IAC5C,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IAClC,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CAC7C;AAED,8EAA8E;AAC9E,MAAM,WAAW,eAAgB,SAAQ,OAAO;IAC9C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AA4CD;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAC5B,SAAS,SAAS,SAAS,EAAE,EAC7B,aAAa,MAAM,KAClB,OA2BD,CAAC;AAEH,2CAA2C;AAC3C,eAAO,MAAM,mBAAmB,GAC9B,SAAS,sBAAsB,KAC9B,eA8CF,CAAC"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
const toAnyValue = (value)=>{
|
|
3
|
+
if (typeof value === 'string') {
|
|
4
|
+
return {
|
|
5
|
+
stringValue: value
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
if (typeof value === 'number') {
|
|
9
|
+
return Number.isInteger(value) ? {
|
|
10
|
+
intValue: value
|
|
11
|
+
} : {
|
|
12
|
+
doubleValue: value
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
if (typeof value === 'boolean') {
|
|
16
|
+
return {
|
|
17
|
+
boolValue: value
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
// `null` (the only remaining AttributeValue) carries no typed value.
|
|
21
|
+
return {
|
|
22
|
+
stringValue: 'null'
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
const toKeyValues = (attributes)=>{
|
|
26
|
+
const out = [];
|
|
27
|
+
for (const [key, value] of Object.entries(attributes)){
|
|
28
|
+
if (value !== undefined) {
|
|
29
|
+
out.push({
|
|
30
|
+
key,
|
|
31
|
+
value: toAnyValue(value)
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
};
|
|
37
|
+
const SEVERITY_TEXT = {
|
|
38
|
+
debug: 'DEBUG',
|
|
39
|
+
info: 'INFO',
|
|
40
|
+
warn: 'WARN',
|
|
41
|
+
error: 'ERROR'
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Encode records into the OTLP/HTTP JSON `logs` payload. Exported so the shape
|
|
45
|
+
* is unit-testable without a live Collector.
|
|
46
|
+
*/ export const toOtlpLogsPayload = (records, serviceName)=>({
|
|
47
|
+
resourceLogs: [
|
|
48
|
+
{
|
|
49
|
+
resource: {
|
|
50
|
+
attributes: [
|
|
51
|
+
{
|
|
52
|
+
key: 'service.name',
|
|
53
|
+
value: {
|
|
54
|
+
stringValue: serviceName
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
scopeLogs: [
|
|
60
|
+
{
|
|
61
|
+
scope: {
|
|
62
|
+
name: '@entifix/tooling'
|
|
63
|
+
},
|
|
64
|
+
logRecords: records.map((record)=>_extends({
|
|
65
|
+
timeUnixNano: (BigInt(Date.parse(record.timestamp)) * 1000000n).toString(),
|
|
66
|
+
severityNumber: record.severityNumber,
|
|
67
|
+
severityText: SEVERITY_TEXT[record.level],
|
|
68
|
+
body: {
|
|
69
|
+
stringValue: record.message
|
|
70
|
+
},
|
|
71
|
+
attributes: toKeyValues(record.attributes)
|
|
72
|
+
}, record.traceId !== undefined ? {
|
|
73
|
+
traceId: record.traceId,
|
|
74
|
+
spanId: record.spanId
|
|
75
|
+
} : {}))
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
});
|
|
81
|
+
/** Build a batching OTLP/HTTP log sink. */ export const makeOtlpHttpLogSink = (options)=>{
|
|
82
|
+
var _options_batchSize, _options_fetchImpl, _options_onError;
|
|
83
|
+
const batchSize = (_options_batchSize = options.batchSize) != null ? _options_batchSize : 20;
|
|
84
|
+
// Resolve the global `fetch` at call time (bound to `globalThis`): a bare
|
|
85
|
+
// `fetch` reference can be dropped by a bundler, whereas `globalThis.fetch`
|
|
86
|
+
// survives bundling and stays correct in Node and the browser.
|
|
87
|
+
const fetchImpl = (_options_fetchImpl = options.fetchImpl) != null ? _options_fetchImpl : (input, init)=>globalThis.fetch(input, init);
|
|
88
|
+
const onError = (_options_onError = options.onError) != null ? _options_onError : ()=>undefined;
|
|
89
|
+
const url = `${options.endpoint}/v1/logs`;
|
|
90
|
+
let buffer = [];
|
|
91
|
+
const flush = async ()=>{
|
|
92
|
+
if (buffer.length === 0) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const batch = buffer;
|
|
96
|
+
buffer = [];
|
|
97
|
+
try {
|
|
98
|
+
const response = await fetchImpl(url, {
|
|
99
|
+
method: 'POST',
|
|
100
|
+
headers: {
|
|
101
|
+
'content-type': 'application/json'
|
|
102
|
+
},
|
|
103
|
+
body: JSON.stringify(toOtlpLogsPayload(batch, options.serviceName))
|
|
104
|
+
});
|
|
105
|
+
if (!response.ok) {
|
|
106
|
+
onError(new Error(`OTLP logs export failed: ${response.status}`));
|
|
107
|
+
}
|
|
108
|
+
} catch (error) {
|
|
109
|
+
onError(error);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
if (options.flushIntervalMs !== undefined) {
|
|
113
|
+
const timer = setInterval(()=>void flush(), options.flushIntervalMs);
|
|
114
|
+
// Never let the flush timer hold the process open.
|
|
115
|
+
timer.unref == null ? void 0 : timer.unref.call(timer);
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
emit: (record)=>{
|
|
119
|
+
buffer.push(record);
|
|
120
|
+
if (buffer.length >= batchSize) {
|
|
121
|
+
void flush();
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
flush
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
//# sourceMappingURL=otlp-http-log-sink.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/logging/sinks/otlp-http-log-sink.ts"],"sourcesContent":["import type { AttributeValue, LogRecord, LogSink } from '../types.js';\n\n/**\n * The dev sink: batches records and POSTs them as OTLP/HTTP JSON to a Collector\n * (the local `grafana/otel-lgtm` at its `/v1/logs` endpoint). Hand-rolled on\n * `fetch` so the framework-free leaf keeps a single runtime dep\n * (`@opentelemetry/api`) instead of pulling the OTel logs SDK.\n */\nexport interface OtlpHttpLogSinkOptions {\n /** Collector base URL, e.g. `http://127.0.0.1:30318`. `/v1/logs` is appended. */\n readonly endpoint: string;\n /** `service.name` stamped on the OTLP resource. */\n readonly serviceName: string;\n /** Flush once the buffer reaches this many records (default 20). */\n readonly batchSize?: number;\n /**\n * Also flush on this interval (ms) so low-volume producers are not stuck below\n * {@link batchSize}. Omit for size-only flushing. The timer is `unref`'d so it\n * never keeps the process alive.\n */\n readonly flushIntervalMs?: number;\n /** `fetch` seam; defaults to the global. */\n readonly fetchImpl?: typeof fetch;\n /** Called when a flush fails (default: swallow — telemetry is best-effort). */\n readonly onError?: (error: unknown) => void;\n}\n\n/** A {@link LogSink} that can be flushed on demand (e.g. before shutdown). */\nexport interface OtlpHttpLogSink extends LogSink {\n flush(): Promise<void>;\n}\n\ninterface OtlpAnyValue {\n stringValue?: string;\n intValue?: number;\n doubleValue?: number;\n boolValue?: boolean;\n}\n\nconst toAnyValue = (value: AttributeValue): OtlpAnyValue => {\n if (typeof value === 'string') {\n return { stringValue: value };\n }\n if (typeof value === 'number') {\n return Number.isInteger(value)\n ? { intValue: value }\n : { doubleValue: value };\n }\n if (typeof value === 'boolean') {\n return { boolValue: value };\n }\n // `null` (the only remaining AttributeValue) carries no typed value.\n return { stringValue: 'null' };\n};\n\nconst toKeyValues = (\n attributes: Record<string, AttributeValue | undefined>,\n): Array<{ key: string; value: OtlpAnyValue }> => {\n const out: Array<{ key: string; value: OtlpAnyValue }> = [];\n for (const [key, value] of Object.entries(attributes)) {\n if (value !== undefined) {\n out.push({ key, value: toAnyValue(value) });\n }\n }\n return out;\n};\n\nconst SEVERITY_TEXT: Record<LogRecord['level'], string> = {\n debug: 'DEBUG',\n info: 'INFO',\n warn: 'WARN',\n error: 'ERROR',\n};\n\n/**\n * Encode records into the OTLP/HTTP JSON `logs` payload. Exported so the shape\n * is unit-testable without a live Collector.\n */\nexport const toOtlpLogsPayload = (\n records: readonly LogRecord[],\n serviceName: string,\n): unknown => ({\n resourceLogs: [\n {\n resource: {\n attributes: [\n { key: 'service.name', value: { stringValue: serviceName } },\n ],\n },\n scopeLogs: [\n {\n scope: { name: '@entifix/tooling' },\n logRecords: records.map(record => ({\n timeUnixNano: (\n BigInt(Date.parse(record.timestamp)) * 1_000_000n\n ).toString(),\n severityNumber: record.severityNumber,\n severityText: SEVERITY_TEXT[record.level],\n body: { stringValue: record.message },\n attributes: toKeyValues(record.attributes),\n ...(record.traceId !== undefined\n ? { traceId: record.traceId, spanId: record.spanId }\n : {}),\n })),\n },\n ],\n },\n ],\n});\n\n/** Build a batching OTLP/HTTP log sink. */\nexport const makeOtlpHttpLogSink = (\n options: OtlpHttpLogSinkOptions,\n): OtlpHttpLogSink => {\n const batchSize = options.batchSize ?? 20;\n // Resolve the global `fetch` at call time (bound to `globalThis`): a bare\n // `fetch` reference can be dropped by a bundler, whereas `globalThis.fetch`\n // survives bundling and stays correct in Node and the browser.\n const fetchImpl =\n options.fetchImpl ?? ((input, init) => globalThis.fetch(input, init));\n const onError = options.onError ?? (() => undefined);\n const url = `${options.endpoint}/v1/logs`;\n let buffer: LogRecord[] = [];\n\n const flush = async (): Promise<void> => {\n if (buffer.length === 0) {\n return;\n }\n const batch = buffer;\n buffer = [];\n try {\n const response = await fetchImpl(url, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(toOtlpLogsPayload(batch, options.serviceName)),\n });\n if (!response.ok) {\n onError(new Error(`OTLP logs export failed: ${response.status}`));\n }\n } catch (error) {\n onError(error);\n }\n };\n\n if (options.flushIntervalMs !== undefined) {\n const timer = setInterval(() => void flush(), options.flushIntervalMs);\n // Never let the flush timer hold the process open.\n (timer as { unref?: () => void }).unref?.();\n }\n\n return {\n emit: (record: LogRecord) => {\n buffer.push(record);\n if (buffer.length >= batchSize) {\n void flush();\n }\n },\n flush,\n };\n};\n"],"names":["toAnyValue","value","stringValue","Number","isInteger","intValue","doubleValue","boolValue","toKeyValues","attributes","out","key","Object","entries","undefined","push","SEVERITY_TEXT","debug","info","warn","error","toOtlpLogsPayload","records","serviceName","resourceLogs","resource","scopeLogs","scope","name","logRecords","map","record","timeUnixNano","BigInt","Date","parse","timestamp","toString","severityNumber","severityText","level","body","message","traceId","spanId","makeOtlpHttpLogSink","options","batchSize","fetchImpl","input","init","globalThis","fetch","onError","url","endpoint","buffer","flush","length","batch","response","method","headers","JSON","stringify","ok","Error","status","flushIntervalMs","timer","setInterval","unref","emit"],"mappings":";AAuCA,MAAMA,aAAa,CAACC;IAClB,IAAI,OAAOA,UAAU,UAAU;QAC7B,OAAO;YAAEC,aAAaD;QAAM;IAC9B;IACA,IAAI,OAAOA,UAAU,UAAU;QAC7B,OAAOE,OAAOC,SAAS,CAACH,SACpB;YAAEI,UAAUJ;QAAM,IAClB;YAAEK,aAAaL;QAAM;IAC3B;IACA,IAAI,OAAOA,UAAU,WAAW;QAC9B,OAAO;YAAEM,WAAWN;QAAM;IAC5B;IACA,qEAAqE;IACrE,OAAO;QAAEC,aAAa;IAAO;AAC/B;AAEA,MAAMM,cAAc,CAClBC;IAEA,MAAMC,MAAmD,EAAE;IAC3D,KAAK,MAAM,CAACC,KAAKV,MAAM,IAAIW,OAAOC,OAAO,CAACJ,YAAa;QACrD,IAAIR,UAAUa,WAAW;YACvBJ,IAAIK,IAAI,CAAC;gBAAEJ;gBAAKV,OAAOD,WAAWC;YAAO;QAC3C;IACF;IACA,OAAOS;AACT;AAEA,MAAMM,gBAAoD;IACxDC,OAAO;IACPC,MAAM;IACNC,MAAM;IACNC,OAAO;AACT;AAEA;;;CAGC,GACD,OAAO,MAAMC,oBAAoB,CAC/BC,SACAC,cACa,CAAA;QACbC,cAAc;YACZ;gBACEC,UAAU;oBACRhB,YAAY;wBACV;4BAAEE,KAAK;4BAAgBV,OAAO;gCAAEC,aAAaqB;4BAAY;wBAAE;qBAC5D;gBACH;gBACAG,WAAW;oBACT;wBACEC,OAAO;4BAAEC,MAAM;wBAAmB;wBAClCC,YAAYP,QAAQQ,GAAG,CAACC,CAAAA,SAAW;gCACjCC,cAAc,AACZC,CAAAA,OAAOC,KAAKC,KAAK,CAACJ,OAAOK,SAAS,KAAK,QAAU,AAAD,EAChDC,QAAQ;gCACVC,gBAAgBP,OAAOO,cAAc;gCACrCC,cAAcvB,aAAa,CAACe,OAAOS,KAAK,CAAC;gCACzCC,MAAM;oCAAEvC,aAAa6B,OAAOW,OAAO;gCAAC;gCACpCjC,YAAYD,YAAYuB,OAAOtB,UAAU;+BACrCsB,OAAOY,OAAO,KAAK7B,YACnB;gCAAE6B,SAASZ,OAAOY,OAAO;gCAAEC,QAAQb,OAAOa,MAAM;4BAAC,IACjD,CAAC;oBAET;iBACD;YACH;SACD;IACH,CAAA,EAAG;AAEH,yCAAyC,GACzC,OAAO,MAAMC,sBAAsB,CACjCC;QAEkBA,oBAKhBA,oBACcA;IANhB,MAAMC,aAAYD,qBAAAA,QAAQC,SAAS,YAAjBD,qBAAqB;IACvC,0EAA0E;IAC1E,4EAA4E;IAC5E,+DAA+D;IAC/D,MAAME,aACJF,qBAAAA,QAAQE,SAAS,YAAjBF,qBAAsB,CAACG,OAAOC,OAASC,WAAWC,KAAK,CAACH,OAAOC;IACjE,MAAMG,WAAUP,mBAAAA,QAAQO,OAAO,YAAfP,mBAAoB,IAAMhC;IAC1C,MAAMwC,MAAM,GAAGR,QAAQS,QAAQ,CAAC,QAAQ,CAAC;IACzC,IAAIC,SAAsB,EAAE;IAE5B,MAAMC,QAAQ;QACZ,IAAID,OAAOE,MAAM,KAAK,GAAG;YACvB;QACF;QACA,MAAMC,QAAQH;QACdA,SAAS,EAAE;QACX,IAAI;YACF,MAAMI,WAAW,MAAMZ,UAAUM,KAAK;gBACpCO,QAAQ;gBACRC,SAAS;oBAAE,gBAAgB;gBAAmB;gBAC9CrB,MAAMsB,KAAKC,SAAS,CAAC3C,kBAAkBsC,OAAOb,QAAQvB,WAAW;YACnE;YACA,IAAI,CAACqC,SAASK,EAAE,EAAE;gBAChBZ,QAAQ,IAAIa,MAAM,CAAC,yBAAyB,EAAEN,SAASO,MAAM,EAAE;YACjE;QACF,EAAE,OAAO/C,OAAO;YACdiC,QAAQjC;QACV;IACF;IAEA,IAAI0B,QAAQsB,eAAe,KAAKtD,WAAW;QACzC,MAAMuD,QAAQC,YAAY,IAAM,KAAKb,SAASX,QAAQsB,eAAe;QACrE,mDAAmD;QAClDC,MAAiCE,KAAK,oBAAvC,AAACF,MAAiCE,KAAK,MAAtCF;IACH;IAEA,OAAO;QACLG,MAAM,CAACzC;YACLyB,OAAOzC,IAAI,CAACgB;YACZ,IAAIyB,OAAOE,MAAM,IAAIX,WAAW;gBAC9B,KAAKU;YACP;QACF;QACAA;IACF;AACF,EAAE"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { LogSink } from '../types.js';
|
|
2
|
+
/** Options for {@link makeStdoutJsonSink}. */
|
|
3
|
+
export interface StdoutJsonSinkOptions {
|
|
4
|
+
/** Write seam (defaults to `process.stdout.write`); overridden in tests. */
|
|
5
|
+
readonly write?: (line: string) => void;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The production/backend sink: one JSON object per line on stdout. Crash-safe
|
|
9
|
+
* (no buffer to flush) and picked up by the OTel Collector's `filelog` receiver
|
|
10
|
+
* tailing `/var/log/pods`. This is the default in a cluster; dev uses the OTLP
|
|
11
|
+
* sink instead because a host-run `nx dev` process has no pod to tail.
|
|
12
|
+
*/
|
|
13
|
+
export declare const makeStdoutJsonSink: (options?: StdoutJsonSinkOptions) => LogSink;
|
|
14
|
+
//# sourceMappingURL=stdout-json-sink.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stdout-json-sink.d.ts","sourceRoot":"","sources":["../../../src/logging/sinks/stdout-json-sink.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAa,OAAO,EAAE,MAAM,aAAa,CAAC;AAEtD,8CAA8C;AAC9C,MAAM,WAAW,qBAAqB;IACpC,4EAA4E;IAC5E,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAED;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAC7B,UAAS,qBAA0B,KAClC,OAOF,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The production/backend sink: one JSON object per line on stdout. Crash-safe
|
|
3
|
+
* (no buffer to flush) and picked up by the OTel Collector's `filelog` receiver
|
|
4
|
+
* tailing `/var/log/pods`. This is the default in a cluster; dev uses the OTLP
|
|
5
|
+
* sink instead because a host-run `nx dev` process has no pod to tail.
|
|
6
|
+
*/ export const makeStdoutJsonSink = (options = {})=>{
|
|
7
|
+
var _options_write;
|
|
8
|
+
const write = (_options_write = options.write) != null ? _options_write : (line)=>process.stdout.write(line);
|
|
9
|
+
return {
|
|
10
|
+
emit: (record)=>{
|
|
11
|
+
write(`${JSON.stringify(record)}\n`);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
//# sourceMappingURL=stdout-json-sink.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/logging/sinks/stdout-json-sink.ts"],"sourcesContent":["import type { LogRecord, LogSink } from '../types.js';\n\n/** Options for {@link makeStdoutJsonSink}. */\nexport interface StdoutJsonSinkOptions {\n /** Write seam (defaults to `process.stdout.write`); overridden in tests. */\n readonly write?: (line: string) => void;\n}\n\n/**\n * The production/backend sink: one JSON object per line on stdout. Crash-safe\n * (no buffer to flush) and picked up by the OTel Collector's `filelog` receiver\n * tailing `/var/log/pods`. This is the default in a cluster; dev uses the OTLP\n * sink instead because a host-run `nx dev` process has no pod to tail.\n */\nexport const makeStdoutJsonSink = (\n options: StdoutJsonSinkOptions = {},\n): LogSink => {\n const write = options.write ?? (line => process.stdout.write(line));\n return {\n emit: (record: LogRecord) => {\n write(`${JSON.stringify(record)}\\n`);\n },\n };\n};\n"],"names":["makeStdoutJsonSink","options","write","line","process","stdout","emit","record","JSON","stringify"],"mappings":"AAQA;;;;;CAKC,GACD,OAAO,MAAMA,qBAAqB,CAChCC,UAAiC,CAAC,CAAC;QAErBA;IAAd,MAAMC,SAAQD,iBAAAA,QAAQC,KAAK,YAAbD,iBAAkBE,CAAAA,OAAQC,QAAQC,MAAM,CAACH,KAAK,CAACC;IAC7D,OAAO;QACLG,MAAM,CAACC;YACLL,MAAM,GAAGM,KAAKC,SAAS,CAACF,QAAQ,EAAE,CAAC;QACrC;IACF;AACF,EAAE"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The logging facade's public shape. Framework-free on purpose: the same
|
|
3
|
+
* interface serves Effect backends, the Next server runtime, and the browser,
|
|
4
|
+
* because it is built on the OpenTelemetry log-record model rather than on any
|
|
5
|
+
* one framework's logger. See `docs/ARCHITECTURE.md` (Observability & tooling).
|
|
6
|
+
*/
|
|
7
|
+
/** The four log levels the platform uses, in ascending severity. */
|
|
8
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
9
|
+
/** A single structured attribute value. `undefined` keys are dropped by JSON. */
|
|
10
|
+
export type AttributeValue = string | number | boolean | null;
|
|
11
|
+
/** Structured key/value context attached to a log line. */
|
|
12
|
+
export interface Attributes {
|
|
13
|
+
readonly [key: string]: AttributeValue | undefined;
|
|
14
|
+
}
|
|
15
|
+
/** An error flattened to a serializable shape (never a live `Error` instance). */
|
|
16
|
+
export interface SerializedError {
|
|
17
|
+
readonly name: string;
|
|
18
|
+
readonly message: string;
|
|
19
|
+
readonly stack?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* One structured log record. This is the wire shape every sink receives; it
|
|
23
|
+
* mirrors the OpenTelemetry LogRecord (severity number, body, attributes,
|
|
24
|
+
* trace/span correlation) so a sink can forward it as OTLP unchanged.
|
|
25
|
+
*/
|
|
26
|
+
export interface LogRecord {
|
|
27
|
+
/** ISO-8601 timestamp. */
|
|
28
|
+
readonly timestamp: string;
|
|
29
|
+
readonly level: LogLevel;
|
|
30
|
+
/** OTel `SeverityNumber` for {@link level}. */
|
|
31
|
+
readonly severityNumber: number;
|
|
32
|
+
/** Emitting service name (`service.name`). */
|
|
33
|
+
readonly service: string;
|
|
34
|
+
readonly message: string;
|
|
35
|
+
readonly attributes: Attributes;
|
|
36
|
+
/** Present only when a span is active — this is the trace-correlation link. */
|
|
37
|
+
readonly traceId?: string;
|
|
38
|
+
readonly spanId?: string;
|
|
39
|
+
readonly error?: SerializedError;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* A transport for log records. The seam that keeps the package portable: the
|
|
43
|
+
* backend uses a stdout-JSON sink (tailed by the Collector's filelog receiver),
|
|
44
|
+
* the browser an HTTP sink to `/api/telemetry`, dev an OTLP sink to otel-lgtm.
|
|
45
|
+
*/
|
|
46
|
+
export interface LogSink {
|
|
47
|
+
emit(record: LogRecord): void;
|
|
48
|
+
}
|
|
49
|
+
/** The leveled logger applications call. */
|
|
50
|
+
export interface Logger {
|
|
51
|
+
debug(message: string, attributes?: Attributes): void;
|
|
52
|
+
info(message: string, attributes?: Attributes): void;
|
|
53
|
+
warn(message: string, attributes?: Attributes): void;
|
|
54
|
+
/** `error` carries an optional cause alongside the structured attributes. */
|
|
55
|
+
error(message: string, error?: unknown, attributes?: Attributes): void;
|
|
56
|
+
/** Returns a logger that stamps every record with `bindings` (e.g. per-request). */
|
|
57
|
+
child(bindings: Attributes): Logger;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/logging/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,oEAAoE;AACpE,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAE3D,iFAAiF;AACjF,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;AAE9D,2DAA2D;AAC3D,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAAC;CACpD;AAED,kFAAkF;AAClF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,0BAA0B;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;IACzB,+CAA+C;IAC/C,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,8CAA8C;IAC9C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,eAAe,CAAC;CAClC;AAED;;;;GAIG;AACH,MAAM,WAAW,OAAO;IACtB,IAAI,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAAC;CAC/B;AAED,4CAA4C;AAC5C,MAAM,WAAW,MAAM;IACrB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IACtD,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IACrD,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IACrD,6EAA6E;IAC7E,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,EAAE,UAAU,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IACvE,oFAAoF;IACpF,KAAK,CAAC,QAAQ,EAAE,UAAU,GAAG,MAAM,CAAC;CACrC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The logging facade's public shape. Framework-free on purpose: the same
|
|
3
|
+
* interface serves Effect backends, the Next server runtime, and the browser,
|
|
4
|
+
* because it is built on the OpenTelemetry log-record model rather than on any
|
|
5
|
+
* one framework's logger. See `docs/ARCHITECTURE.md` (Observability & tooling).
|
|
6
|
+
*/ /** The four log levels the platform uses, in ascending severity. */ /** The leveled logger applications call. */ export { };
|
|
7
|
+
|
|
8
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/logging/types.ts"],"sourcesContent":["/**\n * The logging facade's public shape. Framework-free on purpose: the same\n * interface serves Effect backends, the Next server runtime, and the browser,\n * because it is built on the OpenTelemetry log-record model rather than on any\n * one framework's logger. See `docs/ARCHITECTURE.md` (Observability & tooling).\n */\n\n/** The four log levels the platform uses, in ascending severity. */\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error';\n\n/** A single structured attribute value. `undefined` keys are dropped by JSON. */\nexport type AttributeValue = string | number | boolean | null;\n\n/** Structured key/value context attached to a log line. */\nexport interface Attributes {\n readonly [key: string]: AttributeValue | undefined;\n}\n\n/** An error flattened to a serializable shape (never a live `Error` instance). */\nexport interface SerializedError {\n readonly name: string;\n readonly message: string;\n readonly stack?: string;\n}\n\n/**\n * One structured log record. This is the wire shape every sink receives; it\n * mirrors the OpenTelemetry LogRecord (severity number, body, attributes,\n * trace/span correlation) so a sink can forward it as OTLP unchanged.\n */\nexport interface LogRecord {\n /** ISO-8601 timestamp. */\n readonly timestamp: string;\n readonly level: LogLevel;\n /** OTel `SeverityNumber` for {@link level}. */\n readonly severityNumber: number;\n /** Emitting service name (`service.name`). */\n readonly service: string;\n readonly message: string;\n readonly attributes: Attributes;\n /** Present only when a span is active — this is the trace-correlation link. */\n readonly traceId?: string;\n readonly spanId?: string;\n readonly error?: SerializedError;\n}\n\n/**\n * A transport for log records. The seam that keeps the package portable: the\n * backend uses a stdout-JSON sink (tailed by the Collector's filelog receiver),\n * the browser an HTTP sink to `/api/telemetry`, dev an OTLP sink to otel-lgtm.\n */\nexport interface LogSink {\n emit(record: LogRecord): void;\n}\n\n/** The leveled logger applications call. */\nexport interface Logger {\n debug(message: string, attributes?: Attributes): void;\n info(message: string, attributes?: Attributes): void;\n warn(message: string, attributes?: Attributes): void;\n /** `error` carries an optional cause alongside the structured attributes. */\n error(message: string, error?: unknown, attributes?: Attributes): void;\n /** Returns a logger that stamps every record with `bindings` (e.g. per-request). */\n child(bindings: Attributes): Logger;\n}\n"],"names":[],"mappings":"AAAA;;;;;CAKC,GAED,kEAAkE,GAgDlE,0CAA0C,GAC1C,WAQC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tracking/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/tracking/index.ts"],"sourcesContent":["export * from './noop-tracker.js';\nexport * from './types.js';\n"],"names":[],"mappings":"AAAA,cAAc,oBAAoB;AAClC,cAAc,aAAa"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Tracker } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* A {@link Tracker} that discards everything and resolves every flag to `false`.
|
|
4
|
+
* The safe default before a real adapter (PostHog) is wired into a composition
|
|
5
|
+
* root — code can depend on `Tracker` without a vendor being present yet.
|
|
6
|
+
*/
|
|
7
|
+
export declare const NoopTracker: Tracker;
|
|
8
|
+
//# sourceMappingURL=noop-tracker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"noop-tracker.d.ts","sourceRoot":"","sources":["../../src/tracking/noop-tracker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C;;;;GAIG;AACH,eAAO,MAAM,WAAW,EAAE,OAIzB,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A {@link Tracker} that discards everything and resolves every flag to `false`.
|
|
3
|
+
* The safe default before a real adapter (PostHog) is wired into a composition
|
|
4
|
+
* root — code can depend on `Tracker` without a vendor being present yet.
|
|
5
|
+
*/ export const NoopTracker = {
|
|
6
|
+
track: ()=>undefined,
|
|
7
|
+
identify: ()=>undefined,
|
|
8
|
+
flag: ()=>false
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
//# sourceMappingURL=noop-tracker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/tracking/noop-tracker.ts"],"sourcesContent":["import type { Tracker } from './types.js';\n\n/**\n * A {@link Tracker} that discards everything and resolves every flag to `false`.\n * The safe default before a real adapter (PostHog) is wired into a composition\n * root — code can depend on `Tracker` without a vendor being present yet.\n */\nexport const NoopTracker: Tracker = {\n track: () => undefined,\n identify: () => undefined,\n flag: () => false,\n};\n"],"names":["NoopTracker","track","undefined","identify","flag"],"mappings":"AAEA;;;;CAIC,GACD,OAAO,MAAMA,cAAuB;IAClCC,OAAO,IAAMC;IACbC,UAAU,IAAMD;IAChBE,MAAM,IAAM;AACd,EAAE"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The product-analytics facade. Deliberately NOT sharing an interface with
|
|
3
|
+
* logging: tracking is a different concern (business events + experiments for
|
|
4
|
+
* PMs/growth, long-retained and user-identified) that fans out to a different
|
|
5
|
+
* backend (PostHog), unlike observability logs. Same package, separate module.
|
|
6
|
+
*
|
|
7
|
+
* A real adapter (see `@entifix/posthog`) implements this
|
|
8
|
+
* interface; it is provided per environment behind `TrackerTag` (Effect
|
|
9
|
+
* `Context.Tag`, in `@entifix/business`) — the browser and backend both
|
|
10
|
+
* run Effect DI, so the same seam works in each.
|
|
11
|
+
*/
|
|
12
|
+
/** A single tracked-property value. */
|
|
13
|
+
export type PropertyValue = string | number | boolean | null;
|
|
14
|
+
/** Structured properties attached to an event or identity. */
|
|
15
|
+
export interface Props {
|
|
16
|
+
readonly [key: string]: PropertyValue | undefined;
|
|
17
|
+
}
|
|
18
|
+
/** The product-analytics + feature-flag surface applications call. */
|
|
19
|
+
export interface Tracker {
|
|
20
|
+
/** Record a business event, e.g. `track('checkout_started', { cartValue })`. */
|
|
21
|
+
track(event: string, props?: Props): void;
|
|
22
|
+
/** Associate the current actor with an id (+ optional traits). */
|
|
23
|
+
identify(id: string, traits?: Props): void;
|
|
24
|
+
/** Resolve a feature flag / experiment variant for `key`. */
|
|
25
|
+
flag(key: string): boolean | string;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/tracking/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,uCAAuC;AACvC,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;AAE7D,8DAA8D;AAC9D,MAAM,WAAW,KAAK;IACpB,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAAC;CACnD;AAED,sEAAsE;AACtE,MAAM,WAAW,OAAO;IACtB,gFAAgF;IAChF,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;IAC1C,kEAAkE;IAClE,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;IAC3C,6DAA6D;IAC7D,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;CACrC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The product-analytics facade. Deliberately NOT sharing an interface with
|
|
3
|
+
* logging: tracking is a different concern (business events + experiments for
|
|
4
|
+
* PMs/growth, long-retained and user-identified) that fans out to a different
|
|
5
|
+
* backend (PostHog), unlike observability logs. Same package, separate module.
|
|
6
|
+
*
|
|
7
|
+
* A real adapter (see `@entifix/posthog`) implements this
|
|
8
|
+
* interface; it is provided per environment behind `TrackerTag` (Effect
|
|
9
|
+
* `Context.Tag`, in `@entifix/business`) — the browser and backend both
|
|
10
|
+
* run Effect DI, so the same seam works in each.
|
|
11
|
+
*/ /** A single tracked-property value. */ /** The product-analytics + feature-flag surface applications call. */ export { };
|
|
12
|
+
|
|
13
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/tracking/types.ts"],"sourcesContent":["/**\n * The product-analytics facade. Deliberately NOT sharing an interface with\n * logging: tracking is a different concern (business events + experiments for\n * PMs/growth, long-retained and user-identified) that fans out to a different\n * backend (PostHog), unlike observability logs. Same package, separate module.\n *\n * A real adapter (see `@entifix/posthog`) implements this\n * interface; it is provided per environment behind `TrackerTag` (Effect\n * `Context.Tag`, in `@entifix/business`) — the browser and backend both\n * run Effect DI, so the same seam works in each.\n */\n\n/** A single tracked-property value. */\nexport type PropertyValue = string | number | boolean | null;\n\n/** Structured properties attached to an event or identity. */\nexport interface Props {\n readonly [key: string]: PropertyValue | undefined;\n}\n\n/** The product-analytics + feature-flag surface applications call. */\nexport interface Tracker {\n /** Record a business event, e.g. `track('checkout_started', { cartValue })`. */\n track(event: string, props?: Props): void;\n /** Associate the current actor with an id (+ optional traits). */\n identify(id: string, traits?: Props): void;\n /** Resolve a feature flag / experiment variant for `key`. */\n flag(key: string): boolean | string;\n}\n"],"names":[],"mappings":"AAAA;;;;;;;;;;CAUC,GAED,qCAAqC,GAQrC,oEAAoE,GACpE,WAOC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@entifix/tooling",
|
|
3
|
+
"version": "0.1.0-0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/r10c-technologies/entifix.git",
|
|
8
|
+
"directory": "packages/ts/tooling"
|
|
9
|
+
},
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "./dist/index.js",
|
|
15
|
+
"module": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
"./package.json": "./package.json",
|
|
19
|
+
".": {
|
|
20
|
+
"@entifix/source": "./src/index.ts",
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"import": "./dist/index.js",
|
|
23
|
+
"default": "./dist/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./logging": {
|
|
26
|
+
"@entifix/source": "./src/logging/index.ts",
|
|
27
|
+
"types": "./dist/logging/index.d.ts",
|
|
28
|
+
"import": "./dist/logging/index.js",
|
|
29
|
+
"default": "./dist/logging/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./tracking": {
|
|
32
|
+
"@entifix/source": "./src/tracking/index.ts",
|
|
33
|
+
"types": "./dist/tracking/index.d.ts",
|
|
34
|
+
"import": "./dist/tracking/index.js",
|
|
35
|
+
"default": "./dist/tracking/index.js"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"dist",
|
|
40
|
+
"!**/*.tsbuildinfo",
|
|
41
|
+
"LICENSE"
|
|
42
|
+
],
|
|
43
|
+
"nx": {
|
|
44
|
+
"tags": [
|
|
45
|
+
"layer:entifix",
|
|
46
|
+
"scope:shared",
|
|
47
|
+
"entifix:tooling",
|
|
48
|
+
"tier:0"
|
|
49
|
+
],
|
|
50
|
+
"sourceRoot": "packages/ts/tooling/src",
|
|
51
|
+
"targets": {
|
|
52
|
+
"build": {
|
|
53
|
+
"executor": "@nx/js:swc",
|
|
54
|
+
"outputs": [
|
|
55
|
+
"{options.outputPath}"
|
|
56
|
+
],
|
|
57
|
+
"options": {
|
|
58
|
+
"outputPath": "packages/ts/tooling/dist",
|
|
59
|
+
"main": "packages/ts/tooling/src/index.ts",
|
|
60
|
+
"tsConfig": "packages/ts/tooling/tsconfig.lib.json",
|
|
61
|
+
"skipTypeCheck": true,
|
|
62
|
+
"stripLeadingPaths": true
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"dependencies": {
|
|
68
|
+
"@swc/helpers": "0.5.23"
|
|
69
|
+
},
|
|
70
|
+
"peerDependencies": {
|
|
71
|
+
"@opentelemetry/api": ">=1.9 <2"
|
|
72
|
+
},
|
|
73
|
+
"devDependencies": {
|
|
74
|
+
"@opentelemetry/api": "1.9.1"
|
|
75
|
+
}
|
|
76
|
+
}
|