From b31832b8e3439598fff277d91444237b0e8a82ba Mon Sep 17 00:00:00 2001 From: Dobrunia Kostrigin <48620984+Dobrunia@users.noreply.github.com> Date: Sun, 8 Feb 2026 01:53:00 +0300 Subject: [PATCH 01/12] chore: Replace BreadcrumbLevel and BreadcrumbType enums with String to accept arbitrary SDK values --- package.json | 2 +- src/typeDefs/event.ts | 35 ++++++++--------------------------- 2 files changed, 9 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index 4572039e..76aa78a7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hawk.api", - "version": "1.4.3", + "version": "1.4.4", "main": "index.ts", "license": "BUSL-1.1", "scripts": { diff --git a/src/typeDefs/event.ts b/src/typeDefs/event.ts index c2ac3a2d..c200de96 100644 --- a/src/typeDefs/event.ts +++ b/src/typeDefs/event.ts @@ -116,29 +116,6 @@ type EventUser { photo: String } -""" -Breadcrumb severity level -""" -enum BreadcrumbLevel { - fatal - error - warning - info - debug -} - -""" -Breadcrumb type - controls categorization and UI appearance -""" -enum BreadcrumbType { - default - request - ui - navigation - logic - error -} - """ Single breadcrumb entry - represents an event that occurred before the error """ @@ -149,9 +126,11 @@ type Breadcrumb { timestamp: Float! """ - Type of breadcrumb - controls UI categorization + Type of breadcrumb - controls UI categorization. + Common values: default, request, ui, navigation, logic, error. + Accepts any string since SDK users may send custom types. """ - type: BreadcrumbType + type: String """ Category of the event - more specific than type @@ -164,9 +143,11 @@ type Breadcrumb { message: String """ - Severity level of the breadcrumb + Severity level of the breadcrumb. + Common values: fatal, error, warning, info, debug. + Accepts any string since SDK users may send custom levels. """ - level: BreadcrumbLevel + level: String """ Arbitrary key-value data associated with the breadcrumb From 6fbc666a9d13b6f31f5e77e33261d5883b6f2a5c Mon Sep 17 00:00:00 2001 From: Dobrunia Kostrigin <48620984+Dobrunia@users.noreply.github.com> Date: Tue, 10 Feb 2026 14:10:55 +0300 Subject: [PATCH 02/12] fix: tests --- src/integrations/github/__mocks__/service.ts | 5 +++++ src/integrations/github/routes.ts | 8 ++++---- src/resolvers/project.js | 2 +- test/integrations/github-routes.test.ts | 6 +++--- test/resolvers/project.test.ts | 9 +++++++++ 5 files changed, 22 insertions(+), 8 deletions(-) create mode 100644 src/integrations/github/__mocks__/service.ts diff --git a/src/integrations/github/__mocks__/service.ts b/src/integrations/github/__mocks__/service.ts new file mode 100644 index 00000000..04ff8529 --- /dev/null +++ b/src/integrations/github/__mocks__/service.ts @@ -0,0 +1,5 @@ +export const deleteInstallationMock = jest.fn().mockResolvedValue(undefined); + +export const GitHubService = jest.fn().mockImplementation(() => ({ + deleteInstallation: deleteInstallationMock, +})); diff --git a/src/integrations/github/routes.ts b/src/integrations/github/routes.ts index f000ca24..8144ec34 100644 --- a/src/integrations/github/routes.ts +++ b/src/integrations/github/routes.ts @@ -522,15 +522,15 @@ export function createGitHubRouter(factories: ContextFactories): express.Router * just log query parameters and respond with 200 without signature validation. */ if (req.method !== 'POST') { + // eslint-disable-next-line @typescript-eslint/camelcase, camelcase const { code, installation_id, setup_action, state, ...restQuery } = req.query as Record; + // eslint-disable-next-line @typescript-eslint/camelcase, camelcase if (code || installation_id || state || setup_action) { - // eslint-disable-next-line @typescript-eslint/camelcase, camelcase log('info', `${WEBHOOK_LOG_PREFIX}Received non-POST request on /webhook with OAuth-like params`, { - // eslint-disable-next-line @typescript-eslint/camelcase, camelcase code, - installation_id, - setup_action, + installation_id, // eslint-disable-line @typescript-eslint/camelcase, camelcase + setup_action, // eslint-disable-line @typescript-eslint/camelcase, camelcase state, query: restQuery, }); diff --git a/src/resolvers/project.js b/src/resolvers/project.js index aa16734e..c1a2767c 100644 --- a/src/resolvers/project.js +++ b/src/resolvers/project.js @@ -421,7 +421,7 @@ module.exports = { */ const taskManager = project.taskManager; - if (taskManager && taskManager.type === 'github' && taskManager.config?.installationId) { + if (taskManager && taskManager.type === 'github' && taskManager.config && taskManager.config.installationId) { const githubService = new GitHubService(); await githubService.deleteInstallation(taskManager.config.installationId); diff --git a/test/integrations/github-routes.test.ts b/test/integrations/github-routes.test.ts index 81094183..03eacc94 100644 --- a/test/integrations/github-routes.test.ts +++ b/test/integrations/github-routes.test.ts @@ -480,7 +480,7 @@ describe('GitHub Routes - /integration/github/connect', () => { expect(response.status).toBe(302); expect(response.body).toContain('http://localhost:8080/'); - expect(response.body).toContain('error=Missing+or+invalid+OAuth+code'); + expect(response.body).toContain('apiError=Missing+or+invalid+OAuth+code'); }); it('should redirect with error when state is missing', async () => { @@ -501,7 +501,7 @@ describe('GitHub Routes - /integration/github/connect', () => { expect(response.status).toBe(302); expect(response.body).toContain('http://localhost:8080/'); - expect(response.body).toContain('error=Missing+or+invalid+state'); + expect(response.body).toContain('apiError=Missing+or+invalid+state'); }); it('should redirect with error when state is invalid or expired', async () => { @@ -525,7 +525,7 @@ describe('GitHub Routes - /integration/github/connect', () => { expect(response.status).toBe(302); expect(response.body).toContain('http://localhost:8080/'); - expect(response.body).toContain('error=Invalid+or+expired+state'); + expect(response.body).toContain('apiError=Invalid+or+expired+state'); expect(mockGetState).toHaveBeenCalledWith(state); }); diff --git a/test/resolvers/project.test.ts b/test/resolvers/project.test.ts index bda54a40..9e763bce 100644 --- a/test/resolvers/project.test.ts +++ b/test/resolvers/project.test.ts @@ -3,6 +3,11 @@ import { ObjectId } from 'mongodb'; import { ProjectDBScheme, ProjectTaskManagerConfig } from '@hawk.so/types'; import { ResolverContextWithUser } from '../../src/types/graphql'; import { ApolloError, UserInputError } from 'apollo-server-express'; + +jest.mock('../../src/integrations/github/service'); +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { deleteInstallationMock, GitHubService } = require('../../src/integrations/github/service'); + // @ts-expect-error - CommonJS module, TypeScript can't infer types properly import projectResolverModule from '../../src/resolvers/project'; @@ -141,6 +146,8 @@ describe('Project Resolver - Task Manager Mutations', () => { )) as { taskManager: ProjectTaskManagerConfig | null }; expect(context.factories.projectsFactory.findById).toHaveBeenCalledWith(mockProject._id.toString()); + expect(GitHubService).toHaveBeenCalledTimes(1); + expect(deleteInstallationMock).toHaveBeenCalledWith('123456'); expect(mockProject.updateProject).toHaveBeenCalledWith({ taskManager: null, }); @@ -217,6 +224,8 @@ describe('Project Resolver - Task Manager Mutations', () => { context )) as { taskManager: ProjectTaskManagerConfig | null }; + expect(GitHubService).not.toHaveBeenCalled(); + expect(deleteInstallationMock).not.toHaveBeenCalled(); expect(mockProject.updateProject).toHaveBeenCalledWith({ taskManager: null, }); From c56ad1b5fca791d025b4443521c8bced08f265b1 Mon Sep 17 00:00:00 2001 From: Dobrunia Kostrigin <48620984+Dobrunia@users.noreply.github.com> Date: Tue, 10 Feb 2026 14:14:02 +0300 Subject: [PATCH 03/12] refactor: remove GitHub service mock --- .../__mocks__/service.ts => test/__mocks__/github-service.ts | 0 test/resolvers/project.test.ts | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/integrations/github/__mocks__/service.ts => test/__mocks__/github-service.ts (100%) diff --git a/src/integrations/github/__mocks__/service.ts b/test/__mocks__/github-service.ts similarity index 100% rename from src/integrations/github/__mocks__/service.ts rename to test/__mocks__/github-service.ts diff --git a/test/resolvers/project.test.ts b/test/resolvers/project.test.ts index 9e763bce..4a46fd0d 100644 --- a/test/resolvers/project.test.ts +++ b/test/resolvers/project.test.ts @@ -4,9 +4,9 @@ import { ProjectDBScheme, ProjectTaskManagerConfig } from '@hawk.so/types'; import { ResolverContextWithUser } from '../../src/types/graphql'; import { ApolloError, UserInputError } from 'apollo-server-express'; -jest.mock('../../src/integrations/github/service'); +jest.mock('../../src/integrations/github/service', () => require('../__mocks__/github-service')); // eslint-disable-next-line @typescript-eslint/no-var-requires -const { deleteInstallationMock, GitHubService } = require('../../src/integrations/github/service'); +const { deleteInstallationMock, GitHubService } = require('../__mocks__/github-service'); // @ts-expect-error - CommonJS module, TypeScript can't infer types properly import projectResolverModule from '../../src/resolvers/project'; From be71f5d70323ed9cb6f50c1cd4abbea62cd97cf0 Mon Sep 17 00:00:00 2001 From: Dobrunia Kostrigin <48620984+Dobrunia@users.noreply.github.com> Date: Tue, 10 Feb 2026 14:21:44 +0300 Subject: [PATCH 04/12] fix: import --- test/resolvers/project.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/resolvers/project.test.ts b/test/resolvers/project.test.ts index 4a46fd0d..8f50acbc 100644 --- a/test/resolvers/project.test.ts +++ b/test/resolvers/project.test.ts @@ -6,7 +6,7 @@ import { ApolloError, UserInputError } from 'apollo-server-express'; jest.mock('../../src/integrations/github/service', () => require('../__mocks__/github-service')); // eslint-disable-next-line @typescript-eslint/no-var-requires -const { deleteInstallationMock, GitHubService } = require('../__mocks__/github-service'); +import { deleteInstallationMock, GitHubService } from '../__mocks__/github-service'; // @ts-expect-error - CommonJS module, TypeScript can't infer types properly import projectResolverModule from '../../src/resolvers/project'; From 97a6eb510e056c1df4c0738d860dbed668c917d3 Mon Sep 17 00:00:00 2001 From: Dobrunia Kostrigin <48620984+Dobrunia@users.noreply.github.com> Date: Tue, 10 Feb 2026 15:18:32 +0300 Subject: [PATCH 05/12] chore: remove-amplitude --- .env.sample | 2 -- package.json | 1 - src/models/usersFactory.ts | 10 +--------- src/models/workspacesFactory.ts | 9 --------- src/utils/analytics/amplitude.ts | 27 ------------------------- src/utils/analytics/events.ts | 17 ---------------- src/utils/analytics/index.ts | 2 -- yarn.lock | 34 +------------------------------- 8 files changed, 2 insertions(+), 100 deletions(-) delete mode 100644 src/utils/analytics/amplitude.ts delete mode 100644 src/utils/analytics/events.ts delete mode 100644 src/utils/analytics/index.ts diff --git a/.env.sample b/.env.sample index 87180a6a..bb7e7e22 100644 --- a/.env.sample +++ b/.env.sample @@ -77,8 +77,6 @@ CLOUDPAYMENTS_SECRET= # INN of legal entity for CloudKassir LEGAL_ENTITY_INN= -# Token for Amplitude analytics -AMPLITUDE_TOKEN= # AWS S3 Bucket Configuration AWS_S3_ACCESS_KEY_ID= diff --git a/package.json b/package.json index 76aa78a7..46e1dc90 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,6 @@ }, "dependencies": { "@ai-sdk/openai": "^2.0.64", - "@amplitude/node": "^1.10.0", "@graphql-tools/merge": "^8.3.1", "@graphql-tools/schema": "^8.5.1", "@graphql-tools/utils": "^8.9.0", diff --git a/src/models/usersFactory.ts b/src/models/usersFactory.ts index ba3ee1de..29eb7b69 100644 --- a/src/models/usersFactory.ts +++ b/src/models/usersFactory.ts @@ -3,7 +3,7 @@ import UserModel from './user'; import { Collection, Db, OptionalId } from 'mongodb'; import DataLoaders from '../dataLoaders'; import { UserDBScheme } from '@hawk.so/types'; -import { Analytics, AnalyticsEventTypes } from '../utils/analytics'; + /** * Users factory to work with User Model @@ -91,14 +91,6 @@ export default class UsersFactory extends AbstractModelFactory - */ - logEvent: (event: AnalyticsEvent): Promise => { - return amplitude.logEvent(event); - }, -}; diff --git a/src/utils/analytics/events.ts b/src/utils/analytics/events.ts deleted file mode 100644 index b5342660..00000000 --- a/src/utils/analytics/events.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Event } from '@amplitude/node'; - -/** - * Define available metrics - */ -export enum AnalyticsEventTypes { - NEW_USER_REGISTERED = 'new user registered', - - WORKSPACE_CREATED = 'workspace created', - - PROJECT_CREATED = 'project created', -} - -/** - * Define analytics event type - */ -export type AnalyticsEvent = Omit & { 'event_type': AnalyticsEventTypes }; diff --git a/src/utils/analytics/index.ts b/src/utils/analytics/index.ts deleted file mode 100644 index 40d26b1a..00000000 --- a/src/utils/analytics/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { Analytics } from './amplitude'; -export { AnalyticsEventTypes } from './events'; diff --git a/yarn.lock b/yarn.lock index 2c63c92b..be966091 100644 --- a/yarn.lock +++ b/yarn.lock @@ -35,38 +35,6 @@ dependencies: json-schema "^0.4.0" -"@amplitude/identify@^1.10.0": - version "1.10.0" - resolved "https://registry.yarnpkg.com/@amplitude/identify/-/identify-1.10.0.tgz#d62b8b6785c29350c368810475a6fc7b04985210" - integrity sha512-BshMDcZX9qO4mgGBR45HmiHxfcPCDY/eBOE/MTUZBW+y9+N61aKmNY3YJsAUfRPzieDiyfqs8rNm7quVkaNzJQ== - dependencies: - "@amplitude/types" "^1.10.0" - "@amplitude/utils" "^1.10.0" - tslib "^1.9.3" - -"@amplitude/node@^1.10.0": - version "1.10.0" - resolved "https://registry.yarnpkg.com/@amplitude/node/-/node-1.10.0.tgz#33f84ddf82b31471fce53e6fa60b688d4bc62ee4" - integrity sha512-Jh8w1UpxhonWe0kCALVvqiBE3vo5NYmbNZbZrrI9Lfa/1HbGboZlGdg0I7/WtihbZvEjpfcfTOf8OkmtZh6vsQ== - dependencies: - "@amplitude/identify" "^1.10.0" - "@amplitude/types" "^1.10.0" - "@amplitude/utils" "^1.10.0" - tslib "^1.9.3" - -"@amplitude/types@^1.10.0": - version "1.10.0" - resolved "https://registry.yarnpkg.com/@amplitude/types/-/types-1.10.0.tgz#dfaf7cc25f533a1e2b0ef0ad675371b396733c0f" - integrity sha512-xN0gnhutztv6kqHaZ2bre18anQV5GDmMXOeipTvI670g2VjNbPfOzMwu1LN4p1NadYq+GqYI223UcZrXR+R4Pw== - -"@amplitude/utils@^1.10.0": - version "1.10.0" - resolved "https://registry.yarnpkg.com/@amplitude/utils/-/utils-1.10.0.tgz#138b0ba4e5755540a9e4abf426b7a25d045418a9" - integrity sha512-/R8j8IzFH0GYfA6ehQDm5IEzt71gIeMdiYYFIzZp6grERQlgJcwNJMAiza0o2JwwTDIruzqdB3c/vLVjuakp+w== - dependencies: - "@amplitude/types" "^1.10.0" - tslib "^1.9.3" - "@apollo/protobufjs@1.2.4": version "1.2.4" resolved "https://registry.yarnpkg.com/@apollo/protobufjs/-/protobufjs-1.2.4.tgz#d913e7627210ec5efd758ceeb751c776c68ba133" @@ -6662,7 +6630,7 @@ tsconfig@^7.0.0: strip-bom "^3.0.0" strip-json-comments "^2.0.0" -tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: +tslib@^1.8.1, tslib@^1.9.0: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== From 4be9d840b64e8be27f6ef0beb5d38d5eec398bf4 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 10 Feb 2026 12:24:39 +0000 Subject: [PATCH 06/12] Bump version up to 1.4.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 46e1dc90..4e0ee4ae 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hawk.api", - "version": "1.4.4", + "version": "1.4.5", "main": "index.ts", "license": "BUSL-1.1", "scripts": { From ea251422c1515c901a31f89fb47785b0764863a6 Mon Sep 17 00:00:00 2001 From: Dobrunia Kostrigin <48620984+Dobrunia@users.noreply.github.com> Date: Tue, 10 Feb 2026 16:04:37 +0300 Subject: [PATCH 07/12] chore: Add custom breadcrumbs to API error tracking --- package.json | 4 ++-- src/metrics/graphql.ts | 24 +++++++++++++++++++--- src/metrics/mongodb.ts | 19 ++++++++++++++++++ src/rabbitmq.ts | 14 +++++++++++++ yarn.lock | 45 ++++++++++++++---------------------------- 5 files changed, 71 insertions(+), 35 deletions(-) diff --git a/package.json b/package.json index 4e0ee4ae..c4d82b2d 100644 --- a/package.json +++ b/package.json @@ -41,8 +41,8 @@ "@graphql-tools/merge": "^8.3.1", "@graphql-tools/schema": "^8.5.1", "@graphql-tools/utils": "^8.9.0", - "@hawk.so/nodejs": "^3.1.1", - "@hawk.so/types": "^0.5.6", + "@hawk.so/nodejs": "^3.3.0", + "@hawk.so/types": "^0.5.8", "@n1ru4l/json-patch-plus": "^0.2.0", "@node-saml/node-saml": "^5.0.1", "@octokit/oauth-methods": "^4.0.0", diff --git a/src/metrics/graphql.ts b/src/metrics/graphql.ts index b1c490e0..3dd2f969 100644 --- a/src/metrics/graphql.ts +++ b/src/metrics/graphql.ts @@ -1,6 +1,7 @@ import client from 'prom-client'; import { ApolloServerPlugin, GraphQLRequestContext, GraphQLRequestListener } from 'apollo-server-plugin-base'; import { GraphQLError } from 'graphql'; +import HawkCatcher from '@hawk.so/nodejs'; /** * GraphQL operation duration histogram @@ -71,15 +72,32 @@ export const graphqlMetricsPlugin: ApolloServerPlugin = { }, async willSendResponse(ctx: GraphQLRequestContext): Promise { - const duration = (Date.now() - startTime) / 1000; + const durationMs = Date.now() - startTime; + const duration = durationMs / 1000; gqlOperationDuration .labels(operationName, operationType) .observe(duration); + const hasErrors = ctx.errors && ctx.errors.length > 0; + + const breadcrumbData: Record = { operationName, operationType, durationMs }; + + if (hasErrors) { + breadcrumbData.errors = ctx.errors!.map((e: GraphQLError) => e.message).join('; '); + } + + HawkCatcher.breadcrumbs.add({ + type: 'request', + category: 'gql', + message: `${operationType} ${operationName} ${durationMs}ms${hasErrors ? ` [${ctx.errors!.length} error(s)]` : ''}`, + level: hasErrors ? 'error' : 'debug', + data: breadcrumbData, + }); + // Track errors if any - if (ctx.errors && ctx.errors.length > 0) { - ctx.errors.forEach((error: GraphQLError) => { + if (hasErrors) { + ctx.errors!.forEach((error: GraphQLError) => { const errorType = error.extensions?.code || error.name || 'unknown'; gqlOperationErrors diff --git a/src/metrics/mongodb.ts b/src/metrics/mongodb.ts index 44fd608c..43ca671d 100644 --- a/src/metrics/mongodb.ts +++ b/src/metrics/mongodb.ts @@ -1,6 +1,7 @@ import promClient from 'prom-client'; import { MongoClient, MongoClientOptions } from 'mongodb'; import { Effect, sgr } from '../utils/ansi'; +import HawkCatcher from '@hawk.so/nodejs'; /** * MongoDB command duration histogram @@ -306,6 +307,14 @@ export function setupMongoMetrics(client: MongoClient): void { .labels(metadata.commandName, metadata.collectionFamily, metadata.db) .observe(duration); + HawkCatcher.breadcrumbs.add({ + type: 'request', + category: 'db.query', + message: `${metadata.db}.${metadata.collectionFamily}.${metadata.commandName} ${event.duration}ms`, + level: 'debug', + data: { db: metadata.db, collection: metadata.collectionFamily, command: metadata.commandName, durationMs: event.duration }, + }); + // Clean up metadata // eslint-disable-next-line @typescript-eslint/no-explicit-any delete (client as any)[metadataKey]; @@ -337,6 +346,16 @@ export function setupMongoMetrics(client: MongoClient): void { .labels(metadata.commandName, errorCode) .inc(); + const errorMsg = (event.failure as any)?.message || 'Unknown error'; + + HawkCatcher.breadcrumbs.add({ + type: 'error', + category: 'db.query', + message: `${metadata.db}.${metadata.collectionFamily}.${metadata.commandName} FAILED: ${errorMsg} ${event.duration}ms`, + level: 'error', + data: { db: metadata.db, collection: metadata.collectionFamily, command: metadata.commandName, durationMs: event.duration, errorCode }, + }); + // Clean up metadata // eslint-disable-next-line @typescript-eslint/no-explicit-any delete (client as any)[metadataKey]; diff --git a/src/rabbitmq.ts b/src/rabbitmq.ts index cb28ed67..a39366f8 100644 --- a/src/rabbitmq.ts +++ b/src/rabbitmq.ts @@ -133,8 +133,22 @@ export async function setupConnections(): Promise { export async function publish(exchange: string, route: string, message: string, options?: Options.Publish): Promise { try { await channel.publish(exchange, route, Buffer.from(message), options); + HawkCatcher.breadcrumbs.add({ + type: 'request', + category: 'amqp.publish', + message: `AMQP publish ${exchange || '(default)'}/${route}`, + level: 'debug', + data: { exchange, route }, + }); debug(`Message sent: ${message}`); } catch (err) { + HawkCatcher.breadcrumbs.add({ + type: 'error', + category: 'amqp.publish', + message: `AMQP publish FAILED ${exchange || '(default)'}/${route}: ${(err as Error).message}`, + level: 'error', + data: { exchange, route }, + }); HawkCatcher.send(err as Error); console.log('Message was rejected:', (err as Error).stack); } diff --git a/yarn.lock b/yarn.lock index be966091..4b7666a3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -494,26 +494,26 @@ dependencies: tslib "^2.4.0" -"@hawk.so/nodejs@^3.1.1": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@hawk.so/nodejs/-/nodejs-3.1.2.tgz#b06229f0c8a0d8676412329511f9f2b01e492211" - integrity sha512-FqZtJDEc3G/VdirsEEfA4BodA3OGXCSy2188aPSeaLkLWswaKAnkaJNTGHQL59dtOeSbvipMJVgtoqihHkpGBQ== +"@hawk.so/nodejs@^3.3.0": + version "3.3.0" + resolved "https://registry.yarnpkg.com/@hawk.so/nodejs/-/nodejs-3.3.0.tgz#db140b3166cbb2d1199195dd0753f478318113b5" + integrity sha512-0mSJedm+dKjR8Fh8buHmfz3NVamqI7rJiS1y9a5sEECnqQEb1NQWeYO64iIfQJKgmG+Huxa5SyCl/WN0q7eugw== dependencies: - "@hawk.so/types" "^0.1.15" + "@hawk.so/types" "^0.3.0" axios "^0.21.1" stack-trace "^0.0.10" -"@hawk.so/types@^0.1.15": - version "0.1.18" - resolved "https://registry.yarnpkg.com/@hawk.so/types/-/types-0.1.18.tgz#746537634756825f066182737429d11ea124d5c5" - integrity sha512-SvECLGmLb5t90OSpk3n8DCjJsUoyjrq/Z6Ioil80tVkbMXRdGjaHZpn/0w1gBqtgNWBfW2cSbsQPqmyDj1NsqQ== +"@hawk.so/types@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@hawk.so/types/-/types-0.3.0.tgz#3332de27a2dd39832185b8237cb3a1bc4624ec41" + integrity sha512-xM7bgVK3+JhXXIKXXDjVufhCIEuesAyA/o2E9qyq8h9zg04fTgwo/dJ8G+wJblu5W3bNNJ1dWllMgqaH1lY8bA== dependencies: - "@types/mongodb" "^3.5.34" + bson "^7.0.0" -"@hawk.so/types@^0.5.6": - version "0.5.6" - resolved "https://registry.yarnpkg.com/@hawk.so/types/-/types-0.5.6.tgz#1fbd06a79de32595936c817ff416471c0767bd5a" - integrity sha512-oPoi0Zf2GZDh0OdEd+imw9VAIJcp9zwtk3jLVBOvXcX+LbTKOt0kwkcblacQpsTFB1ljleVQ15gULnV3qbHCLw== +"@hawk.so/types@^0.5.8": + version "0.5.8" + resolved "https://registry.yarnpkg.com/@hawk.so/types/-/types-0.5.8.tgz#4278b489f77b5b0335a04ae8184f87c2112116d0" + integrity sha512-3LebU/fFWFCVBHcj8yAyZqjjam9vYo7diRi8BlMBXJ5yC1fE7M44+Zb+lzudHQnysj+ZcHZyBuA/dEpGhB7vxg== dependencies: bson "^7.0.0" @@ -1373,13 +1373,6 @@ "@types/connect" "*" "@types/node" "*" -"@types/bson@*": - version "4.2.0" - resolved "https://registry.yarnpkg.com/@types/bson/-/bson-4.2.0.tgz#a2f71e933ff54b2c3bf267b67fa221e295a33337" - integrity sha512-ELCPqAdroMdcuxqwMgUpifQyRoTpyYCNr1V9xKyF40VsBobsj+BbWNRvwGchMgBPGqkw655ypkjj2MEF5ywVwg== - dependencies: - bson "*" - "@types/connect@*": version "3.4.35" resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" @@ -1602,14 +1595,6 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.0.tgz#e9a9903894405c6a6551f1774df4e64d9804d69c" integrity sha512-fccbsHKqFDXClBZTDLA43zl0+TbxyIwyzIzwwhvoJvhNjOErCdeX2xJbURimv2EbSVUGav001PaCJg4mZxMl4w== -"@types/mongodb@^3.5.34": - version "3.6.20" - resolved "https://registry.yarnpkg.com/@types/mongodb/-/mongodb-3.6.20.tgz#b7c5c580644f6364002b649af1c06c3c0454e1d2" - integrity sha512-WcdpPJCakFzcWWD9juKoZbRtQxKIMYF/JIAM4JrNHrMcnJL6/a2NWjXxW7fo9hxboxxkg+icff8d7+WIEvKgYQ== - dependencies: - "@types/bson" "*" - "@types/node" "*" - "@types/morgan@^1.9.10": version "1.9.10" resolved "https://registry.yarnpkg.com/@types/morgan/-/morgan-1.9.10.tgz#725c15d95a5e6150237524cd713bc2d68f9edf1a" @@ -2488,7 +2473,7 @@ bser@2.1.1: dependencies: node-int64 "^0.4.0" -bson@*, bson@^1.1.4, bson@^6.10.4, bson@^6.7.0, bson@^7.0.0: +bson@^1.1.4, bson@^6.10.4, bson@^6.7.0, bson@^7.0.0: version "6.10.4" resolved "https://registry.yarnpkg.com/bson/-/bson-6.10.4.tgz#d530733bb5bb16fb25c162e01a3344fab332fd2b" integrity sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng== From 5fb22e1595b8f750b7ebcfc7bb52f6fc56c2a83b Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 10 Feb 2026 13:10:47 +0000 Subject: [PATCH 08/12] Bump version up to 1.4.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c4d82b2d..09fffdad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hawk.api", - "version": "1.4.5", + "version": "1.4.6", "main": "index.ts", "license": "BUSL-1.1", "scripts": { From f1abf01702287ce30f5198bd9b0acfe9ee162c58 Mon Sep 17 00:00:00 2001 From: Dobrunia Kostrigin <48620984+Dobrunia@users.noreply.github.com> Date: Tue, 10 Feb 2026 16:24:22 +0300 Subject: [PATCH 09/12] refactor: Enhance logging data structure in publish function --- src/metrics/graphql.ts | 6 +++++- src/metrics/mongodb.ts | 15 +++++++++++++-- src/models/usersFactory.ts | 1 - src/rabbitmq.ts | 10 ++++++++-- 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/metrics/graphql.ts b/src/metrics/graphql.ts index 3dd2f969..a8c80de9 100644 --- a/src/metrics/graphql.ts +++ b/src/metrics/graphql.ts @@ -81,7 +81,11 @@ export const graphqlMetricsPlugin: ApolloServerPlugin = { const hasErrors = ctx.errors && ctx.errors.length > 0; - const breadcrumbData: Record = { operationName, operationType, durationMs }; + const breadcrumbData: Record = { + operationName, + operationType, + durationMs, + }; if (hasErrors) { breadcrumbData.errors = ctx.errors!.map((e: GraphQLError) => e.message).join('; '); diff --git a/src/metrics/mongodb.ts b/src/metrics/mongodb.ts index 43ca671d..b6e3dc55 100644 --- a/src/metrics/mongodb.ts +++ b/src/metrics/mongodb.ts @@ -312,7 +312,12 @@ export function setupMongoMetrics(client: MongoClient): void { category: 'db.query', message: `${metadata.db}.${metadata.collectionFamily}.${metadata.commandName} ${event.duration}ms`, level: 'debug', - data: { db: metadata.db, collection: metadata.collectionFamily, command: metadata.commandName, durationMs: event.duration }, + data: { + db: metadata.db, + collection: metadata.collectionFamily, + command: metadata.commandName, + durationMs: { value: event.duration }, + }, }); // Clean up metadata @@ -353,7 +358,13 @@ export function setupMongoMetrics(client: MongoClient): void { category: 'db.query', message: `${metadata.db}.${metadata.collectionFamily}.${metadata.commandName} FAILED: ${errorMsg} ${event.duration}ms`, level: 'error', - data: { db: metadata.db, collection: metadata.collectionFamily, command: metadata.commandName, durationMs: event.duration, errorCode }, + data: { + db: metadata.db, + collection: metadata.collectionFamily, + command: metadata.commandName, + durationMs: { value: event.duration }, + errorCode, + }, }); // Clean up metadata diff --git a/src/models/usersFactory.ts b/src/models/usersFactory.ts index 29eb7b69..8e1869f8 100644 --- a/src/models/usersFactory.ts +++ b/src/models/usersFactory.ts @@ -4,7 +4,6 @@ import { Collection, Db, OptionalId } from 'mongodb'; import DataLoaders from '../dataLoaders'; import { UserDBScheme } from '@hawk.so/types'; - /** * Users factory to work with User Model */ diff --git a/src/rabbitmq.ts b/src/rabbitmq.ts index a39366f8..e790fd2b 100644 --- a/src/rabbitmq.ts +++ b/src/rabbitmq.ts @@ -138,7 +138,10 @@ export async function publish(exchange: string, route: string, message: string, category: 'amqp.publish', message: `AMQP publish ${exchange || '(default)'}/${route}`, level: 'debug', - data: { exchange, route }, + data: { + exchange: { value: exchange }, + route: { value: route }, + }, }); debug(`Message sent: ${message}`); } catch (err) { @@ -147,7 +150,10 @@ export async function publish(exchange: string, route: string, message: string, category: 'amqp.publish', message: `AMQP publish FAILED ${exchange || '(default)'}/${route}: ${(err as Error).message}`, level: 'error', - data: { exchange, route }, + data: { + exchange: { value: exchange }, + route: { value: route }, + }, }); HawkCatcher.send(err as Error); console.log('Message was rejected:', (err as Error).stack); From 18f14eb7216e0f5a50156d6fd7122c4f99148e37 Mon Sep 17 00:00:00 2001 From: Dobrunia Kostrigin <48620984+Dobrunia@users.noreply.github.com> Date: Tue, 10 Feb 2026 17:11:36 +0300 Subject: [PATCH 10/12] fix: lint --- src/metrics/graphql.ts | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/metrics/graphql.ts b/src/metrics/graphql.ts index a8c80de9..8bb38877 100644 --- a/src/metrics/graphql.ts +++ b/src/metrics/graphql.ts @@ -2,7 +2,6 @@ import client from 'prom-client'; import { ApolloServerPlugin, GraphQLRequestContext, GraphQLRequestListener } from 'apollo-server-plugin-base'; import { GraphQLError } from 'graphql'; import HawkCatcher from '@hawk.so/nodejs'; - /** * GraphQL operation duration histogram * Tracks GraphQL operation duration by operation name and type @@ -81,22 +80,17 @@ export const graphqlMetricsPlugin: ApolloServerPlugin = { const hasErrors = ctx.errors && ctx.errors.length > 0; - const breadcrumbData: Record = { - operationName, - operationType, - durationMs, - }; - - if (hasErrors) { - breadcrumbData.errors = ctx.errors!.map((e: GraphQLError) => e.message).join('; '); - } - HawkCatcher.breadcrumbs.add({ type: 'request', category: 'gql', message: `${operationType} ${operationName} ${durationMs}ms${hasErrors ? ` [${ctx.errors!.length} error(s)]` : ''}`, level: hasErrors ? 'error' : 'debug', - data: breadcrumbData, + data: { + operationName: { value: operationName }, + operationType: { value: operationType }, + durationMs: { value: durationMs }, + ...(hasErrors && { errors: { value: ctx.errors!.map((e: GraphQLError) => e.message).join('; ') } }), + }, }); // Track errors if any From 71d43a076e55a61175a74aaee621b7e31cf49a4a Mon Sep 17 00:00:00 2001 From: Dobrunia Kostrigin <48620984+Dobrunia@users.noreply.github.com> Date: Tue, 10 Feb 2026 19:24:47 +0300 Subject: [PATCH 11/12] chore: rename breadcrumbs category --- index.ts | 3 +++ src/metrics/graphql.ts | 2 +- src/metrics/mongodb.ts | 4 ++-- src/rabbitmq.ts | 4 ++-- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/index.ts b/index.ts index 5832cf8d..591016ab 100644 --- a/index.ts +++ b/index.ts @@ -8,6 +8,9 @@ if (process.env.HAWK_CATCHER_TOKEN) { HawkCatcher.init({ token: process.env.HAWK_CATCHER_TOKEN, release: `${name}-${version}`, + breadcrumbs: { + maxBreadcrumbs: 100 + } }); } diff --git a/src/metrics/graphql.ts b/src/metrics/graphql.ts index 8bb38877..046d903a 100644 --- a/src/metrics/graphql.ts +++ b/src/metrics/graphql.ts @@ -82,7 +82,7 @@ export const graphqlMetricsPlugin: ApolloServerPlugin = { HawkCatcher.breadcrumbs.add({ type: 'request', - category: 'gql', + category: 'GraphQL Operation', message: `${operationType} ${operationName} ${durationMs}ms${hasErrors ? ` [${ctx.errors!.length} error(s)]` : ''}`, level: hasErrors ? 'error' : 'debug', data: { diff --git a/src/metrics/mongodb.ts b/src/metrics/mongodb.ts index b6e3dc55..a5c1f51d 100644 --- a/src/metrics/mongodb.ts +++ b/src/metrics/mongodb.ts @@ -309,7 +309,7 @@ export function setupMongoMetrics(client: MongoClient): void { HawkCatcher.breadcrumbs.add({ type: 'request', - category: 'db.query', + category: 'MongoDB Operation', message: `${metadata.db}.${metadata.collectionFamily}.${metadata.commandName} ${event.duration}ms`, level: 'debug', data: { @@ -355,7 +355,7 @@ export function setupMongoMetrics(client: MongoClient): void { HawkCatcher.breadcrumbs.add({ type: 'error', - category: 'db.query', + category: 'MongoDB operation', message: `${metadata.db}.${metadata.collectionFamily}.${metadata.commandName} FAILED: ${errorMsg} ${event.duration}ms`, level: 'error', data: { diff --git a/src/rabbitmq.ts b/src/rabbitmq.ts index e790fd2b..c4c7e7db 100644 --- a/src/rabbitmq.ts +++ b/src/rabbitmq.ts @@ -135,7 +135,7 @@ export async function publish(exchange: string, route: string, message: string, await channel.publish(exchange, route, Buffer.from(message), options); HawkCatcher.breadcrumbs.add({ type: 'request', - category: 'amqp.publish', + category: 'RabbitMQ Operation', message: `AMQP publish ${exchange || '(default)'}/${route}`, level: 'debug', data: { @@ -147,7 +147,7 @@ export async function publish(exchange: string, route: string, message: string, } catch (err) { HawkCatcher.breadcrumbs.add({ type: 'error', - category: 'amqp.publish', + category: 'RabbitMQ Operation', message: `AMQP publish FAILED ${exchange || '(default)'}/${route}: ${(err as Error).message}`, level: 'error', data: { From cf1eedadc6f3f0ffc0ed5d4194219ad0113a50e1 Mon Sep 17 00:00:00 2001 From: Dobrunia Kostrigin <48620984+Dobrunia@users.noreply.github.com> Date: Tue, 10 Feb 2026 19:33:58 +0300 Subject: [PATCH 12/12] chore: update @hawk.so/nodejs to version 3.3.1 --- package.json | 2 +- yarn.lock | 17 +++++------------ 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 09fffdad..52feab40 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "@graphql-tools/merge": "^8.3.1", "@graphql-tools/schema": "^8.5.1", "@graphql-tools/utils": "^8.9.0", - "@hawk.so/nodejs": "^3.3.0", + "@hawk.so/nodejs": "^3.3.1", "@hawk.so/types": "^0.5.8", "@n1ru4l/json-patch-plus": "^0.2.0", "@node-saml/node-saml": "^5.0.1", diff --git a/yarn.lock b/yarn.lock index 4b7666a3..490bd0dd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -494,22 +494,15 @@ dependencies: tslib "^2.4.0" -"@hawk.so/nodejs@^3.3.0": - version "3.3.0" - resolved "https://registry.yarnpkg.com/@hawk.so/nodejs/-/nodejs-3.3.0.tgz#db140b3166cbb2d1199195dd0753f478318113b5" - integrity sha512-0mSJedm+dKjR8Fh8buHmfz3NVamqI7rJiS1y9a5sEECnqQEb1NQWeYO64iIfQJKgmG+Huxa5SyCl/WN0q7eugw== +"@hawk.so/nodejs@^3.3.1": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@hawk.so/nodejs/-/nodejs-3.3.1.tgz#23e304607a64cd3a91e488d481cc968fccab6dba" + integrity sha512-GALpgM/96As5gE3YdwVcMglTc57Dfqez3b2EciKJoq0u174gK/h+8tayEL+/65pqBy7BNni8ptCQWdgw5Zv5yA== dependencies: - "@hawk.so/types" "^0.3.0" + "@hawk.so/types" "^0.5.8" axios "^0.21.1" stack-trace "^0.0.10" -"@hawk.so/types@^0.3.0": - version "0.3.0" - resolved "https://registry.yarnpkg.com/@hawk.so/types/-/types-0.3.0.tgz#3332de27a2dd39832185b8237cb3a1bc4624ec41" - integrity sha512-xM7bgVK3+JhXXIKXXDjVufhCIEuesAyA/o2E9qyq8h9zg04fTgwo/dJ8G+wJblu5W3bNNJ1dWllMgqaH1lY8bA== - dependencies: - bson "^7.0.0" - "@hawk.so/types@^0.5.8": version "0.5.8" resolved "https://registry.yarnpkg.com/@hawk.so/types/-/types-0.5.8.tgz#4278b489f77b5b0335a04ae8184f87c2112116d0"