Skip to content

Commit 335f6b7

Browse files
Add custom breadcrumbs to API error tracking (#625)
* chore: Replace BreadcrumbLevel and BreadcrumbType enums with String to accept arbitrary SDK values * fix: tests * refactor: remove GitHub service mock * fix: import * chore: remove-amplitude * Bump version up to 1.4.5 * chore: Add custom breadcrumbs to API error tracking * Bump version up to 1.4.6 * refactor: Enhance logging data structure in publish function * fix: lint * chore: rename breadcrumbs category * chore: update @hawk.so/nodejs to version 3.3.1 --------- Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
1 parent d115f61 commit 335f6b7

File tree

6 files changed

+86
-39
lines changed

6 files changed

+86
-39
lines changed

index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ if (process.env.HAWK_CATCHER_TOKEN) {
88
HawkCatcher.init({
99
token: process.env.HAWK_CATCHER_TOKEN,
1010
release: `${name}-${version}`,
11+
breadcrumbs: {
12+
maxBreadcrumbs: 100
13+
}
1114
});
1215
}
1316

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "hawk.api",
3-
"version": "1.4.5",
3+
"version": "1.4.6",
44
"main": "index.ts",
55
"license": "BUSL-1.1",
66
"scripts": {
@@ -41,8 +41,8 @@
4141
"@graphql-tools/merge": "^8.3.1",
4242
"@graphql-tools/schema": "^8.5.1",
4343
"@graphql-tools/utils": "^8.9.0",
44-
"@hawk.so/nodejs": "^3.1.1",
45-
"@hawk.so/types": "^0.5.6",
44+
"@hawk.so/nodejs": "^3.3.1",
45+
"@hawk.so/types": "^0.5.8",
4646
"@n1ru4l/json-patch-plus": "^0.2.0",
4747
"@node-saml/node-saml": "^5.0.1",
4848
"@octokit/oauth-methods": "^4.0.0",

src/metrics/graphql.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import client from 'prom-client';
22
import { ApolloServerPlugin, GraphQLRequestContext, GraphQLRequestListener } from 'apollo-server-plugin-base';
33
import { GraphQLError } from 'graphql';
4-
4+
import HawkCatcher from '@hawk.so/nodejs';
55
/**
66
* GraphQL operation duration histogram
77
* Tracks GraphQL operation duration by operation name and type
@@ -71,15 +71,31 @@ export const graphqlMetricsPlugin: ApolloServerPlugin = {
7171
},
7272

7373
async willSendResponse(ctx: GraphQLRequestContext): Promise<void> {
74-
const duration = (Date.now() - startTime) / 1000;
74+
const durationMs = Date.now() - startTime;
75+
const duration = durationMs / 1000;
7576

7677
gqlOperationDuration
7778
.labels(operationName, operationType)
7879
.observe(duration);
7980

81+
const hasErrors = ctx.errors && ctx.errors.length > 0;
82+
83+
HawkCatcher.breadcrumbs.add({
84+
type: 'request',
85+
category: 'GraphQL Operation',
86+
message: `${operationType} ${operationName} ${durationMs}ms${hasErrors ? ` [${ctx.errors!.length} error(s)]` : ''}`,
87+
level: hasErrors ? 'error' : 'debug',
88+
data: {
89+
operationName: { value: operationName },
90+
operationType: { value: operationType },
91+
durationMs: { value: durationMs },
92+
...(hasErrors && { errors: { value: ctx.errors!.map((e: GraphQLError) => e.message).join('; ') } }),
93+
},
94+
});
95+
8096
// Track errors if any
81-
if (ctx.errors && ctx.errors.length > 0) {
82-
ctx.errors.forEach((error: GraphQLError) => {
97+
if (hasErrors) {
98+
ctx.errors!.forEach((error: GraphQLError) => {
8399
const errorType = error.extensions?.code || error.name || 'unknown';
84100

85101
gqlOperationErrors

src/metrics/mongodb.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import promClient from 'prom-client';
22
import { MongoClient, MongoClientOptions } from 'mongodb';
33
import { Effect, sgr } from '../utils/ansi';
4+
import HawkCatcher from '@hawk.so/nodejs';
45

56
/**
67
* MongoDB command duration histogram
@@ -306,6 +307,19 @@ export function setupMongoMetrics(client: MongoClient): void {
306307
.labels(metadata.commandName, metadata.collectionFamily, metadata.db)
307308
.observe(duration);
308309

310+
HawkCatcher.breadcrumbs.add({
311+
type: 'request',
312+
category: 'MongoDB Operation',
313+
message: `${metadata.db}.${metadata.collectionFamily}.${metadata.commandName} ${event.duration}ms`,
314+
level: 'debug',
315+
data: {
316+
db: metadata.db,
317+
collection: metadata.collectionFamily,
318+
command: metadata.commandName,
319+
durationMs: { value: event.duration },
320+
},
321+
});
322+
309323
// Clean up metadata
310324
// eslint-disable-next-line @typescript-eslint/no-explicit-any
311325
delete (client as any)[metadataKey];
@@ -337,6 +351,22 @@ export function setupMongoMetrics(client: MongoClient): void {
337351
.labels(metadata.commandName, errorCode)
338352
.inc();
339353

354+
const errorMsg = (event.failure as any)?.message || 'Unknown error';
355+
356+
HawkCatcher.breadcrumbs.add({
357+
type: 'error',
358+
category: 'MongoDB operation',
359+
message: `${metadata.db}.${metadata.collectionFamily}.${metadata.commandName} FAILED: ${errorMsg} ${event.duration}ms`,
360+
level: 'error',
361+
data: {
362+
db: metadata.db,
363+
collection: metadata.collectionFamily,
364+
command: metadata.commandName,
365+
durationMs: { value: event.duration },
366+
errorCode,
367+
},
368+
});
369+
340370
// Clean up metadata
341371
// eslint-disable-next-line @typescript-eslint/no-explicit-any
342372
delete (client as any)[metadataKey];

src/rabbitmq.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,28 @@ export async function setupConnections(): Promise<void> {
133133
export async function publish(exchange: string, route: string, message: string, options?: Options.Publish): Promise<void> {
134134
try {
135135
await channel.publish(exchange, route, Buffer.from(message), options);
136+
HawkCatcher.breadcrumbs.add({
137+
type: 'request',
138+
category: 'RabbitMQ Operation',
139+
message: `AMQP publish ${exchange || '(default)'}/${route}`,
140+
level: 'debug',
141+
data: {
142+
exchange: { value: exchange },
143+
route: { value: route },
144+
},
145+
});
136146
debug(`Message sent: ${message}`);
137147
} catch (err) {
148+
HawkCatcher.breadcrumbs.add({
149+
type: 'error',
150+
category: 'RabbitMQ Operation',
151+
message: `AMQP publish FAILED ${exchange || '(default)'}/${route}: ${(err as Error).message}`,
152+
level: 'error',
153+
data: {
154+
exchange: { value: exchange },
155+
route: { value: route },
156+
},
157+
});
138158
HawkCatcher.send(err as Error);
139159
console.log('Message was rejected:', (err as Error).stack);
140160
}

yarn.lock

Lines changed: 10 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -494,26 +494,19 @@
494494
dependencies:
495495
tslib "^2.4.0"
496496

497-
"@hawk.so/nodejs@^3.1.1":
498-
version "3.1.2"
499-
resolved "https://registry.yarnpkg.com/@hawk.so/nodejs/-/nodejs-3.1.2.tgz#b06229f0c8a0d8676412329511f9f2b01e492211"
500-
integrity sha512-FqZtJDEc3G/VdirsEEfA4BodA3OGXCSy2188aPSeaLkLWswaKAnkaJNTGHQL59dtOeSbvipMJVgtoqihHkpGBQ==
497+
"@hawk.so/nodejs@^3.3.1":
498+
version "3.3.1"
499+
resolved "https://registry.yarnpkg.com/@hawk.so/nodejs/-/nodejs-3.3.1.tgz#23e304607a64cd3a91e488d481cc968fccab6dba"
500+
integrity sha512-GALpgM/96As5gE3YdwVcMglTc57Dfqez3b2EciKJoq0u174gK/h+8tayEL+/65pqBy7BNni8ptCQWdgw5Zv5yA==
501501
dependencies:
502-
"@hawk.so/types" "^0.1.15"
502+
"@hawk.so/types" "^0.5.8"
503503
axios "^0.21.1"
504504
stack-trace "^0.0.10"
505505

506-
"@hawk.so/types@^0.1.15":
507-
version "0.1.18"
508-
resolved "https://registry.yarnpkg.com/@hawk.so/types/-/types-0.1.18.tgz#746537634756825f066182737429d11ea124d5c5"
509-
integrity sha512-SvECLGmLb5t90OSpk3n8DCjJsUoyjrq/Z6Ioil80tVkbMXRdGjaHZpn/0w1gBqtgNWBfW2cSbsQPqmyDj1NsqQ==
510-
dependencies:
511-
"@types/mongodb" "^3.5.34"
512-
513-
"@hawk.so/types@^0.5.6":
514-
version "0.5.6"
515-
resolved "https://registry.yarnpkg.com/@hawk.so/types/-/types-0.5.6.tgz#1fbd06a79de32595936c817ff416471c0767bd5a"
516-
integrity sha512-oPoi0Zf2GZDh0OdEd+imw9VAIJcp9zwtk3jLVBOvXcX+LbTKOt0kwkcblacQpsTFB1ljleVQ15gULnV3qbHCLw==
506+
"@hawk.so/types@^0.5.8":
507+
version "0.5.8"
508+
resolved "https://registry.yarnpkg.com/@hawk.so/types/-/types-0.5.8.tgz#4278b489f77b5b0335a04ae8184f87c2112116d0"
509+
integrity sha512-3LebU/fFWFCVBHcj8yAyZqjjam9vYo7diRi8BlMBXJ5yC1fE7M44+Zb+lzudHQnysj+ZcHZyBuA/dEpGhB7vxg==
517510
dependencies:
518511
bson "^7.0.0"
519512

@@ -1373,13 +1366,6 @@
13731366
"@types/connect" "*"
13741367
"@types/node" "*"
13751368

1376-
"@types/bson@*":
1377-
version "4.2.0"
1378-
resolved "https://registry.yarnpkg.com/@types/bson/-/bson-4.2.0.tgz#a2f71e933ff54b2c3bf267b67fa221e295a33337"
1379-
integrity sha512-ELCPqAdroMdcuxqwMgUpifQyRoTpyYCNr1V9xKyF40VsBobsj+BbWNRvwGchMgBPGqkw655ypkjj2MEF5ywVwg==
1380-
dependencies:
1381-
bson "*"
1382-
13831369
"@types/connect@*":
13841370
version "3.4.35"
13851371
resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1"
@@ -1602,14 +1588,6 @@
16021588
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.0.tgz#e9a9903894405c6a6551f1774df4e64d9804d69c"
16031589
integrity sha512-fccbsHKqFDXClBZTDLA43zl0+TbxyIwyzIzwwhvoJvhNjOErCdeX2xJbURimv2EbSVUGav001PaCJg4mZxMl4w==
16041590

1605-
"@types/mongodb@^3.5.34":
1606-
version "3.6.20"
1607-
resolved "https://registry.yarnpkg.com/@types/mongodb/-/mongodb-3.6.20.tgz#b7c5c580644f6364002b649af1c06c3c0454e1d2"
1608-
integrity sha512-WcdpPJCakFzcWWD9juKoZbRtQxKIMYF/JIAM4JrNHrMcnJL6/a2NWjXxW7fo9hxboxxkg+icff8d7+WIEvKgYQ==
1609-
dependencies:
1610-
"@types/bson" "*"
1611-
"@types/node" "*"
1612-
16131591
"@types/morgan@^1.9.10":
16141592
version "1.9.10"
16151593
resolved "https://registry.yarnpkg.com/@types/morgan/-/morgan-1.9.10.tgz#725c15d95a5e6150237524cd713bc2d68f9edf1a"
@@ -2488,7 +2466,7 @@ bser@2.1.1:
24882466
dependencies:
24892467
node-int64 "^0.4.0"
24902468

2491-
bson@*, bson@^1.1.4, bson@^6.10.4, bson@^6.7.0, bson@^7.0.0:
2469+
bson@^1.1.4, bson@^6.10.4, bson@^6.7.0, bson@^7.0.0:
24922470
version "6.10.4"
24932471
resolved "https://registry.yarnpkg.com/bson/-/bson-6.10.4.tgz#d530733bb5bb16fb25c162e01a3344fab332fd2b"
24942472
integrity sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==

0 commit comments

Comments
 (0)