summaryrefslogtreecommitdiff
path: root/u/trace
diff options
context:
space:
mode:
Diffstat (limited to 'u/trace')
-rw-r--r--u/trace/itrace.ts29
-rw-r--r--u/trace/logger.ts87
-rw-r--r--u/trace/metrics.ts133
-rw-r--r--u/trace/mod.ts2
-rw-r--r--u/trace/trace.ts94
-rw-r--r--u/trace/util.ts48
6 files changed, 316 insertions, 77 deletions
diff --git a/u/trace/itrace.ts b/u/trace/itrace.ts
index b9b750d..620fff0 100644
--- a/u/trace/itrace.ts
+++ b/u/trace/itrace.ts
@@ -1,4 +1,4 @@
-import type { Mapper, SideEffect } from "@emprespresso/pengueno";
+import type { Mapper, SideEffect, Supplier } from "@emprespresso/pengueno";
// the "thing" every Trace writer must "trace()"
type BaseTraceWith = string;
@@ -19,15 +19,18 @@ export type ITraceableMapper<
) => U;
export interface ITraceable<T, Trace = BaseTraceWith> {
- readonly item: T;
readonly trace: ITrace<Trace>;
-
+ get: Supplier<T>;
move<U>(u: U): ITraceable<U, Trace>;
map: <U>(
mapper: ITraceableMapper<T, U, Trace>,
) => ITraceable<U, Trace>;
bimap: <U>(
- mapper: ITraceableMapper<T, ITraceableTuple<U, Trace>, Trace>,
+ mapper: ITraceableMapper<
+ T,
+ ITraceableTuple<U, Array<Trace> | Trace>,
+ Trace
+ >,
) => ITraceable<U, Trace>;
peek: (peek: ITraceableMapper<T, void, Trace>) => ITraceable<T, Trace>;
flatMap: <U>(
@@ -40,8 +43,8 @@ export interface ITraceable<T, Trace = BaseTraceWith> {
export class TraceableImpl<T, TraceWith> implements ITraceable<T, TraceWith> {
protected constructor(
- readonly item: T,
- readonly trace: ITrace<TraceWith>,
+ private readonly item: T,
+ public readonly trace: ITrace<TraceWith>,
) {}
public map<U>(
@@ -69,7 +72,7 @@ export class TraceableImpl<T, TraceWith> implements ITraceable<T, TraceWith> {
>,
): ITraceable<Promise<U>, TraceWith> {
return new TraceableImpl(
- mapper(this).then(({ item }) => item),
+ mapper(this).then((t) => t.get()),
this.trace,
);
}
@@ -86,11 +89,19 @@ export class TraceableImpl<T, TraceWith> implements ITraceable<T, TraceWith> {
public bimap<U>(
mapper: ITraceableMapper<
T,
- ITraceableTuple<U, TraceWith>,
+ ITraceableTuple<U, Array<TraceWith> | TraceWith>,
TraceWith
>,
) {
const [item, trace] = mapper(this);
- return new TraceableImpl(item, this.trace.addTrace(trace));
+ const traces = Array.isArray(trace) ? trace : [trace];
+ return new TraceableImpl(
+ item,
+ traces.reduce((trace, _trace) => trace.addTrace(_trace), this.trace),
+ );
+ }
+
+ public get() {
+ return this.item;
}
}
diff --git a/u/trace/logger.ts b/u/trace/logger.ts
index 4f3c856..a5739c8 100644
--- a/u/trace/logger.ts
+++ b/u/trace/logger.ts
@@ -1,6 +1,7 @@
import {
isDebug,
type ITrace,
+ type ITraceWith,
type SideEffect,
type Supplier,
} from "@emprespresso/pengueno";
@@ -53,47 +54,55 @@ export const logWithLevel = (
}
};
+export type LogTraceSupplier = ITraceWith<Supplier<string>>;
+
+const defaultTrace = () => `[${new Date().toISOString()}]`;
export const LoggerImpl = console;
+export class LogTrace implements ITrace<LogTraceSupplier> {
+ constructor(
+ private readonly logger: ILogger = LoggerImpl,
+ private readonly traces: Array<LogTraceSupplier> = [defaultTrace],
+ private readonly allowedLevels: Supplier<Array<LogLevel>> =
+ defaultAllowedLevels,
+ private readonly defaultLevel: LogLevel = LogLevel.INFO,
+ ) {
+ }
-export type LogTraceSupplier = string | Supplier<string>;
+ public addTrace(trace: LogTraceSupplier): ITrace<LogTraceSupplier> {
+ return new LogTrace(
+ this.logger,
+ this.traces.concat(trace),
+ this.allowedLevels,
+ this.defaultLevel,
+ );
+ }
-const foldTraces = (traces: Array<LogTraceSupplier>) => {
- const { line, level } = traces.reduce(
- (acc: { line: string; level: number }, t) => {
- const val = typeof t === "function" ? t() : t;
- if (isLogLevel(val)) {
- return {
- ...acc,
- level: Math.max(logLevelOrder.indexOf(val), acc.level),
- };
- }
- const prefix = [
- acc.line,
- val,
- ].join(" ");
- return { ...acc, prefix };
- },
- { line: "", level: -1 },
- );
- return { line, level: logLevelOrder[level] ?? LogLevel.UNKNOWN };
-};
+ public trace(trace: LogTraceSupplier) {
+ const { line, level: _level } = this.foldTraces(this.traces.concat(trace));
+ if (!this.allowedLevels().includes(_level)) return;
-const defaultTrace = () => `[${new Date().toISOString()}]`;
-export const LogTrace = (
- logger: ILogger,
- traces: Array<LogTraceSupplier> = [defaultTrace],
- allowedLevels: Supplier<Array<LogLevel>> = defaultAllowedLevels,
- defaultLevel: LogLevel = LogLevel.INFO,
-): ITrace<LogTraceSupplier> => {
- return {
- addTrace: (trace: LogTraceSupplier) =>
- LogTrace(logger, traces.concat(trace), allowedLevels, defaultLevel),
- trace: (trace: LogTraceSupplier) => {
- const { line, level: _level } = foldTraces(traces.concat(trace));
- if (!allowedLevels().includes(_level)) return;
+ const level = _level === LogLevel.UNKNOWN ? this.defaultLevel : _level;
+ logWithLevel(this.logger, level)(`[${level}]${line}`);
+ }
- const level = _level === LogLevel.UNKNOWN ? defaultLevel : _level;
- logWithLevel(logger, level)(`[${level}]${line}`);
- },
- };
-};
+ private foldTraces(traces: Array<LogTraceSupplier>) {
+ const { line, level } = traces.reduce(
+ (acc: { line: string; level: number }, t) => {
+ const val = typeof t === "function" ? t() : t;
+ if (isLogLevel(val)) {
+ return {
+ ...acc,
+ level: Math.max(logLevelOrder.indexOf(val), acc.level),
+ };
+ }
+ const prefix = [
+ acc.line,
+ val,
+ ].join(" ");
+ return { ...acc, prefix };
+ },
+ { line: "", level: -1 },
+ );
+ return { line, level: logLevelOrder[level] ?? LogLevel.UNKNOWN };
+ }
+}
diff --git a/u/trace/metrics.ts b/u/trace/metrics.ts
new file mode 100644
index 0000000..a26ee5d
--- /dev/null
+++ b/u/trace/metrics.ts
@@ -0,0 +1,133 @@
+import {
+ isObject,
+ type ITrace,
+ type ITraceWith,
+ type Mapper,
+ type SideEffect,
+} from "@emprespresso/pengueno";
+
+export enum Unit {
+ COUNT,
+ MILLISECONDS,
+}
+
+export interface IMetric {
+ readonly count: IEmittableMetric;
+ readonly time: IEmittableMetric;
+ readonly failure: IMetric;
+ readonly success: IMetric;
+ readonly _isIMetric: true;
+}
+export const isIMetric = (t: unknown): t is IMetric =>
+ isObject(t) && "_isIMetric" in t;
+
+export interface IEmittableMetric {
+ readonly name: string;
+ readonly unit: Unit;
+ withValue: Mapper<number, MetricValue>;
+}
+
+export class EmittableMetric implements IEmittableMetric {
+ constructor(public readonly name: string, public readonly unit: Unit) {
+ }
+
+ public withValue(value: number): MetricValue {
+ return {
+ name: this.name,
+ unit: this.unit,
+ _isMetricValue: true as true,
+ emissionTimestamp: Date.now(),
+ value,
+ };
+ }
+}
+
+export class Metric implements IMetric {
+ constructor(
+ public readonly count: IEmittableMetric,
+ public readonly time: IEmittableMetric,
+ public readonly failure: Metric,
+ public readonly success: Metric,
+ public readonly _isIMetric: true = true,
+ ) {}
+
+ static fromName(name: string): Metric {
+ return new Metric(
+ new EmittableMetric(`${name}.count`, Unit.COUNT),
+ new EmittableMetric(`${name}.elapsed`, Unit.MILLISECONDS),
+ Metric.fromName(`${name}.failure`),
+ Metric.fromName(`${name}.success`),
+ );
+ }
+}
+
+export interface MetricValue {
+ readonly name: string;
+ readonly unit: Unit;
+ readonly value: number;
+ readonly emissionTimestamp: number;
+ readonly _isMetricValue: true;
+}
+export const isMetricValue = (t: unknown): t is MetricValue =>
+ isObject(t) && "_isMetricValue" in t;
+
+export const isMetricsTraceSupplier = (t: unknown): t is MetricsTraceSupplier =>
+ isMetricValue(t) || isIMetric(t);
+
+export type MetricsTraceSupplier = ITraceWith<IMetric | MetricValue>;
+type MetricTracingTuple = [IMetric, Date];
+export class MetricsTrace implements ITrace<MetricsTraceSupplier> {
+ constructor(
+ private readonly metricConsumer: SideEffect<Array<MetricValue>>,
+ private readonly tracing: Array<MetricTracingTuple> = [],
+ private readonly flushed: Set<IMetric> = new Set(),
+ ) {}
+
+ public addTrace(trace: MetricsTraceSupplier) {
+ if (isMetricValue(trace) || typeof trace === "string") return this;
+ return new MetricsTrace(this.metricConsumer)._nowTracing(trace);
+ }
+
+ public trace(metric: MetricsTraceSupplier) {
+ if (typeof metric === "string") return this;
+ if (isMetricValue(metric)) {
+ this.metricConsumer([metric]);
+ return this;
+ }
+
+ const foundMetricValues = this.tracing.flatMap((
+ [tracing, startedTracing],
+ ) =>
+ [tracing, tracing.success, tracing.failure]
+ .filter((_tracing) => metric === _tracing)
+ .flatMap((metric) => [
+ this.addMetric(metric, startedTracing),
+ this.addMetric(tracing, startedTracing),
+ ])
+ ).flatMap((values) => values);
+
+ if (foundMetricValues.length === 0) {
+ return this._nowTracing(metric);
+ }
+
+ this.metricConsumer(foundMetricValues);
+ return this;
+ }
+
+ private addMetric(metric: IMetric, startedTracing: Date): Array<MetricValue> {
+ if (this.flushed.has(metric)) {
+ return [];
+ }
+
+ this.flushed.add(metric);
+ return [
+ metric.count.withValue(1.0),
+ metric.time.withValue(Date.now() - startedTracing.getTime()),
+ ];
+ }
+
+ private _nowTracing(metric: IMetric): MetricsTrace {
+ this.tracing.push([metric, new Date()]);
+ return this;
+ }
+}
diff --git a/u/trace/mod.ts b/u/trace/mod.ts
index 9c42858..0f9b61b 100644
--- a/u/trace/mod.ts
+++ b/u/trace/mod.ts
@@ -1,3 +1,5 @@
export * from "./itrace.ts";
+export * from "./util.ts";
export * from "./logger.ts";
+export * from "./metrics.ts";
export * from "./trace.ts";
diff --git a/u/trace/trace.ts b/u/trace/trace.ts
index 1d3d2d8..72d4eef 100644
--- a/u/trace/trace.ts
+++ b/u/trace/trace.ts
@@ -1,46 +1,82 @@
import {
- type Callable,
- type ITraceableMapper,
- type ITraceableTuple,
- LoggerImpl,
+ isMetricsTraceSupplier,
+ type ITrace,
+ type ITraceWith,
LogTrace,
type LogTraceSupplier,
+ MetricsTrace,
+ type MetricsTraceSupplier,
+ type MetricValue,
TraceableImpl,
} from "@emprespresso/pengueno";
export class LogTraceable<T> extends TraceableImpl<T, LogTraceSupplier> {
+ public static LogTrace = new LogTrace();
static from<T>(t: T) {
- return new LogTraceable(t, LogTrace(LoggerImpl));
+ return new LogTraceable(t, LogTraceable.LogTrace);
}
}
-export class TraceUtil {
- static withFunctionTrace<F extends Callable, T, Trace>(
- f: F,
- ): ITraceableMapper<
- T,
- ITraceableTuple<T, Trace>,
- Trace
- > {
- return (t) => [t.item, `[${f.name}]`];
+const getEmbeddedMetricConsumer =
+ (logTrace: LogTrace) => (metrics: Array<MetricValue>) =>
+ logTrace.addTrace("<metrics>").trace(
+ JSON.stringify(metrics, null, 2) + "</metrics>",
+ );
+export class EmbeddedMetricsTraceable<T>
+ extends TraceableImpl<T, MetricsTraceSupplier> {
+ public static MetricsTrace = new MetricsTrace(
+ getEmbeddedMetricConsumer(LogTraceable.LogTrace),
+ );
+
+ static from<T>(t: T) {
+ return new EmbeddedMetricsTraceable(
+ t,
+ EmbeddedMetricsTraceable.MetricsTrace,
+ );
}
+}
- static withClassTrace<C extends object, T, Trace>(
- c: C,
- ): ITraceableMapper<
- T,
- ITraceableTuple<T, Trace>,
- Trace
- > {
- return (t) => [t.item, `[${c.constructor.name}]`];
+export type LogMetricTraceSupplier = ITraceWith<
+ LogTraceSupplier | MetricsTraceSupplier
+>;
+export class LogMetricTrace implements ITrace<LogMetricTraceSupplier> {
+ constructor(
+ private readonly logTrace: ITrace<LogTraceSupplier>,
+ private readonly metricsTrace: ITrace<MetricsTraceSupplier>,
+ ) {}
+
+ public addTrace(
+ trace: LogTraceSupplier | MetricsTraceSupplier,
+ ): LogMetricTrace {
+ if (isMetricsTraceSupplier(trace)) {
+ this.metricsTrace.addTrace(trace);
+ return this;
+ }
+ this.logTrace.addTrace(trace);
+ return this;
}
- static promiseify<T, U, Trace>(
- mapper: ITraceableMapper<T, U, Trace>,
- ): ITraceableMapper<Promise<T>, Promise<U>, Trace> {
- return (traceablePromise) =>
- traceablePromise.flatMapAsync(async (t) =>
- t.move(await t.item).map(mapper)
- ).item;
+ public trace(trace: LogTraceSupplier | MetricsTraceSupplier) {
+ if (isMetricsTraceSupplier(trace)) {
+ this.metricsTrace.trace(trace);
+ return this;
+ }
+ this.logTrace.trace(trace);
+ return this;
+ }
+}
+
+export class LogMetricTraceable<T>
+ extends TraceableImpl<T, MetricsTraceSupplier | LogTraceSupplier> {
+ public static LogMetricTrace = new LogMetricTrace(
+ LogTraceable.LogTrace,
+ EmbeddedMetricsTraceable.MetricsTrace,
+ );
+
+ static from<T>(t: T) {
+ return new LogMetricTraceable(
+ t,
+ LogMetricTraceable.LogMetricTrace,
+ );
}
}
diff --git a/u/trace/util.ts b/u/trace/util.ts
new file mode 100644
index 0000000..dd8fb0d
--- /dev/null
+++ b/u/trace/util.ts
@@ -0,0 +1,48 @@
+import type {
+ Callable,
+ IMetric,
+ ITraceableMapper,
+ ITraceableTuple,
+ MetricsTraceSupplier,
+} from "@emprespresso/pengueno";
+
+export class TraceUtil {
+ static withMetricTrace<T, Trace extends MetricsTraceSupplier>(
+ metric: IMetric,
+ ): ITraceableMapper<
+ T,
+ ITraceableTuple<T, Trace | Array<Trace>>,
+ Trace
+ > {
+ return (t) => [t.get(), metric as Trace];
+ }
+
+ static withFunctionTrace<F extends Callable, T, Trace>(
+ f: F,
+ ): ITraceableMapper<
+ T,
+ ITraceableTuple<T, Trace | Array<Trace>>,
+ Trace
+ > {
+ return (t) => [t.get(), `[${f.name}]`];
+ }
+
+ static withClassTrace<C extends object, T, Trace>(
+ c: C,
+ ): ITraceableMapper<
+ T,
+ ITraceableTuple<T, Trace | Array<Trace>>,
+ Trace
+ > {
+ return (t) => [t.get(), `[${c.constructor.name}]`];
+ }
+
+ static promiseify<T, U, Trace>(
+ mapper: ITraceableMapper<T, U, Trace>,
+ ): ITraceableMapper<Promise<T>, Promise<U>, Trace> {
+ return (traceablePromise) =>
+ traceablePromise.flatMapAsync(async (t) =>
+ t.move(await t.get()).map(mapper)
+ ).get();
+ }
+}