From f3b1e30f6dfa5732a84df64979d29351553951cb Mon Sep 17 00:00:00 2001 From: Ulad Kasach Date: Fri, 27 Dec 2024 13:03:01 -0800 Subject: [PATCH] feat(context): upgrade to grab logger from context --- package-lock.json | 127 ++++++++++++++---- package.json | 4 +- ...AsyncTaskExecutionLifecycleEnqueue.test.ts | 6 +- .../withAsyncTaskExecutionLifecycleEnqueue.ts | 18 ++- ...AsyncTaskExecutionLifecycleExecute.test.ts | 56 ++++---- .../withAsyncTaskExecutionLifecycleExecute.ts | 25 ++-- src/utils/withRetry.ts | 5 +- 7 files changed, 163 insertions(+), 78 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4e873c0..e5bd3c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,8 @@ "change-case": "4.1.2", "date-fns": "2.30.0", "simple-in-memory-queue": "1.1.7", - "uuid": "9.0.0" + "uuid": "9.0.0", + "visualogic": "1.3.2" }, "devDependencies": { "@commitlint/cli": "19.3.0", @@ -42,7 +43,6 @@ "husky": "8.0.3", "jest": "29.3.1", "prettier": "2.8.1", - "simple-leveled-log-methods": "0.3.0", "test-fns": "1.3.0", "ts-jest": "29.1.3", "ts-node": "10.9.2", @@ -17409,27 +17409,6 @@ "uuid": "dist/bin/uuid" } }, - "node_modules/simple-leveled-log-methods": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/simple-leveled-log-methods/-/simple-leveled-log-methods-0.3.0.tgz", - "integrity": "sha512-QlUtTGbNd83eveT82R5uaWxP+PGqVQjBYJOhKgM35gLCEx46CjXvAw2mJnugz8o6MBsHgwXkjoEdbW8ksNHF5A==", - "dev": true, - "dependencies": { - "simple-type-guards": "^0.2.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/simple-leveled-log-methods/node_modules/simple-type-guards": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/simple-type-guards/-/simple-type-guards-0.2.1.tgz", - "integrity": "sha512-3e1kWs8WjJ4+3sq/0WgkLbqdEv2J3HoiMyvne6OQ+IA57qVbdVnkkwb4TbHT939NrczR0fRUz7mqSW2onS6WvQ==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -19011,6 +18990,108 @@ "node": ">=0.10.0" } }, + "node_modules/visualogic": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/visualogic/-/visualogic-1.3.2.tgz", + "integrity": "sha512-lq7tL9lg4X0EJCuEcvLtPC57D/g1B6US108dlhrPl+4WBfnzM+lU57lePgQL4T9JdgybtOAvw/q/qVn9wDy7vg==", + "hasInstallScript": true, + "dependencies": { + "@ehmpathy/error-fns": "1.0.2", + "@ehmpathy/uni-time": "1.5.0", + "type-fns": "1.17.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/visualogic/node_modules/@ehmpathy/error-fns": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@ehmpathy/error-fns/-/error-fns-1.0.2.tgz", + "integrity": "sha512-v3aJIqUvD9a3drx1pyS8La+9u9WTTvNE35NksiD4Oo3VanNe8Rmue/atRHPg4nNYQ/xPv4+RoqC+OBj6cAY8VA==", + "hasInstallScript": true, + "dependencies": { + "type-fns": "0.9.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/visualogic/node_modules/@ehmpathy/error-fns/node_modules/type-fns": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/type-fns/-/type-fns-0.9.0.tgz", + "integrity": "sha512-ndhY4JBIbKix0LuGA5smh/XhFFnbeudnih++WxVoGTfdrITsZe/s3qje9GZNdWwsO+YWGyQkNXwAjnWyM/dipw==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/visualogic/node_modules/@ehmpathy/uni-time": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@ehmpathy/uni-time/-/uni-time-1.5.0.tgz", + "integrity": "sha512-JqmGlboocgn+YZvUCUq/IzXt/CCo1b5NfIzRAk7sXp+wi76sxW/w21LtQGt5ALF/B9UR2GTXNY20EArgDIkuFA==", + "hasInstallScript": true, + "dependencies": { + "@ehmpathy/error-fns": "1.0.2", + "date-fns": "3.6.0", + "domain-glossaries": "1.0.0", + "type-fns": "1.15.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/visualogic/node_modules/@ehmpathy/uni-time/node_modules/type-fns": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/type-fns/-/type-fns-1.15.0.tgz", + "integrity": "sha512-IAxaiIMleiQ1pU6pBbmJagVmQz/88JVydpGXSEGYoIBo1F1Zkz9ibC1IWN+t5mj1PAByDf7S4yOsc6LNYxQM+Q==", + "dependencies": { + "@ehmpathy/error-fns": "1.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/visualogic/node_modules/date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/visualogic/node_modules/type-fns": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/type-fns/-/type-fns-1.17.0.tgz", + "integrity": "sha512-zscRD2lGbnD2Ktyvb79cZ7R1kGm0OBnfisG3g3LdFS9C9pR8YKdXtUojDStvgQtaqyb/a77E5gYTsBY3jrZMsA==", + "hasInstallScript": true, + "dependencies": { + "@ehmpathy/error-fns": "1.0.2", + "type-fns": "1.16.0", + "uuid": "9.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/visualogic/node_modules/type-fns/node_modules/type-fns": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/type-fns/-/type-fns-1.16.0.tgz", + "integrity": "sha512-4erPa91wW979ZHH/zVI340b3s4HQGFYUMH5rBUUP0tyqPGzpZJddUpZXBliu8KF/oXEMOq3Elydb7OgdgKpung==", + "dependencies": { + "@ehmpathy/error-fns": "1.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/visualogic/node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/walk-up-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-1.0.0.tgz", diff --git a/package.json b/package.json index 1de68f6..63943d3 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,8 @@ "change-case": "4.1.2", "date-fns": "2.30.0", "simple-in-memory-queue": "1.1.7", - "uuid": "9.0.0" + "uuid": "9.0.0", + "visualogic": "1.3.2" }, "peerDependencies": { "type-fns": "^1.13.0" @@ -90,7 +91,6 @@ "husky": "8.0.3", "jest": "29.3.1", "prettier": "2.8.1", - "simple-leveled-log-methods": "0.3.0", "test-fns": "1.3.0", "ts-jest": "29.1.3", "ts-node": "10.9.2", diff --git a/src/logic/withAsyncTaskExecutionLifecycleEnqueue.test.ts b/src/logic/withAsyncTaskExecutionLifecycleEnqueue.test.ts index 9c006f4..1271e36 100644 --- a/src/logic/withAsyncTaskExecutionLifecycleEnqueue.test.ts +++ b/src/logic/withAsyncTaskExecutionLifecycleEnqueue.test.ts @@ -47,12 +47,11 @@ describe('withAsyncTaskExecutionLifecycleQueue', () => { const enqueue = withAsyncTaskExecutionLifecycleEnqueue({ getNew: exampleGetNew, dao: exampleDao, - log: console, queue, }); then('it should successfully allow enqueue', async () => { - const enqueued = await enqueue({}); + const enqueued = await enqueue({}, { log: console }); expect(enqueued).toHaveProperty('status'); expect(enqueued.status).toEqual(AsyncTaskStatus.QUEUED); }); @@ -76,12 +75,11 @@ describe('withAsyncTaskExecutionLifecycleQueue', () => { const enqueue = withAsyncTaskExecutionLifecycleEnqueue({ getNew: exampleGetNew, dao: exampleDao, - log: console, queue, }); then('it should successfully allow enqueue', async () => { - const enqueued = await enqueue({}); + const enqueued = await enqueue({}, { log: console }); expect(enqueued).toHaveProperty('status'); expect(enqueued.status).toEqual(AsyncTaskStatus.QUEUED); }); diff --git a/src/logic/withAsyncTaskExecutionLifecycleEnqueue.ts b/src/logic/withAsyncTaskExecutionLifecycleEnqueue.ts index ee3ab1b..b4108b8 100644 --- a/src/logic/withAsyncTaskExecutionLifecycleEnqueue.ts +++ b/src/logic/withAsyncTaskExecutionLifecycleEnqueue.ts @@ -1,6 +1,6 @@ import { UnexpectedCodePathError } from '@ehmpathy/error-fns'; -import type { LogMethods } from 'simple-leveled-log-methods'; import { HasMetadata, isAFunction } from 'type-fns'; +import { VisualogicContext } from 'visualogic'; import { uuid } from '../deps'; import { @@ -96,17 +96,15 @@ export const withAsyncTaskExecutionLifecycleEnqueue = < T extends AsyncTask, U extends Partial, M extends Partial & { status: AsyncTaskStatus }, - C extends AsyncTaskDaoContext, + C extends AsyncTaskDaoContext & VisualogicContext, I extends U, >({ getNew, dao, - log, queue, }: { getNew: (input: I, context: C) => T | Promise; dao: AsyncTaskDao; - log: LogMethods; queue: SimpleAsyncTaskSqsQueueContract | SimpleAsyncTaskAnyQueueContract; }) => { return async (input: I, context: C): Promise> => { @@ -120,7 +118,7 @@ export const withAsyncTaskExecutionLifecycleEnqueue = < // if the task already exists, check that its in a queueable state if (taskFound?.status === AsyncTaskStatus.QUEUED) { - log.debug( + context.log.debug( 'enqueueTask.progress: skipped adding task to queue. reason: task is already queued', { task: taskFound, @@ -129,7 +127,7 @@ export const withAsyncTaskExecutionLifecycleEnqueue = < return taskFound; // if already queued, no need to duplicate the request } if (taskFound?.status === AsyncTaskStatus.ATTEMPTED) { - log.debug( + context.log.debug( 'enqueueTask.progress: skipped adding task to queue. reason: task is already being attempted from queue', { task: taskFound, @@ -138,7 +136,7 @@ export const withAsyncTaskExecutionLifecycleEnqueue = < return taskFound; // if already being attempted, no need to duplicate the request } if (taskFound?.status === AsyncTaskStatus.FULFILLED) { - log.debug( + context.log.debug( 'enqueueTask.progress: skipped adding task to queue. reason: task was already fulfilled', { task: taskFound, @@ -147,7 +145,7 @@ export const withAsyncTaskExecutionLifecycleEnqueue = < return taskFound; // if already fulfilled, no sense in trying it again } if (taskFound?.status === AsyncTaskStatus.CANCELED) { - log.debug( + context.log.debug( 'enqueueTask.progress: skipped adding task to queue. reason: task was already canceled', { task: taskFound, @@ -164,7 +162,7 @@ export const withAsyncTaskExecutionLifecycleEnqueue = < ...taskReadyToQueue, status: AsyncTaskStatus.QUEUED, }; - log.debug('enqueueTask.progress: adding task to queue', { + context.log.debug('enqueueTask.progress: adding task to queue', { task: taskToQueue, queue: { type: queue.type }, }); @@ -196,7 +194,7 @@ export const withAsyncTaskExecutionLifecycleEnqueue = < { queue }, ); })(); - log.debug('enqueueTask.progress: added task to queue', { + context.log.debug('enqueueTask.progress: added task to queue', { task: taskToQueue, queue: { type: queue.type }, }); diff --git a/src/logic/withAsyncTaskExecutionLifecycleExecute.test.ts b/src/logic/withAsyncTaskExecutionLifecycleExecute.test.ts index f1ebc26..c905bc5 100644 --- a/src/logic/withAsyncTaskExecutionLifecycleExecute.test.ts +++ b/src/logic/withAsyncTaskExecutionLifecycleExecute.test.ts @@ -5,7 +5,6 @@ import { HasMetadata } from 'type-fns'; import { uuid } from '../deps'; import { AsyncTaskStatus } from '../domain/objects/AsyncTask'; -import { withAsyncTaskExecutionLifecycleEnqueue } from './withAsyncTaskExecutionLifecycleEnqueue'; import { withAsyncTaskExecutionLifecycleExecute } from './withAsyncTaskExecutionLifecycleExecute'; describe('withAsyncTaskExecutionLifecycleExecute', () => { @@ -55,7 +54,6 @@ describe('withAsyncTaskExecutionLifecycleExecute', () => { }, { dao: daoAsyncTaskEnrichProduct, - log: console, api: { sqs: { sendMessage: sqsSendMessageMock, @@ -78,7 +76,10 @@ describe('withAsyncTaskExecutionLifecycleExecute', () => { }); then('it should successfully attempt to execute', async () => { - const result = await execute({ task: await promiseTask }); + const result = await execute( + { task: await promiseTask }, + { log: console }, + ); expect(executeInnerLogicMock).toHaveBeenCalledTimes(1); expect(result.task.status).toEqual(AsyncTaskStatus.FULFILLED); }); @@ -97,7 +98,9 @@ describe('withAsyncTaskExecutionLifecycleExecute', () => { then( 'it should throw an error, to trigger a retry later, without invoking the inner execution logic', async () => { - const error = await getError(execute({ task: await promiseTask })); + const error = await getError( + execute({ task: await promiseTask }, { log: console }), + ); expect(error.message).toContain( 'this error was thrown to ensure this task is retried later', ); @@ -119,15 +122,18 @@ describe('withAsyncTaskExecutionLifecycleExecute', () => { then( 'it should sqs.sendMessage on the task, to try again later, without invoking the inner execution logic', async () => { - const result = await execute({ - task: await promiseTask, - meta: { - queueUrl: '__queue_url__', - enqueueUuid: uuid(), - queueType: 'SQS', - requeueDepth: 1, + const result = await execute( + { + task: await promiseTask, + meta: { + queueUrl: '__queue_url__', + enqueueUuid: uuid(), + queueType: 'SQS', + requeueDepth: 1, + }, }, - }); + { log: console }, + ); console.log(result); expect(sqsSendMessageMock).toHaveBeenCalledTimes(1); expect(executeInnerLogicMock).toHaveBeenCalledTimes(0); @@ -150,15 +156,18 @@ describe('withAsyncTaskExecutionLifecycleExecute', () => { 'it should throw an error to prevent infiloop due to requeue depth', async () => { const error = await getError( - execute({ - task: await promiseTask, - meta: { - queueUrl: '__queue_url__', - enqueueUuid: uuid(), - queueType: 'SQS', - requeueDepth: 7, + execute( + { + task: await promiseTask, + meta: { + queueUrl: '__queue_url__', + enqueueUuid: uuid(), + queueType: 'SQS', + requeueDepth: 7, + }, }, - }), + { log: console }, + ), ); expect(error.message).toContain( 'attempted to retry a task more than limit times. blocked this attempt to prevent infiloop', @@ -231,7 +240,6 @@ describe('withAsyncTaskExecutionLifecycleExecute', () => { }, { dao: daoAsyncTaskSyncProspects, - log: console, api: { sqs: { sendMessage: sqsSendMessageMock, @@ -254,7 +262,7 @@ describe('withAsyncTaskExecutionLifecycleExecute', () => { status: AsyncTaskStatus.QUEUED, }), }); - const result = await execute({ task }); + const result = await execute({ task }, { log: console }); expect(executeInnerLogicMock).toHaveBeenCalledTimes(1); expect(result.task.status).toEqual(AsyncTaskStatus.FULFILLED); }); @@ -281,7 +289,7 @@ describe('withAsyncTaskExecutionLifecycleExecute', () => { }), }); - const error = await getError(execute({ task })); + const error = await getError(execute({ task }, { log: console })); expect(error.message).toContain( 'this error was thrown to ensure this task is retried later', ); @@ -316,7 +324,7 @@ describe('withAsyncTaskExecutionLifecycleExecute', () => { }), ), }); - const result = await execute({ task }); + const result = await execute({ task }, { log: console }); expect(executeInnerLogicMock).toHaveBeenCalledTimes(1); expect(result.task.status).toEqual(AsyncTaskStatus.FULFILLED); }); diff --git a/src/logic/withAsyncTaskExecutionLifecycleExecute.ts b/src/logic/withAsyncTaskExecutionLifecycleExecute.ts index 19dc34b..ed06de5 100644 --- a/src/logic/withAsyncTaskExecutionLifecycleExecute.ts +++ b/src/logic/withAsyncTaskExecutionLifecycleExecute.ts @@ -1,7 +1,7 @@ import { HelpfulError, UnexpectedCodePathError } from '@ehmpathy/error-fns'; import { HelpfulErrorMetadata } from '@ehmpathy/error-fns/dist/HelpfulError'; import { addSeconds, isBefore, parseISO } from 'date-fns'; -import type { LogMethods } from 'simple-leveled-log-methods'; +import { VisualogicContext } from 'visualogic'; import { AsyncTaskDao, @@ -48,18 +48,16 @@ export const withAsyncTaskExecutionLifecycleExecute = < U extends Partial, M extends Partial & { status: AsyncTaskStatus }, I extends AsyncTaskQueueParcel, - C extends AsyncTaskDaoContext, + C extends AsyncTaskDaoContext & VisualogicContext, O extends Record, >( logic: (input: I & AsyncTaskQueueParcel, context: C) => O | Promise, { dao, - log, api, options, }: { dao: AsyncTaskDao; - log: LogMethods; api?: { sqs?: SimpleAwsSqsApi; }; @@ -105,7 +103,7 @@ export const withAsyncTaskExecutionLifecycleExecute = < { // wait 3 seconds before retrying, to attempt to recover from read-after-write out-of-sync issues (e.g., if we tried to search for the task before the db.reader was synced to db.writer) delay: { seconds: 3 }, - log, + log: context.log, }, )(); @@ -119,10 +117,13 @@ export const withAsyncTaskExecutionLifecycleExecute = < const queueUrl = input.meta.queueUrl; return async (message: string, metadata: HelpfulErrorMetadata) => { // log what we're up to - log.debug(`executeTask.progress: requeueing task to retry later`, { - message, - metadata, - }); + context.log.debug( + `executeTask.progress: requeueing task to retry later`, + { + message, + metadata, + }, + ); // get the sqs api const sqs = @@ -199,14 +200,14 @@ export const withAsyncTaskExecutionLifecycleExecute = < // check that the task has not already been fulfilled and is not canceled; if either are true, warn and exit const emptyResult: Partial = {}; if (foundTask.status === AsyncTaskStatus.FULFILLED) { - log.warn( + context.log.warn( 'executeTask.progress: attempted to execute a task that has already been fulfilled. skipping', { task: foundTask }, ); return { ...emptyResult, task: foundTask }; } if (foundTask.status === AsyncTaskStatus.CANCELED) { - log.warn( + context.log.warn( 'executeTask.progress: attempted to execute a task that has already been canceled. skipping', { task: foundTask }, ); @@ -290,7 +291,7 @@ export const withAsyncTaskExecutionLifecycleExecute = < // otherwise, just return the result with the state of the task now, since this is probably a multi-step task return { ...result, task: taskNow }; } catch (error) { - log.warn( + context.log.warn( 'executeTask.progress: caught an error from the execution attempt. marking it as failed. will re-emit the error for sqs retry', { task: attemptedTask, diff --git a/src/utils/withRetry.ts b/src/utils/withRetry.ts index 5d765b1..063c206 100644 --- a/src/utils/withRetry.ts +++ b/src/utils/withRetry.ts @@ -1,5 +1,5 @@ import { UniDuration, sleep } from '@ehmpathy/uni-time'; -import type { LogMethods } from 'simple-leveled-log-methods'; +import { VisualogicContext } from 'visualogic'; /** * function which calls the wrapped function and runs it again one time if an error is caught @@ -8,8 +8,7 @@ export const withRetry = Promise>( logic: T, options?: { delay?: UniDuration; - log?: LogMethods; - }, + } & VisualogicContext, ): T => { return (async (...args: Parameters): Promise> => { try {