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/package.json b/package.json index 4e0ee4ae..52feab40 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": { @@ -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.1", + "@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..046d903a 100644 --- a/src/metrics/graphql.ts +++ b/src/metrics/graphql.ts @@ -1,7 +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 * Tracks GraphQL operation duration by operation name and type @@ -71,15 +71,31 @@ 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; + + HawkCatcher.breadcrumbs.add({ + type: 'request', + category: 'GraphQL Operation', + message: `${operationType} ${operationName} ${durationMs}ms${hasErrors ? ` [${ctx.errors!.length} error(s)]` : ''}`, + level: hasErrors ? 'error' : 'debug', + 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 - 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..a5c1f51d 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,19 @@ export function setupMongoMetrics(client: MongoClient): void { .labels(metadata.commandName, metadata.collectionFamily, metadata.db) .observe(duration); + HawkCatcher.breadcrumbs.add({ + type: 'request', + category: 'MongoDB Operation', + message: `${metadata.db}.${metadata.collectionFamily}.${metadata.commandName} ${event.duration}ms`, + level: 'debug', + data: { + db: metadata.db, + collection: metadata.collectionFamily, + command: metadata.commandName, + durationMs: { value: event.duration }, + }, + }); + // Clean up metadata // eslint-disable-next-line @typescript-eslint/no-explicit-any delete (client as any)[metadataKey]; @@ -337,6 +351,22 @@ 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: 'MongoDB operation', + 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: { value: 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..c4c7e7db 100644 --- a/src/rabbitmq.ts +++ b/src/rabbitmq.ts @@ -133,8 +133,28 @@ 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: 'RabbitMQ Operation', + message: `AMQP publish ${exchange || '(default)'}/${route}`, + level: 'debug', + data: { + exchange: { value: exchange }, + route: { value: route }, + }, + }); debug(`Message sent: ${message}`); } catch (err) { + HawkCatcher.breadcrumbs.add({ + type: 'error', + category: 'RabbitMQ Operation', + message: `AMQP publish FAILED ${exchange || '(default)'}/${route}: ${(err as Error).message}`, + level: 'error', + data: { + exchange: { value: exchange }, + route: { value: 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..490bd0dd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -494,26 +494,19 @@ 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.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.1.15" + "@hawk.so/types" "^0.5.8" 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== - dependencies: - "@types/mongodb" "^3.5.34" - -"@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 +1366,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 +1588,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 +2466,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==