fix(core): Remove threads pkg, rewrite log writer worker (#5134)

This commit is contained in:
Michael Auerswald 2023-01-13 15:39:25 +01:00 committed by GitHub
parent b7faf4a0df
commit e845eb33f9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 287 additions and 286 deletions

View file

@ -188,7 +188,6 @@
"sse-channel": "^4.0.0", "sse-channel": "^4.0.0",
"swagger-ui-express": "^4.3.0", "swagger-ui-express": "^4.3.0",
"syslog-client": "^1.1.1", "syslog-client": "^1.1.1",
"threads": "^1.7.0",
"tslib": "1.14.1", "tslib": "1.14.1",
"typeorm": "0.2.45", "typeorm": "0.2.45",
"uuid": "^8.3.2", "uuid": "^8.3.2",

View file

@ -1065,12 +1065,6 @@ export const schema = {
env: 'N8N_EVENTBUS_CHECKUNSENTINTERVAL', env: 'N8N_EVENTBUS_CHECKUNSENTINTERVAL',
}, },
logWriter: { logWriter: {
syncFileAccess: {
doc: 'Whether all file access happens synchronously within the thread.',
format: Boolean,
default: false,
env: 'N8N_EVENTBUS_LOGWRITER_SYNCFILEACCESS',
},
keepLogCount: { keepLogCount: {
doc: 'How many event log files to keep.', doc: 'How many event log files to keep.',
format: Number, format: Number,

View file

@ -1,5 +1,4 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import type { EventMessageTypeNames, JsonObject } from 'n8n-workflow'; import type { EventMessageTypeNames, JsonObject } from 'n8n-workflow';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';

View file

@ -44,7 +44,6 @@ export interface EventMessageAuditOptions extends AbstractEventMessageOptions {
} }
export class EventMessageAudit extends AbstractEventMessage { export class EventMessageAudit extends AbstractEventMessage {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
readonly __type = EventMessageTypeNames.audit; readonly __type = EventMessageTypeNames.audit;
eventName: EventNamesAuditType; eventName: EventNamesAuditType;

View file

@ -66,7 +66,6 @@ class MessageEventBus extends EventEmitter {
LoggerProxy.debug('Initializing event bus...'); LoggerProxy.debug('Initializing event bus...');
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
const savedEventDestinations = await Db.collections.EventDestinations.find({}); const savedEventDestinations = await Db.collections.EventDestinations.find({});
if (savedEventDestinations.length > 0) { if (savedEventDestinations.length > 0) {
for (const destinationData of savedEventDestinations) { for (const destinationData of savedEventDestinations) {
@ -91,11 +90,9 @@ class MessageEventBus extends EventEmitter {
LoggerProxy.debug('Checking for unsent event messages'); LoggerProxy.debug('Checking for unsent event messages');
const unsentAndUnfinished = await this.getUnsentAndUnfinishedExecutions(); const unsentAndUnfinished = await this.getUnsentAndUnfinishedExecutions();
LoggerProxy.debug( LoggerProxy.debug(
`Start logging into ${ `Start logging into ${this.logWriter?.getLogFileName() ?? 'unknown filename'} `,
(await this.logWriter?.getThread()?.getLogFileName()) ?? 'unknown filename'
} `,
); );
await this.logWriter?.startLogging(); this.logWriter?.startLogging();
await this.send(unsentAndUnfinished.unsentMessages); await this.send(unsentAndUnfinished.unsentMessages);
if (unsentAndUnfinished.unfinishedExecutions.size > 0) { if (unsentAndUnfinished.unfinishedExecutions.size > 0) {
@ -130,10 +127,8 @@ class MessageEventBus extends EventEmitter {
if (id && Object.keys(this.destinations).includes(id)) { if (id && Object.keys(this.destinations).includes(id)) {
result = [this.destinations[id].serialize()]; result = [this.destinations[id].serialize()];
} else { } else {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
result = Object.keys(this.destinations).map((e) => this.destinations[e].serialize()); result = Object.keys(this.destinations).map((e) => this.destinations[e].serialize());
} }
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
return result.sort((a, b) => (a.__type ?? '').localeCompare(b.__type ?? '')); return result.sort((a, b) => (a.__type ?? '').localeCompare(b.__type ?? ''));
} }
@ -175,7 +170,11 @@ class MessageEventBus extends EventEmitter {
msgs = [msgs]; msgs = [msgs];
} }
for (const msg of msgs) { for (const msg of msgs) {
await this.logWriter?.putMessage(msg); this.logWriter?.putMessage(msg);
// if there are no set up destinations, immediately mark the event as sent
if (!this.shouldSendMsg(msg)) {
this.confirmSent(msg, { id: '0', name: 'eventBus' });
}
await this.emitMessage(msg); await this.emitMessage(msg);
} }
} }
@ -192,8 +191,8 @@ class MessageEventBus extends EventEmitter {
return false; return false;
} }
async confirmSent(msg: EventMessageTypes, source?: EventMessageConfirmSource) { confirmSent(msg: EventMessageTypes, source?: EventMessageConfirmSource) {
await this.logWriter?.confirmMessageSent(msg.id, source); this.logWriter?.confirmMessageSent(msg.id, source);
} }
private hasAnyDestinationSubscribedToEvent(msg: EventMessageTypes): boolean { private hasAnyDestinationSubscribedToEvent(msg: EventMessageTypes): boolean {
@ -210,22 +209,23 @@ class MessageEventBus extends EventEmitter {
// this is for internal use ONLY and not for use with custom destinations! // this is for internal use ONLY and not for use with custom destinations!
this.emit('message', msg); this.emit('message', msg);
LoggerProxy.debug(`Listeners: ${this.eventNames().join(',')}`); // LoggerProxy.debug(`Listeners: ${this.eventNames().join(',')}`);
// if there are no set up destinations, immediately mark the event as sent if (this.shouldSendMsg(msg)) {
if (
!isLogStreamingEnabled() ||
Object.keys(this.destinations).length === 0 ||
!this.hasAnyDestinationSubscribedToEvent(msg)
) {
await this.confirmSent(msg, { id: '0', name: 'eventBus' });
} else {
for (const destinationName of Object.keys(this.destinations)) { for (const destinationName of Object.keys(this.destinations)) {
this.emit(this.destinations[destinationName].getId(), msg); this.emit(this.destinations[destinationName].getId(), msg);
} }
} }
} }
shouldSendMsg(msg: EventMessageTypes): boolean {
return (
isLogStreamingEnabled() &&
Object.keys(this.destinations).length > 0 &&
this.hasAnyDestinationSubscribedToEvent(msg)
);
}
async getEventsAll(): Promise<EventMessageTypes[]> { async getEventsAll(): Promise<EventMessageTypes[]> {
const queryResult = await this.logWriter?.getMessagesAll(); const queryResult = await this.logWriter?.getMessagesAll();
const filtered = uniqby(queryResult, 'id'); const filtered = uniqby(queryResult, 'id');

View file

@ -1,5 +1,4 @@
/* eslint-disable import/no-cycle */ /* eslint-disable import/no-cycle */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { MessageEventBusDestinationTypeNames } from 'n8n-workflow'; import { MessageEventBusDestinationTypeNames } from 'n8n-workflow';
import type { EventDestinations } from '@/databases/entities/MessageEventBusDestinationEntity'; import type { EventDestinations } from '@/databases/entities/MessageEventBusDestinationEntity';
import type { MessageEventBusDestination } from './MessageEventBusDestination.ee'; import type { MessageEventBusDestination } from './MessageEventBusDestination.ee';
@ -10,7 +9,6 @@ import { MessageEventBusDestinationWebhook } from './MessageEventBusDestinationW
export function messageEventBusDestinationFromDb( export function messageEventBusDestinationFromDb(
dbData: EventDestinations, dbData: EventDestinations,
): MessageEventBusDestination | null { ): MessageEventBusDestination | null {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-assignment
const destinationData = dbData.destination; const destinationData = dbData.destination;
if ('__type' in destinationData) { if ('__type' in destinationData) {
switch (destinationData.__type) { switch (destinationData.__type) {

View file

@ -1,5 +1,3 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { import {
INodeCredentials, INodeCredentials,
@ -83,7 +81,6 @@ export abstract class MessageEventBusDestination implements MessageEventBusDesti
id: this.getId(), id: this.getId(),
destination: this.serialize(), destination: this.serialize(),
}; };
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
const dbResult: InsertResult = await Db.collections.EventDestinations.upsert(data, { const dbResult: InsertResult = await Db.collections.EventDestinations.upsert(data, {
skipUpdateIfNoValuesChanged: true, skipUpdateIfNoValuesChanged: true,
conflictPaths: ['id'], conflictPaths: ['id'],
@ -97,7 +94,6 @@ export abstract class MessageEventBusDestination implements MessageEventBusDesti
} }
static async deleteFromDb(id: string): Promise<DeleteResult> { static async deleteFromDb(id: string): Promise<DeleteResult> {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
const dbResult = await Db.collections.EventDestinations.delete({ id }); const dbResult = await Db.collections.EventDestinations.delete({ id });
return dbResult; return dbResult;
} }

View file

@ -36,7 +36,6 @@ export class MessageEventBusDestinationSentry
constructor(options: MessageEventBusDestinationSentryOptions) { constructor(options: MessageEventBusDestinationSentryOptions) {
super(options); super(options);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
this.label = options.label ?? 'Sentry DSN'; this.label = options.label ?? 'Sentry DSN';
this.__type = options.__type ?? MessageEventBusDestinationTypeNames.sentry; this.__type = options.__type ?? MessageEventBusDestinationTypeNames.sentry;
this.dsn = options.dsn; this.dsn = options.dsn;
@ -85,7 +84,7 @@ export class MessageEventBusDestinationSentry
); );
if (sentryResult) { if (sentryResult) {
await eventBus.confirmSent(msg, { id: this.id, name: this.label }); eventBus.confirmSent(msg, { id: this.id, name: this.label });
sendResult = true; sendResult = true;
} }
} catch (error) { } catch (error) {
@ -109,7 +108,6 @@ export class MessageEventBusDestinationSentry
): MessageEventBusDestinationSentry | null { ): MessageEventBusDestinationSentry | null {
if ( if (
'__type' in data && '__type' in data &&
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
data.__type === MessageEventBusDestinationTypeNames.sentry && data.__type === MessageEventBusDestinationTypeNames.sentry &&
isMessageEventBusDestinationSentryOptions(data) isMessageEventBusDestinationSentryOptions(data)
) { ) {

View file

@ -96,7 +96,7 @@ export class MessageEventBusDestinationSyslog
if (error) { if (error) {
console.log(error); console.log(error);
} else { } else {
await eventBus.confirmSent(msg, { id: this.id, name: this.label }); eventBus.confirmSent(msg, { id: this.id, name: this.label });
sendResult = true; sendResult = true;
} }
}, },
@ -112,7 +112,6 @@ export class MessageEventBusDestinationSyslog
serialize(): MessageEventBusDestinationSyslogOptions { serialize(): MessageEventBusDestinationSyslogOptions {
const abstractSerialized = super.serialize(); const abstractSerialized = super.serialize();
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return { return {
...abstractSerialized, ...abstractSerialized,
expectedStatusCode: this.expectedStatusCode, expectedStatusCode: this.expectedStatusCode,

View file

@ -192,8 +192,6 @@ export class MessageEventBusDestinationWebhook
} catch (_) { } catch (_) {
console.log('JSON parameter need to be an valid JSON'); console.log('JSON parameter need to be an valid JSON');
} }
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
this.axiosRequestOptions.params = jsonParse(this.jsonQuery); this.axiosRequestOptions.params = jsonParse(this.jsonQuery);
} }
} }
@ -212,8 +210,6 @@ export class MessageEventBusDestinationWebhook
} catch (_) { } catch (_) {
console.log('JSON parameter need to be an valid JSON'); console.log('JSON parameter need to be an valid JSON');
} }
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
this.axiosRequestOptions.headers = jsonParse(this.jsonHeaders); this.axiosRequestOptions.headers = jsonParse(this.jsonHeaders);
} }
} }
@ -222,7 +218,6 @@ export class MessageEventBusDestinationWebhook
if (this.axiosRequestOptions.headers === undefined) { if (this.axiosRequestOptions.headers === undefined) {
this.axiosRequestOptions.headers = {}; this.axiosRequestOptions.headers = {};
} }
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
this.axiosRequestOptions.headers['Content-Type'] = 'application/json'; this.axiosRequestOptions.headers['Content-Type'] = 'application/json';
} }
@ -336,10 +331,8 @@ export class MessageEventBusDestinationWebhook
password: httpBasicAuth.password as string, password: httpBasicAuth.password as string,
}; };
} else if (httpHeaderAuth) { } else if (httpHeaderAuth) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
this.axiosRequestOptions.headers[httpHeaderAuth.name as string] = httpHeaderAuth.value; this.axiosRequestOptions.headers[httpHeaderAuth.name as string] = httpHeaderAuth.value;
} else if (httpQueryAuth) { } else if (httpQueryAuth) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
this.axiosRequestOptions.params[httpQueryAuth.name as string] = httpQueryAuth.value; this.axiosRequestOptions.params[httpQueryAuth.name as string] = httpQueryAuth.value;
} else if (httpDigestAuth) { } else if (httpDigestAuth) {
this.axiosRequestOptions.auth = { this.axiosRequestOptions.auth = {
@ -353,13 +346,13 @@ export class MessageEventBusDestinationWebhook
if (requestResponse) { if (requestResponse) {
if (this.responseCodeMustMatch) { if (this.responseCodeMustMatch) {
if (requestResponse.status === this.expectedStatusCode) { if (requestResponse.status === this.expectedStatusCode) {
await eventBus.confirmSent(msg, { id: this.id, name: this.label }); eventBus.confirmSent(msg, { id: this.id, name: this.label });
sendResult = true; sendResult = true;
} else { } else {
sendResult = false; sendResult = false;
} }
} else { } else {
await eventBus.confirmSent(msg, { id: this.id, name: this.label }); eventBus.confirmSent(msg, { id: this.id, name: this.label });
sendResult = true; sendResult = true;
} }
} }

View file

@ -3,9 +3,8 @@
import { isEventMessageOptions } from '../EventMessageClasses/AbstractEventMessage'; import { isEventMessageOptions } from '../EventMessageClasses/AbstractEventMessage';
import { UserSettings } from 'n8n-core'; import { UserSettings } from 'n8n-core';
import path, { parse } from 'path'; import path, { parse } from 'path';
import { ModuleThread, spawn, Thread, Worker } from 'threads'; import { Worker } from 'worker_threads';
import { MessageEventBusLogWriterWorker } from './MessageEventBusLogWriterWorker'; import { createReadStream, existsSync, rmSync } from 'fs';
import { createReadStream, existsSync } from 'fs';
import readline from 'readline'; import readline from 'readline';
import { jsonParse, LoggerProxy } from 'n8n-workflow'; import { jsonParse, LoggerProxy } from 'n8n-workflow';
import remove from 'lodash.remove'; import remove from 'lodash.remove';
@ -19,15 +18,21 @@ import {
isEventMessageConfirm, isEventMessageConfirm,
} from '../EventMessageClasses/EventMessageConfirm'; } from '../EventMessageClasses/EventMessageConfirm';
import { once as eventOnce } from 'events'; import { once as eventOnce } from 'events';
import { inTest } from '../../constants';
interface MessageEventBusLogWriterOptions { interface MessageEventBusLogWriterConstructorOptions {
syncFileAccess?: boolean;
logBaseName?: string; logBaseName?: string;
logBasePath?: string; logBasePath?: string;
keepLogCount?: number; keepNumberOfFiles?: number;
maxFileSizeInKB?: number; maxFileSizeInKB?: number;
} }
export interface MessageEventBusLogWriterOptions {
logFullBasePath: string;
keepNumberOfFiles: number;
maxFileSizeInKB: number;
}
interface ReadMessagesFromLogFileResult { interface ReadMessagesFromLogFileResult {
loggedMessages: EventMessageTypes[]; loggedMessages: EventMessageTypes[];
sentMessages: EventMessageTypes[]; sentMessages: EventMessageTypes[];
@ -42,7 +47,11 @@ export class MessageEventBusLogWriter {
static options: Required<MessageEventBusLogWriterOptions>; static options: Required<MessageEventBusLogWriterOptions>;
private worker: ModuleThread<MessageEventBusLogWriterWorker> | null; private _worker: Worker | undefined;
public get worker(): Worker | undefined {
return this._worker;
}
/** /**
* Instantiates the Writer and the corresponding worker thread. * Instantiates the Writer and the corresponding worker thread.
@ -51,16 +60,17 @@ export class MessageEventBusLogWriter {
* **Note** that starting to log will archive existing logs, so handle unsent events first before calling startLogging() * **Note** that starting to log will archive existing logs, so handle unsent events first before calling startLogging()
*/ */
static async getInstance( static async getInstance(
options?: MessageEventBusLogWriterOptions, options?: MessageEventBusLogWriterConstructorOptions,
): Promise<MessageEventBusLogWriter> { ): Promise<MessageEventBusLogWriter> {
if (!MessageEventBusLogWriter.instance) { if (!MessageEventBusLogWriter.instance) {
MessageEventBusLogWriter.instance = new MessageEventBusLogWriter(); MessageEventBusLogWriter.instance = new MessageEventBusLogWriter();
MessageEventBusLogWriter.options = { MessageEventBusLogWriter.options = {
logBaseName: options?.logBaseName ?? config.getEnv('eventBus.logWriter.logBaseName'), logFullBasePath: path.join(
logBasePath: options?.logBasePath ?? UserSettings.getUserN8nFolderPath(), options?.logBasePath ?? UserSettings.getUserN8nFolderPath(),
syncFileAccess: options?.logBaseName ?? config.getEnv('eventBus.logWriter.logBaseName'),
options?.syncFileAccess ?? config.getEnv('eventBus.logWriter.syncFileAccess'), ),
keepLogCount: options?.keepLogCount ?? config.getEnv('eventBus.logWriter.keepLogCount'), keepNumberOfFiles:
options?.keepNumberOfFiles ?? config.getEnv('eventBus.logWriter.keepLogCount'),
maxFileSizeInKB: maxFileSizeInKB:
options?.maxFileSizeInKB ?? config.getEnv('eventBus.logWriter.maxFileSizeInKB'), options?.maxFileSizeInKB ?? config.getEnv('eventBus.logWriter.maxFileSizeInKB'),
}; };
@ -73,15 +83,19 @@ export class MessageEventBusLogWriter {
* First archives existing log files one history level upwards, * First archives existing log files one history level upwards,
* then starts logging events into a fresh event log * then starts logging events into a fresh event log
*/ */
async startLogging() { startLogging() {
await MessageEventBusLogWriter.instance.getThread()?.startLogging(); if (this.worker) {
this.worker.postMessage({ command: 'startLogging', data: {} });
}
} }
/** /**
* Pauses all logging. Events are still received by the worker, they just are not logged any more * Pauses all logging. Events are still received by the worker, they just are not logged any more
*/ */
async pauseLogging() { async pauseLogging() {
await MessageEventBusLogWriter.instance.getThread()?.pauseLogging(); if (this.worker) {
this.worker.postMessage({ command: 'pauseLogging', data: {} });
}
} }
private async startThread() { private async startThread() {
@ -89,26 +103,23 @@ export class MessageEventBusLogWriter {
await this.close(); await this.close();
} }
await MessageEventBusLogWriter.instance.spawnThread(); await MessageEventBusLogWriter.instance.spawnThread();
await MessageEventBusLogWriter.instance if (this.worker) {
.getThread() this.worker.postMessage({ command: 'initialize', data: MessageEventBusLogWriter.options });
?.initialize( }
path.join(
MessageEventBusLogWriter.options.logBasePath,
MessageEventBusLogWriter.options.logBaseName,
),
MessageEventBusLogWriter.options.syncFileAccess,
MessageEventBusLogWriter.options.keepLogCount,
MessageEventBusLogWriter.options.maxFileSizeInKB,
);
} }
private async spawnThread(): Promise<boolean> { private async spawnThread(): Promise<boolean> {
this.worker = await spawn<MessageEventBusLogWriterWorker>( const parsedName = parse(__filename);
new Worker(`${parse(__filename).name}Worker`), let workerFileName;
); if (inTest) {
workerFileName = './dist/eventbus/MessageEventBusWriter/MessageEventBusLogWriterWorker.js';
} else {
workerFileName = path.join(parsedName.dir, `${parsedName.name}Worker${parsedName.ext}`);
}
this._worker = new Worker(workerFileName);
if (this.worker) { if (this.worker) {
Thread.errors(this.worker).subscribe(async (error) => { this.worker.on('messageerror', async (error) => {
LoggerProxy.error('Event Bus Log Writer thread error', error); LoggerProxy.error('Event Bus Log Writer thread error, attempting to restart...', error);
await MessageEventBusLogWriter.instance.startThread(); await MessageEventBusLogWriter.instance.startThread();
}); });
return true; return true;
@ -116,29 +127,25 @@ export class MessageEventBusLogWriter {
return false; return false;
} }
getThread(): ModuleThread<MessageEventBusLogWriterWorker> | undefined {
if (this.worker) {
return this.worker;
}
return;
}
async close(): Promise<void> { async close(): Promise<void> {
if (this.worker) { if (this.worker) {
await Thread.terminate(this.worker); await this.worker.terminate();
this.worker = null; this._worker = undefined;
} }
} }
async putMessage(msg: EventMessageTypes): Promise<void> { putMessage(msg: EventMessageTypes): void {
if (this.worker) { if (this.worker) {
await this.worker.appendMessageToLog(msg.serialize()); this.worker.postMessage({ command: 'appendMessageToLog', data: msg.serialize() });
} }
} }
async confirmMessageSent(msgId: string, source?: EventMessageConfirmSource): Promise<void> { confirmMessageSent(msgId: string, source?: EventMessageConfirmSource): void {
if (this.worker) { if (this.worker) {
await this.worker.confirmMessageSent(new EventMessageConfirm(msgId, source).serialize()); this.worker.postMessage({
command: 'confirmMessageSent',
data: new EventMessageConfirm(msgId, source).serialize(),
});
} }
} }
@ -155,7 +162,7 @@ export class MessageEventBusLogWriter {
? Math.min(config.get('eventBus.logWriter.keepLogCount') as number, logHistory) ? Math.min(config.get('eventBus.logWriter.keepLogCount') as number, logHistory)
: (config.get('eventBus.logWriter.keepLogCount') as number); : (config.get('eventBus.logWriter.keepLogCount') as number);
for (let i = logCount; i >= 0; i--) { for (let i = logCount; i >= 0; i--) {
const logFileName = await MessageEventBusLogWriter.instance.getThread()?.getLogFileName(i); const logFileName = this.getLogFileName(i);
if (logFileName) { if (logFileName) {
await this.readLoggedMessagesFromFile(results, mode, logFileName); await this.readLoggedMessagesFromFile(results, mode, logFileName);
} }
@ -212,6 +219,22 @@ export class MessageEventBusLogWriter {
return results; return results;
} }
getLogFileName(counter?: number): string {
if (counter) {
return `${MessageEventBusLogWriter.options.logFullBasePath}-${counter}.log`;
} else {
return `${MessageEventBusLogWriter.options.logFullBasePath}.log`;
}
}
cleanAllLogs() {
for (let i = 0; i <= MessageEventBusLogWriter.options.keepNumberOfFiles; i++) {
if (existsSync(this.getLogFileName(i))) {
rmSync(this.getLogFileName(i));
}
}
}
async getMessagesByExecutionId( async getMessagesByExecutionId(
executionId: string, executionId: string,
logHistory?: number, logHistory?: number,
@ -221,7 +244,7 @@ export class MessageEventBusLogWriter {
? Math.min(config.get('eventBus.logWriter.keepLogCount') as number, logHistory) ? Math.min(config.get('eventBus.logWriter.keepLogCount') as number, logHistory)
: (config.get('eventBus.logWriter.keepLogCount') as number); : (config.get('eventBus.logWriter.keepLogCount') as number);
for (let i = 0; i < logCount; i++) { for (let i = 0; i < logCount; i++) {
const logFileName = await MessageEventBusLogWriter.instance.getThread()?.getLogFileName(i); const logFileName = this.getLogFileName(i);
if (logFileName) { if (logFileName) {
result.push(...(await this.readFromFileByExecutionId(executionId, logFileName))); result.push(...(await this.readFromFileByExecutionId(executionId, logFileName)));
} }

View file

@ -1,17 +1,11 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import { appendFileSync, existsSync, rmSync, renameSync, openSync, closeSync } from 'fs'; import { appendFileSync, existsSync, rmSync, renameSync, openSync, closeSync } from 'fs';
import { appendFile, stat } from 'fs/promises'; import { stat } from 'fs/promises';
import { expose, isWorkerRuntime } from 'threads/worker'; import { isMainThread, parentPort } from 'worker_threads';
import type { MessageEventBusLogWriterOptions } from './MessageEventBusLogWriter';
// -----------------------------------------
// * This part runs in the Worker Thread ! *
// -----------------------------------------
// all references to and imports from classes have been remove to keep memory usage low
let logFileBasePath = ''; let logFileBasePath = '';
let loggingPaused = true; let loggingPaused = true;
let syncFileAccess = false;
let keepFiles = 10; let keepFiles = 10;
let fileStatTimer: NodeJS.Timer; let fileStatTimer: NodeJS.Timer;
let maxLogFileSizeInKB = 102400; let maxLogFileSizeInKB = 102400;
@ -20,12 +14,8 @@ function setLogFileBasePath(basePath: string) {
logFileBasePath = basePath; logFileBasePath = basePath;
} }
function setUseSyncFileAccess(useSync: boolean) { function setMaxLogFileSizeInKB(maxFileSizeInKB: number) {
syncFileAccess = useSync; maxLogFileSizeInKB = maxFileSizeInKB;
}
function setMaxLogFileSizeInKB(maxSizeInKB: number) {
maxLogFileSizeInKB = maxSizeInKB;
} }
function setKeepFiles(keepNumberOfFiles: number) { function setKeepFiles(keepNumberOfFiles: number) {
@ -81,65 +71,53 @@ function appendMessageSync(msg: any) {
appendFileSync(buildLogFileNameWithCounter(), JSON.stringify(msg) + '\n'); appendFileSync(buildLogFileNameWithCounter(), JSON.stringify(msg) + '\n');
} }
async function appendMessage(msg: any) { if (!isMainThread) {
if (loggingPaused) { // -----------------------------------------
return; // * This part runs in the Worker Thread ! *
} // -----------------------------------------
await appendFile(buildLogFileNameWithCounter(), JSON.stringify(msg) + '\n'); parentPort?.on('message', async (msg: { command: string; data: any }) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const { command, data } = msg;
try {
switch (command) {
case 'appendMessageToLog':
case 'confirmMessageSent':
appendMessageSync(data);
parentPort?.postMessage({ command, data: true });
break;
case 'pauseLogging':
loggingPaused = true;
clearInterval(fileStatTimer);
break;
case 'initialize':
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const settings: MessageEventBusLogWriterOptions = {
logFullBasePath: (data as MessageEventBusLogWriterOptions).logFullBasePath ?? '',
keepNumberOfFiles: (data as MessageEventBusLogWriterOptions).keepNumberOfFiles ?? 10,
maxFileSizeInKB: (data as MessageEventBusLogWriterOptions).maxFileSizeInKB ?? 102400,
};
setLogFileBasePath(settings.logFullBasePath);
setKeepFiles(settings.keepNumberOfFiles);
setMaxLogFileSizeInKB(settings.maxFileSizeInKB);
break;
case 'startLogging':
if (logFileBasePath) {
renameAndCreateLogs();
loggingPaused = false;
fileStatTimer = setInterval(async () => {
await checkFileSize(buildLogFileNameWithCounter());
}, 5000);
}
break;
case 'cleanLogs':
cleanAllLogs();
parentPort?.postMessage('cleanedAllLogs');
break;
default:
break;
}
} catch (error) {
parentPort?.postMessage(error);
}
});
} }
const messageEventBusLogWriterWorker = {
async appendMessageToLog(msg: any) {
if (syncFileAccess) {
appendMessageSync(msg);
} else {
await appendMessage(msg);
}
},
async confirmMessageSent(confirm: unknown) {
if (syncFileAccess) {
appendMessageSync(confirm);
} else {
await appendMessage(confirm);
}
},
pauseLogging() {
loggingPaused = true;
clearInterval(fileStatTimer);
},
initialize(
basePath: string,
useSyncFileAccess = false,
keepNumberOfFiles = 10,
maxSizeInKB = 102400,
) {
setLogFileBasePath(basePath);
setUseSyncFileAccess(useSyncFileAccess);
setKeepFiles(keepNumberOfFiles);
setMaxLogFileSizeInKB(maxSizeInKB);
},
startLogging() {
if (logFileBasePath) {
renameAndCreateLogs();
loggingPaused = false;
fileStatTimer = setInterval(async () => {
await checkFileSize(buildLogFileNameWithCounter());
}, 5000);
}
},
getLogFileName(counter?: number) {
if (logFileBasePath) {
return buildLogFileNameWithCounter(counter);
} else {
return undefined;
}
},
cleanLogs() {
cleanAllLogs();
},
};
if (isWorkerRuntime()) {
// Register the serializer on the worker thread
expose(messageEventBusLogWriterWorker);
}
export type MessageEventBusLogWriterWorker = typeof messageEventBusLogWriterWorker;

View file

@ -55,17 +55,14 @@ const isWithQueryString = (candidate: unknown): candidate is { query: string } =
const isMessageEventBusDestinationWebhookOptions = ( const isMessageEventBusDestinationWebhookOptions = (
candidate: unknown, candidate: unknown,
): candidate is MessageEventBusDestinationWebhookOptions => { ): candidate is MessageEventBusDestinationWebhookOptions => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const o = candidate as MessageEventBusDestinationWebhookOptions; const o = candidate as MessageEventBusDestinationWebhookOptions;
if (!o) return false; if (!o) return false;
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
return o.url !== undefined; return o.url !== undefined;
}; };
const isMessageEventBusDestinationOptions = ( const isMessageEventBusDestinationOptions = (
candidate: unknown, candidate: unknown,
): candidate is MessageEventBusDestinationOptions => { ): candidate is MessageEventBusDestinationOptions => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const o = candidate as MessageEventBusDestinationOptions; const o = candidate as MessageEventBusDestinationOptions;
if (!o) return false; if (!o) return false;
return o.__type !== undefined; return o.__type !== undefined;
@ -138,23 +135,20 @@ eventBusRouter.post(
eventBusRouter.get( eventBusRouter.get(
'/destination', '/destination',
// eslint-disable-next-line @typescript-eslint/no-unused-vars ResponseHelper.send(async (req: express.Request): Promise<any> => {
ResponseHelper.send(async (req: express.Request, res: express.Response): Promise<any> => {
let result = []; let result = [];
if (isWithIdString(req.query)) { if (isWithIdString(req.query)) {
result = await eventBus.findDestination(req.query.id); result = await eventBus.findDestination(req.query.id);
} else { } else {
result = await eventBus.findDestination(); result = await eventBus.findDestination();
} }
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return result; return result;
}), }),
); );
eventBusRouter.post( eventBusRouter.post(
'/destination', '/destination',
// eslint-disable-next-line @typescript-eslint/no-unused-vars ResponseHelper.send(async (req: express.Request): Promise<any> => {
ResponseHelper.send(async (req: express.Request, res: express.Response): Promise<any> => {
if (!req.user || (req.user as User).globalRole.name !== 'owner') { if (!req.user || (req.user as User).globalRole.name !== 'owner') {
throw new ResponseHelper.UnauthorizedError('Invalid request'); throw new ResponseHelper.UnauthorizedError('Invalid request');
} }
@ -195,8 +189,7 @@ eventBusRouter.post(
eventBusRouter.get( eventBusRouter.get(
'/testmessage', '/testmessage',
// eslint-disable-next-line @typescript-eslint/no-unused-vars ResponseHelper.send(async (req: express.Request): Promise<any> => {
ResponseHelper.send(async (req: express.Request, res: express.Response): Promise<any> => {
let result = false; let result = false;
if (isWithIdString(req.query)) { if (isWithIdString(req.query)) {
result = await eventBus.testDestination(req.query.id); result = await eventBus.testDestination(req.query.id);
@ -207,8 +200,7 @@ eventBusRouter.get(
eventBusRouter.delete( eventBusRouter.delete(
'/destination', '/destination',
// eslint-disable-next-line @typescript-eslint/no-unused-vars ResponseHelper.send(async (req: express.Request): Promise<any> => {
ResponseHelper.send(async (req: express.Request, res: express.Response): Promise<any> => {
if (!req.user || (req.user as User).globalRole.name !== 'owner') { if (!req.user || (req.user as User).globalRole.name !== 'owner') {
throw new ResponseHelper.UnauthorizedError('Invalid request'); throw new ResponseHelper.UnauthorizedError('Invalid request');
} }

View file

@ -21,6 +21,7 @@ import { MessageEventBusDestinationSyslog } from '../../src/eventbus/MessageEven
import { MessageEventBusDestinationWebhook } from '../../src/eventbus/MessageEventBusDestination/MessageEventBusDestinationWebhook.ee'; import { MessageEventBusDestinationWebhook } from '../../src/eventbus/MessageEventBusDestination/MessageEventBusDestinationWebhook.ee';
import { MessageEventBusDestinationSentry } from '../../src/eventbus/MessageEventBusDestination/MessageEventBusDestinationSentry.ee'; import { MessageEventBusDestinationSentry } from '../../src/eventbus/MessageEventBusDestination/MessageEventBusDestinationSentry.ee';
import { EventMessageAudit } from '../../src/eventbus/EventMessageClasses/EventMessageAudit'; import { EventMessageAudit } from '../../src/eventbus/EventMessageClasses/EventMessageAudit';
import { v4 as uuid } from 'uuid';
jest.unmock('@/eventbus/MessageEventBus/MessageEventBus'); jest.unmock('@/eventbus/MessageEventBus/MessageEventBus');
jest.mock('axios'); jest.mock('axios');
@ -63,28 +64,22 @@ const testSentryDestination: MessageEventBusDestinationSentryOptions = {
}; };
async function cleanLogs() { async function cleanLogs() {
await eventBus.logWriter.getThread()?.cleanLogs(); eventBus.logWriter.cleanAllLogs();
const allMessages = await eventBus.getEventsAll(); const allMessages = await eventBus.getEventsAll();
expect(allMessages.length).toBe(0); expect(allMessages.length).toBe(0);
} }
async function confirmIdsSentUnsent(id: string) { async function confirmIdInAll(id: string) {
const sent = await eventBus.getEventsSent(); const sent = await eventBus.getEventsAll();
const unsent = await eventBus.getEventsUnsent(); expect(sent.length).toBeGreaterThan(0);
expect(sent.length).toBe(1); expect(sent.find((msg) => msg.id === id)).toBeTruthy();
expect(sent[0].id).toBe(id);
expect(unsent.length).toBe(0);
} }
const testMessage = new EventMessageGeneric({ eventName: 'n8n.test.message' }); async function confirmIdSent(id: string) {
const testMessageUnsubscribed = new EventMessageGeneric({ eventName: 'n8n.test.unsub' }); const sent = await eventBus.getEventsSent();
const testAuditMessage = new EventMessageAudit({ expect(sent.length).toBeGreaterThan(0);
eventName: 'n8n.audit.user.updated', expect(sent.find((msg) => msg.id === id)).toBeTruthy();
payload: { }
_secret: 'secret',
public: 'public',
},
});
beforeAll(async () => { beforeAll(async () => {
const initResult = await testDb.init(); const initResult = await testDb.init();
@ -119,36 +114,33 @@ beforeAll(async () => {
}); });
beforeEach(async () => { beforeEach(async () => {
// await testDb.truncate(['EventDestinations'], testDbName);
config.set('userManagement.disabled', false); config.set('userManagement.disabled', false);
config.set('userManagement.isInstanceOwnerSetUp', true); config.set('userManagement.isInstanceOwnerSetUp', true);
config.set('enterprise.features.logStreaming', false); config.set('enterprise.features.logStreaming', false);
}); });
afterAll(async () => { afterAll(async () => {
jest.mock('@/eventbus/MessageEventBus/MessageEventBus');
await testDb.terminate(testDbName); await testDb.terminate(testDbName);
await eventBus.close(); await eventBus.close();
}); });
test('should have a running logwriter process', async () => { test('should have a running logwriter process', () => {
const thread = eventBus.logWriter.getThread(); const thread = eventBus.logWriter.worker;
expect(thread).toBeDefined(); expect(thread).toBeDefined();
}); });
test('should have a clean log', async () => {
await eventBus.logWriter.getThread()?.cleanLogs();
const allMessages = await eventBus.getEventsAll();
expect(allMessages.length).toBe(0);
});
test('should have logwriter log messages', async () => { test('should have logwriter log messages', async () => {
const testMessage = new EventMessageGeneric({ eventName: 'n8n.test.message', id: uuid() });
await eventBus.send(testMessage); await eventBus.send(testMessage);
const sent = await eventBus.getEventsSent(); await new Promise((resolve) => {
const unsent = await eventBus.getEventsUnsent(); eventBus.logWriter.worker?.once('message', async (msg: { command: string; data: any }) => {
expect(sent.length).toBeGreaterThan(0); expect(msg.command).toBe('appendMessageToLog');
expect(unsent.length).toBe(0); expect(msg.data).toBe(true);
expect(sent.find((e) => e.id === testMessage.id)).toEqual(testMessage); await confirmIdInAll(testMessage.id);
resolve(true);
});
});
}); });
test('GET /eventbus/destination should fail due to missing authentication', async () => { test('GET /eventbus/destination should fail due to missing authentication', async () => {
@ -189,8 +181,9 @@ test('GET /eventbus/destination all returned destinations should exist in eventb
}); });
test('should send message to syslog ', async () => { test('should send message to syslog ', async () => {
const testMessage = new EventMessageGeneric({ eventName: 'n8n.test.message', id: uuid() });
config.set('enterprise.features.logStreaming', true); config.set('enterprise.features.logStreaming', true);
await cleanLogs(); // await cleanLogs();
const syslogDestination = eventBus.destinations[ const syslogDestination = eventBus.destinations[
testSyslogDestination.id! testSyslogDestination.id!
@ -208,17 +201,31 @@ test('should send message to syslog ', async () => {
}); });
await eventBus.send(testMessage); await eventBus.send(testMessage);
await new Promise((resolve) => {
await new Promise((resolve) => setTimeout(resolve, 100)); eventBus.logWriter.worker?.on(
expect(mockedSyslogClientLog).toHaveBeenCalled(); 'message',
await confirmIdsSentUnsent(testMessage.id); async function handler001(msg: { command: string; data: any }) {
if (msg.command === 'appendMessageToLog') {
syslogDestination.disable(); await confirmIdInAll(testMessage.id);
} else if (msg.command === 'confirmMessageSent') {
await confirmIdSent(testMessage.id);
expect(mockedSyslogClientLog).toHaveBeenCalled();
syslogDestination.disable();
eventBus.logWriter.worker?.removeListener('message', handler001);
resolve(true);
}
},
);
});
}); });
test('should confirm send message if there are no subscribers', async () => { test('should confirm send message if there are no subscribers', async () => {
const testMessageUnsubscribed = new EventMessageGeneric({
eventName: 'n8n.test.unsub',
id: uuid(),
});
config.set('enterprise.features.logStreaming', true); config.set('enterprise.features.logStreaming', true);
await cleanLogs(); // await cleanLogs();
const syslogDestination = eventBus.destinations[ const syslogDestination = eventBus.destinations[
testSyslogDestination.id! testSyslogDestination.id!
@ -228,25 +235,40 @@ test('should confirm send message if there are no subscribers', async () => {
const mockedSyslogClientLog = jest.spyOn(syslogDestination.client, 'log'); const mockedSyslogClientLog = jest.spyOn(syslogDestination.client, 'log');
mockedSyslogClientLog.mockImplementation((_m, _options, _cb) => { mockedSyslogClientLog.mockImplementation((_m, _options, _cb) => {
eventBus.confirmSent(testMessage, {
id: syslogDestination.id,
name: syslogDestination.label,
});
return syslogDestination.client; return syslogDestination.client;
}); });
await eventBus.send(testMessageUnsubscribed); await eventBus.send(testMessageUnsubscribed);
await new Promise((resolve) => setTimeout(resolve, 100)); await new Promise((resolve) => {
expect(mockedSyslogClientLog).toHaveBeenCalled(); eventBus.logWriter.worker?.on(
await confirmIdsSentUnsent(testMessageUnsubscribed.id); 'message',
async function handler002(msg: { command: string; data: any }) {
syslogDestination.disable(); if (msg.command === 'appendMessageToLog') {
await confirmIdInAll(testMessageUnsubscribed.id);
} else if (msg.command === 'confirmMessageSent') {
await confirmIdSent(testMessageUnsubscribed.id);
expect(mockedSyslogClientLog).toHaveBeenCalled();
syslogDestination.disable();
eventBus.logWriter.worker?.removeListener('message', handler002);
resolve(true);
}
},
);
});
}); });
test('should anonymize audit message to syslog ', async () => { test('should anonymize audit message to syslog ', async () => {
const testAuditMessage = new EventMessageAudit({
eventName: 'n8n.audit.user.updated',
payload: {
_secret: 'secret',
public: 'public',
},
id: uuid(),
});
config.set('enterprise.features.logStreaming', true); config.set('enterprise.features.logStreaming', true);
await cleanLogs(); // await cleanLogs();
const syslogDestination = eventBus.destinations[ const syslogDestination = eventBus.destinations[
testSyslogDestination.id! testSyslogDestination.id!
@ -269,18 +291,44 @@ test('should anonymize audit message to syslog ', async () => {
syslogDestination.anonymizeAuditMessages = true; syslogDestination.anonymizeAuditMessages = true;
await eventBus.send(testAuditMessage); await eventBus.send(testAuditMessage);
expect(mockedSyslogClientLog).toHaveBeenCalled(); await new Promise((resolve) => {
eventBus.logWriter.worker?.on(
'message',
async function handler005(msg: { command: string; data: any }) {
if (msg.command === 'appendMessageToLog') {
const sent = await eventBus.getEventsAll();
await confirmIdInAll(testAuditMessage.id);
expect(mockedSyslogClientLog).toHaveBeenCalled();
eventBus.logWriter.worker?.removeListener('message', handler005);
resolve(true);
}
},
);
});
syslogDestination.anonymizeAuditMessages = false; syslogDestination.anonymizeAuditMessages = false;
await eventBus.send(testAuditMessage); await eventBus.send(testAuditMessage);
expect(mockedSyslogClientLog).toHaveBeenCalled(); await new Promise((resolve) => {
eventBus.logWriter.worker?.on(
syslogDestination.disable(); 'message',
async function handler006(msg: { command: string; data: any }) {
if (msg.command === 'appendMessageToLog') {
const sent = await eventBus.getEventsAll();
await confirmIdInAll(testAuditMessage.id);
expect(mockedSyslogClientLog).toHaveBeenCalled();
syslogDestination.disable();
eventBus.logWriter.worker?.removeListener('message', handler006);
resolve(true);
}
},
);
});
}); });
test('should send message to webhook ', async () => { test('should send message to webhook ', async () => {
const testMessage = new EventMessageGeneric({ eventName: 'n8n.test.message', id: uuid() });
config.set('enterprise.features.logStreaming', true); config.set('enterprise.features.logStreaming', true);
await cleanLogs(); // await cleanLogs();
const webhookDestination = eventBus.destinations[ const webhookDestination = eventBus.destinations[
testWebhookDestination.id! testWebhookDestination.id!
@ -292,16 +340,28 @@ test('should send message to webhook ', async () => {
mockedAxios.request.mockResolvedValue({ status: 200, data: { msg: 'OK' } }); mockedAxios.request.mockResolvedValue({ status: 200, data: { msg: 'OK' } });
await eventBus.send(testMessage); await eventBus.send(testMessage);
// not elegant, but since communication happens through emitters, we'll wait for a bit await new Promise((resolve) => {
await new Promise((resolve) => setTimeout(resolve, 100)); eventBus.logWriter.worker?.on(
await confirmIdsSentUnsent(testMessage.id); 'message',
async function handler003(msg: { command: string; data: any }) {
webhookDestination.disable(); if (msg.command === 'appendMessageToLog') {
await confirmIdInAll(testMessage.id);
} else if (msg.command === 'confirmMessageSent') {
await confirmIdSent(testMessage.id);
expect(mockedAxios.request).toHaveBeenCalled();
webhookDestination.disable();
eventBus.logWriter.worker?.removeListener('message', handler003);
resolve(true);
}
},
);
});
}); });
test('should send message to sentry ', async () => { test('should send message to sentry ', async () => {
const testMessage = new EventMessageGeneric({ eventName: 'n8n.test.message', id: uuid() });
config.set('enterprise.features.logStreaming', true); config.set('enterprise.features.logStreaming', true);
await cleanLogs(); // await cleanLogs();
const sentryDestination = eventBus.destinations[ const sentryDestination = eventBus.destinations[
testSentryDestination.id! testSentryDestination.id!
@ -319,12 +379,22 @@ test('should send message to sentry ', async () => {
}); });
await eventBus.send(testMessage); await eventBus.send(testMessage);
// not elegant, but since communication happens through emitters, we'll wait for a bit await new Promise((resolve) => {
await new Promise((resolve) => setTimeout(resolve, 100)); eventBus.logWriter.worker?.on(
expect(mockedSentryCaptureMessage).toHaveBeenCalled(); 'message',
await confirmIdsSentUnsent(testMessage.id); async function handler004(msg: { command: string; data: any }) {
if (msg.command === 'appendMessageToLog') {
sentryDestination.disable(); await confirmIdInAll(testMessage.id);
} else if (msg.command === 'confirmMessageSent') {
await confirmIdSent(testMessage.id);
expect(mockedSentryCaptureMessage).toHaveBeenCalled();
sentryDestination.disable();
eventBus.logWriter.worker?.removeListener('message', handler004);
resolve(true);
}
},
);
});
}); });
test('DEL /eventbus/destination delete all destinations by id', async () => { test('DEL /eventbus/destination delete all destinations by id', async () => {

View file

@ -214,7 +214,6 @@ importers:
supertest: ^6.2.2 supertest: ^6.2.2
swagger-ui-express: ^4.3.0 swagger-ui-express: ^4.3.0
syslog-client: ^1.1.1 syslog-client: ^1.1.1
threads: ^1.7.0
ts-node: ^9.1.1 ts-node: ^9.1.1
tsc-alias: ^1.7.0 tsc-alias: ^1.7.0
tsconfig-paths: ^3.14.1 tsconfig-paths: ^3.14.1
@ -304,7 +303,6 @@ importers:
sse-channel: 4.0.0 sse-channel: 4.0.0
swagger-ui-express: 4.5.0_express@4.18.2 swagger-ui-express: 4.5.0_express@4.18.2
syslog-client: 1.1.1 syslog-client: 1.1.1
threads: 1.7.0
tslib: 1.14.1 tslib: 1.14.1
typeorm: 0.2.45_b2izk5tn6tm5xb65gvog337urq typeorm: 0.2.45_b2izk5tn6tm5xb65gvog337urq
uuid: 8.3.2 uuid: 8.3.2
@ -11304,12 +11302,6 @@ packages:
- supports-color - supports-color
dev: true dev: true
/esm/3.2.25:
resolution: {integrity: sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==}
engines: {node: '>=6'}
dev: false
optional: true
/espree/6.2.1: /espree/6.2.1:
resolution: {integrity: sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==} resolution: {integrity: sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
@ -13635,11 +13627,6 @@ packages:
resolution: {integrity: sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==} resolution: {integrity: sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==}
dev: true dev: true
/is-observable/2.1.0:
resolution: {integrity: sha512-DailKdLb0WU+xX8K5w7VsJhapwHLZ9jjmazqCJq4X12CTgqq73TKnbRcnSLuXYPOoLQgV5IrD7ePiX/h1vnkBw==}
engines: {node: '>=8'}
dev: false
/is-path-inside/3.0.3: /is-path-inside/3.0.3:
resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -16632,10 +16619,6 @@ packages:
es-abstract: 1.20.4 es-abstract: 1.20.4
dev: true dev: true
/observable-fns/0.6.1:
resolution: {integrity: sha512-9gRK4+sRWzeN6AOewNBTLXir7Zl/i3GB6Yl26gK4flxz8BXVpD3kt8amREmWNb0mxYOGDotvE5a4N+PtGGKdkg==}
dev: false
/on-finished/2.4.1: /on-finished/2.4.1:
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
@ -20391,19 +20374,6 @@ packages:
any-promise: 1.3.0 any-promise: 1.3.0
dev: false dev: false
/threads/1.7.0:
resolution: {integrity: sha512-Mx5NBSHX3sQYR6iI9VYbgHKBLisyB+xROCBGjjWm1O9wb9vfLxdaGtmT/KCjUqMsSNW6nERzCW3T6H43LqjDZQ==}
dependencies:
callsites: 3.1.0
debug: 4.3.4
is-observable: 2.1.0
observable-fns: 0.6.1
optionalDependencies:
tiny-worker: 2.3.0
transitivePeerDependencies:
- supports-color
dev: false
/throttle-debounce/1.1.0: /throttle-debounce/1.1.0:
resolution: {integrity: sha512-XH8UiPCQcWNuk2LYePibW/4qL97+ZQ1AN3FNXwZRBNPPowo/NRU5fAlDCSNBJIYCKbioZfuYtMhG4quqoJhVzg==} resolution: {integrity: sha512-XH8UiPCQcWNuk2LYePibW/4qL97+ZQ1AN3FNXwZRBNPPowo/NRU5fAlDCSNBJIYCKbioZfuYtMhG4quqoJhVzg==}
engines: {node: '>=4'} engines: {node: '>=4'}
@ -20455,13 +20425,6 @@ packages:
globrex: 0.1.2 globrex: 0.1.2
dev: true dev: true
/tiny-worker/2.3.0:
resolution: {integrity: sha512-pJ70wq5EAqTAEl9IkGzA+fN0836rycEuz2Cn6yeZ6FRzlVS5IDOkFHpIoEsksPRQV34GDqXm65+OlnZqUSyK2g==}
dependencies:
esm: 3.2.25
dev: false
optional: true
/tinycolor2/1.4.2: /tinycolor2/1.4.2:
resolution: {integrity: sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==} resolution: {integrity: sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==}
dev: false dev: false