Skip to content

Commit

Permalink
Add support for the custom attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
kibertoad committed Dec 15, 2024
1 parent 7cd2b16 commit fa50606
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 21 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,8 @@ The plugin decorates your Fastify instance with a `NewRelicTransactionManager`,
- `start()`, which takes a `jobName`, and starts a background transaction with the provided name;
- `stop()`, which takes a `jobId`, and ends the background transaction referenced by the ID;
- `addCustomAttribute()`, which takes `attrName` and `attrValue` and records the custom attribute as such defined. `attrValue` can be a string, a number, or a boolean.
- `addCustomAttribute()`, which takes `attrName` and `attrValue` and adds the custom attribute to the current transaction. `attrValue` can be a string, a number, or a boolean.
- `addCustomAttributes()`, which passes `atts` map of the custom attributes to the current transaction. `_uniqueTransactionKey` argument is not used (because New Relic doesn't support setting custom attributes directly on the transaction handle), any string can be passed.
### Amplitude Plugin
Expand Down
12 changes: 12 additions & 0 deletions lib/plugins/newrelicTransactionManagerPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ interface Newrelic {
shutdown: typeof Shutdown
getTransaction: typeof GetTransaction
addCustomAttribute(key: string, value: string | number | boolean): void
addCustomAttributes(atts: { [key: string]: string | number | boolean }): void
}

let newrelic: Newrelic
Expand Down Expand Up @@ -50,6 +51,17 @@ export class NewRelicTransactionManager implements TransactionObservabilityManag
newrelic.addCustomAttribute(attrName, attrValue)
}

public addCustomAttributes(
_uniqueTransactionKey: string,
atts: { [p: string]: string | number | boolean },
): void {
if (!this.isEnabled) {
return
}

newrelic.addCustomAttributes(atts)
}

