UNPKG

4.67 kBJavaScriptView Raw
1import { inspect } from "util";
2import { AppError } from "./error.js";
3import { isNil } from "./lodash.js";
4
5/**
6 * @typedef {import("../types/advanced-types.js").Logger} Logger
7 */
8
9/**
10 * @typedef {import("../types/advanced-types").InsightEventCall} InsightEventCall
11 */
12
13/**
14 * @typedef {import("../types/advanced-types").InsightEvent} InsightEvent
15 */
16
17/**
18 *
19 * @param {Logger} logger
20 * @param {AbortSignal|undefined} [signal]
21 * @returns {InsightEventConstructor}
22 */
23function InsightEventConstructor(logger, signal) {
24 if (!(this instanceof InsightEventConstructor)) {
25 return new InsightEventConstructor(logger, signal);
26 }
27
28 const _this = this;
29
30 /** @type {Logger} */
31 this.log = logger;
32 /** @type {AbortSignal|undefined} */
33 this.signal = signal;
34 /** @type {InsightEventConstructor|undefined} */
35 this.parent = undefined;
36 /** @type {string|undefined} */
37 this.name = undefined;
38 /** @type {InsightEventCall[]} */
39 this.callStack = [];
40
41 this.calculateDuration = calculateDuration.bind(this);
42 this[inspect.custom] = print.bind(this);
43 this.toJSON = print.bind(this);
44
45 function calculateDuration() {
46 // @ts-ignore
47 if (_this.callStack[0]?.type !== "start") {
48 return;
49 }
50
51 const lastIdx = _this.callStack.length - 1;
52 // @ts-ignore
53 const lastType = _this.callStack[lastIdx]?.type;
54
55 if (lastType === "stop" || lastType === "aborted") {
56 // @ts-ignore
57 _this.callStack[0].duration =
58 // @ts-ignore
59 _this.callStack[lastIdx].time - _this.callStack[0].time;
60 }
61 }
62
63 function print() {
64 return {
65 type: "event_callstack",
66 aborted: !!_this.signal?.aborted,
67 callStack: _this.callStack,
68 };
69 }
70
71 return this;
72}
73
74/**
75 * Create a new event from a logger
76 *
77 * @since 0.1.0
78 *
79 * @param {Logger} logger Logger should have a context, like the default `ctx.log`
80 * @param {AbortSignal|undefined} [signal]
81 * @returns {InsightEvent}
82 */
83export function newEvent(logger, signal) {
84 return new InsightEventConstructor(logger, signal);
85}
86
87/**
88 * Create a 'child' event, reuses the logger, adds callstack to the passed event
89 *
90 * @since 0.1.0
91 *
92 * @param {InsightEvent} event
93 * @returns {InsightEvent}
94 */
95export function newEventFromEvent(event) {
96 if (event.signal?.aborted) {
97 event.callStack.push({
98 type: "aborted",
99 name: event.name,
100 time: Date.now(),
101 });
102 // @ts-ignore
103 event.calculateDuration();
104 throw AppError.serverError({
105 message: "Operation aborted",
106 // @ts-ignore
107 event: getEventRoot(event).toJSON(),
108 });
109 }
110
111 const callStack = [];
112 event.callStack.push(callStack);
113
114 const newEvent = new InsightEventConstructor(event.log, event.signal);
115 newEvent.callStack = callStack;
116 // @ts-ignore
117 newEvent.root = event;
118
119 return newEvent;
120}
121
122/**
123 * Track event start times
124 *
125 * @since 0.1.0
126 *
127 * @param {InsightEvent} event
128 * @param {string} name
129 * @returns {void}
130 */
131export function eventStart(event, name) {
132 event.name = name;
133
134 if (event.signal?.aborted) {
135 event.callStack.push({
136 type: "aborted",
137 name: event.name,
138 time: Date.now(),
139 });
140 throw AppError.serverError({
141 message: "Operation aborted",
142 // @ts-ignore
143 event: getEventRoot(event).toJSON(),
144 });
145 }
146
147 event.callStack.push({
148 type: "start",
149 name,
150 time: Date.now(),
151 });
152}
153
154/**
155 * Rename an event, and all callStack items
156 *
157 * @since 0.1.0
158 *
159 * @param {InsightEvent} event
160 * @param {string} name
161 * @returns {void}
162 */
163export function eventRename(event, name) {
164 event.name = name;
165
166 for (const item of event.callStack) {
167 // @ts-ignore
168 if (typeof item.name === "string") {
169 // @ts-ignore
170 item.name = name;
171 }
172 }
173
174 if (event.signal?.aborted) {
175 event.callStack.push({
176 type: "aborted",
177 name: event.name,
178 time: Date.now(),
179 });
180 // @ts-ignore
181 event.calculateDuration();
182 throw AppError.serverError({
183 message: "Operation aborted",
184 // @ts-ignore
185 event: getEventRoot(event).toJSON(),
186 });
187 }
188}
189
190/**
191 * Track event end times and log if necessary
192 *
193 * @since 0.1.0
194 *
195 * @param {InsightEvent} event
196 * @returns {void}
197 */
198export function eventStop(event) {
199 event.callStack.push({
200 type: "stop",
201 name: event.name,
202 time: Date.now(),
203 });
204
205 // @ts-ignore
206 event.calculateDuration();
207
208 // @ts-ignore
209 if (isNil(event.root)) {
210 event.log.info(event);
211 }
212}
213
214/**
215 * Get the root event from the provided event
216 *
217 * @param {InsightEvent} event
218 * @returns {InsightEvent}
219 */
220function getEventRoot(event) {
221 return isNil(event.parent) ? event : getEventRoot(event.parent);
222}