/**
* @param transactionName - used for grouping similar transactions together
* @param uniqueTransactionKey - used for identifying specific ongoing transaction. Must be reasonably unique to reduce possibility of collisions
Expand Down
47 changes: 41 additions & 6 deletions lib/plugins/prometheus/PrometheusCounterTransactionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,25 @@ import type { Counter } from 'prom-client'
* TransactionObservabilityManager implementation that uses Prometheus counter
* to track the number of started, failed and success transactions.
*/
export class PrometheusCounterTransactionManager implements TransactionObservabilityManager {
export class PrometheusCounterTransactionManager<CustomLabels extends string = never>
implements TransactionObservabilityManager
{
private readonly metricName: string
private readonly metricDescription: string
private readonly counter?: Counter<'status' | 'transactionName'>

private readonly transactionNameByKey: Map<string, string> = new Map()
private readonly customLabelsByKey: Map<string, Record<CustomLabels, string>> = new Map()

constructor(metricName: string, metricDescription: string, appMetrics?: IFastifyMetrics) {
constructor(
metricName: string,
metricDescription: string,
appMetrics?: IFastifyMetrics,
customLabels?: string[],
) {
this.metricName = metricName
this.metricDescription = metricDescription
this.counter = this.registerMetric(appMetrics)
this.counter = this.registerMetric(appMetrics, customLabels)
}

start(transactionName: string, uniqueTransactionKey: string): void {
Expand All @@ -37,11 +45,38 @@ export class PrometheusCounterTransactionManager implements TransactionObservabi
const transactionName = this.transactionNameByKey.get(uniqueTransactionKey)
if (!transactionName) return

this.counter?.inc({ status: wasSuccessful ? 'success' : 'failed', transactionName })
const labels: Record<'status' | 'transactionName', string> = {
status: wasSuccessful ? 'success' : 'failed',
transactionName,
}
let labelsWithCustom: Record<'status' | 'transactionName' | CustomLabels, string> | undefined
if (this.customLabelsByKey.has(uniqueTransactionKey)) {
const customLabels = this.customLabelsByKey.get(uniqueTransactionKey)
labelsWithCustom = {
...labels,
// biome-ignore lint/style/noNonNullAssertion: we already checked the presence
...customLabels!,
}
}

this.counter?.inc(labelsWithCustom ?? labels)
this.transactionNameByKey.delete(uniqueTransactionKey)
this.customLabelsByKey.delete(uniqueTransactionKey)
}

// Prometheus labels are the way Prometheus handles custom attributes
addCustomAttributes(
uniqueTransactionKey: string,
atts: { [p: string]: string | number | boolean },
): void {
const transactionName = this.transactionNameByKey.get(uniqueTransactionKey)
if (!transactionName) return

// @ts-expect-error We only enforce types lightly here. If this ever causes us problem, we can start doing runtime validation here, but it seems to be an overkill for now.
this.customLabelsByKey.set(uniqueTransactionKey, atts)
}

private registerMetric(appMetrics?: IFastifyMetrics) {
private registerMetric(appMetrics?: IFastifyMetrics, customLabels: string[] = []) {
if (!appMetrics) return

const existingMetric: Counter | undefined = appMetrics.client.register.getSingleMetric(
Expand All @@ -53,7 +88,7 @@ export class PrometheusCounterTransactionManager implements TransactionObservabi
return new appMetrics.client.Counter({
name: this.metricName,
help: this.metricDescription,
labelNames: ['status', 'transactionName'],
labelNames: ['status', 'transactionName', ...customLabels],
})
}
}
28 changes: 14 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"@amplitude/analytics-node": "^1.3.6",
"@bugsnag/js": "^8.1.2",
"@lokalise/error-utils": "^2.2.0",
"@splitsoftware/splitio": "^11.0.1",
"@splitsoftware/splitio": "^11.0.3",
"@supercharge/promise-pool": "^3.2.0",
"fastify-metrics": "^12.1.0",
"fastify-plugin": "^5.0.1",
Expand All @@ -47,7 +47,7 @@
"peerDependencies": {
"@fastify/jwt": "^9.0.1",
"@lokalise/background-jobs-common": ">=8.0.0",
"@lokalise/node-core": ">=12.0.0",
"@lokalise/node-core": ">=13.3.0",
"bullmq": "^5.19.0",
"fastify": "^5.0.0",
"ioredis": "^5.4.1",
Expand All @@ -59,24 +59,24 @@
"@amplitude/analytics-types": "^2.8.4",
"@biomejs/biome": "^1.9.4",
"@lokalise/backend-http-client": "^3.0.0",
"@lokalise/background-jobs-common": "^9.0.0",
"@lokalise/background-jobs-common": "^9.0.3",
"@lokalise/biome-config": "^1.5.0",
"@lokalise/node-core": "^13.1.0",
"@types/newrelic": "^9.14.5",
"@types/node": "^22.9.0",
"@vitest/coverage-v8": "^2.1.4",
"@lokalise/node-core": "^13.3.0",
"@types/newrelic": "^9.14.6",
"@types/node": "^22.10.2",
"@vitest/coverage-v8": "^2.1.8",
"auto-changelog": "^2.4.0",
"bullmq": "^5.21.1",
"fastify": "^5.1.0",
"bullmq": "^5.34.2",
"fastify": "^5.2.0",
"fastify-type-provider-zod": "^4.0.2",
"ioredis": "^5.4.1",
"newrelic": "12.8.1",
"pino": "^9.4.0",
"newrelic": "12.8.2",
"pino": "^9.5.0",
"pino-pretty": "^13.0.0",
"shx": "^0.3.4",
"typescript": "^5.6.3",
"vitest": "^2.1.4",
"zod": "^3.23.8"
"typescript": "^5.7.2",
"vitest": "^2.1.8",
"zod": "^3.24.1"
},
"engines": {
"node": ">=20"
Expand Down

0 comments on commit fa50606

Please sign in to comment.