Skip to content

Commit

Permalink
Instrument fetch
Browse files Browse the repository at this point in the history
  • Loading branch information
chtushar committed May 21, 2024
1 parent 6b0ded7 commit ac616c3
Show file tree
Hide file tree
Showing 9 changed files with 139 additions and 29 deletions.
18 changes: 9 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"@typescript-eslint/eslint-plugin": "^5.61.0",
"@typescript-eslint/parser": "^5.61.0",
"@vitest/ui": "^0.34.1",
"axios": "^1.4.0",
"axios": "^1.7.1",
"dotenv": "^16.3.1",
"eslint": "^8.44.0",
"eslint-config-semistandard": "^17.0.0",
Expand Down
17 changes: 15 additions & 2 deletions playground/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const express = require('express');
const {
OpenAPM,
setOpenAPMLabels,
metricClient
getMetricClient
} = require('../dist/src/index.js');
const mysql2 = require('mysql2');

Expand Down Expand Up @@ -41,7 +41,7 @@ const pool = mysql2.createPool(
'mysql://express-app:password@127.0.0.1/express' // If this throws an error, Change the db url to the one you're running on your machine locally or the testing instance you might have hosted.
);

const client = metricClient();
const client = getMetricClient();
const counter = new client.Counter({
name: 'cancelation_calls',
help: 'no. of times cancel operation is called'
Expand Down Expand Up @@ -77,6 +77,19 @@ app.post('/api/v2/product/search/:term', (req, res) => {
res.status(200).json({});
});

app.get('/cat-facts', async (req, res) => {
const catFacts = await fetch('https://cat-fact.herokuapp.com/facts', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
})
.then((res) => res.json())
.then((json) => json.all);

res.status(200).json(catFacts);
});

app.all('/api/v1/slug/:slug', (req, res) => {
setOpenAPMLabels({ slug: req.params.slug });
res.status(200).json({});
Expand Down
40 changes: 29 additions & 11 deletions src/OpenAPM.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ import type {
import type { NextFunction, Request, Response } from 'express';
import type { IncomingMessage, ServerResponse, Server } from 'http';

import { getHostIpAddress, getPackageJson, getSanitizedPath } from './utils';
import { getPackageJson, getSanitizedPath } from './utils';

import { instrumentExpress } from './clients/express';
import { instrumentMySQL } from './clients/mysql2';
import { instrumentNestFactory } from './clients/nestjs';
import { instrumentNextjs } from './clients/nextjs';
import { instrumentFetch } from './clients/fetch';

import { LevitateConfig, LevitateEvents } from './levitate/events';
import {
Expand All @@ -32,11 +33,7 @@ export type ExtractFromParams = {
mask: string;
};

export type DefaultLabels =
| 'environment'
| 'program'
| 'version'
| 'host';
export type DefaultLabels = 'environment' | 'program' | 'version' | 'host';

export interface OpenAPMOptions {
/**
Expand Down Expand Up @@ -142,6 +139,7 @@ export class OpenAPM extends LevitateEvents {
if (this.enabled) {
this.initiateMetricsRoute();
this.initiatePromClient();
this.instrumentThirdParty();
}
}

Expand Down Expand Up @@ -186,7 +184,7 @@ export class OpenAPM extends LevitateEvents {
environment: this.environment,
program: packageJson?.name ?? '',
version: packageJson?.version ?? '',
host: os.hostname(),
host: os.hostname(),
...this.defaultLabels
};

Expand Down Expand Up @@ -416,6 +414,30 @@ export class OpenAPM extends LevitateEvents {
return metrics.trim();
};

private instrumentThirdParty = () => {
// wrap(http, 'request', (original): typeof http.request => {
// return function (
// this: typeof original,
// ...args: Parameters<typeof original>
// ) {
// console.log('OpenAPM: http.request', args);
// return original.apply(this, args);
// } as typeof http.request;
// });

// wrap(https, 'request', (original): typeof http.request => {
// return function (
// this: typeof original,
// ...args: Parameters<typeof original>
// ) {
// console.log('OpenAPM: http.request', args);
// return original.apply(this, args);
// } as typeof http.request;
// });

instrumentFetch();
};

public instrument(moduleName: SupportedModules): boolean {
if (!this.enabled) {
return false;
Expand Down Expand Up @@ -469,8 +491,4 @@ export class OpenAPM extends LevitateEvents {
}
}

export function getMetricClient() {
return promClient;
}

export default OpenAPM;
55 changes: 55 additions & 0 deletions src/clients/fetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { getMetricClient } from '../get-metric-client';
import { wrap } from '../shimmer';

export const instrumentFetch = () => {
const client = getMetricClient();

const counter = new client.Counter({
name: 'fetch_requests_total',
help: 'Monitor the number of fetch requests made by the application to external services',
labelNames: ['method', 'status', 'origin']
});

const histogram = new client.Histogram({
name: 'fetch_duration_milliseconds',
help: 'Monitor the duration of fetch requests made by the application to external services',
labelNames: ['method', 'status', 'origin']
});

wrap(globalThis, 'fetch', (originalFetch): typeof globalThis.fetch => {
return async function (
this: typeof originalFetch,
...args: Parameters<typeof originalFetch>
) {
const start = process.hrtime.bigint();
const result = originalFetch.apply(this, args);

if (result instanceof Promise) {
await result;
}
const end = process.hrtime.bigint();
const duration = Number(end - start) / 1e6;

const origin = new URL(args[0].toString()).origin;

counter.inc({
method: args[1]?.method ?? 'GET',
// @ts-ignore
status: result.status as string,
origin: origin.toString()
});

histogram.observe(
{
method: args[1]?.method ?? 'GET',
// @ts-ignore
status: result.status as string,
origin: origin.toString()
},
duration
);

return result;
} as typeof fetch;
});
};
5 changes: 5 additions & 0 deletions src/get-metric-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import promClient from 'prom-client';

export function getMetricClient() {
return promClient;
}
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { default as OpenAPM, getMetricClient } from './OpenAPM';
export { getMetricClient } from './get-metric-client';
export { default as OpenAPM } from './OpenAPM';
export { setOpenAPMLabels } from './async-local-storage.http';
export type { OpenAPMOptions } from './OpenAPM';
12 changes: 8 additions & 4 deletions tests/express.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import express, { Express } from 'express';
import { test, expect, describe, beforeAll, afterAll } from 'vitest';

import OpenAPM, { getMetricClient } from '../src/OpenAPM';
import OpenAPM from '../src/OpenAPM';
import { getMetricClient } from '../src/get-metric-client';
import { addRoutes, makeRequest, sendTestRequests } from './utils';
import prom from 'prom-client';

Expand All @@ -28,7 +29,7 @@ describe('REDMiddleware', () => {
addRoutes(app);
app.listen(3002);

const out = await sendTestRequests(app, NUMBER_OF_REQUESTS);
// const out = await sendTestRequests(app, NUMBER_OF_REQUESTS);
});

afterAll(async () => {
Expand All @@ -38,8 +39,6 @@ describe('REDMiddleware', () => {
});
});

test;

test('Captures Counter Metrics - App', async () => {
const parsedData = await getMetrics();

Expand Down Expand Up @@ -142,4 +141,9 @@ describe('REDMiddleware', () => {
metricValues?.find((m) => m.labels.path === '/api/labels/:id')?.labels.id
).toBe('123');
});

test('Third Party call instrumentation', async () => {
const res = await makeRequest(app, '/cat-facts');
expect(true).toBe(true);
});
});
16 changes: 15 additions & 1 deletion tests/utils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import request from 'supertest';
import express from 'express';
import axios from 'axios';
import type { Express } from 'express';
import { setOpenAPMLabels } from '../src/async-local-storage.http';
import { getMetricClient } from '../src/OpenAPM';
import { getMetricClient } from '../src/get-metric-client';

export const addRoutes = (app: Express) => {
const router = express.Router();
Expand Down Expand Up @@ -41,6 +42,19 @@ export const addRoutes = (app: Express) => {
res.status(200).send(id);
});

app.get('/cat-facts', async (req, res) => {
const catFacts = await fetch('https://cat-fact.herokuapp.com/facts', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
})
.then((res) => res.json())
.then((json) => json.all);

res.status(200).send(catFacts);
});

return app;
};

Expand Down

0 comments on commit ac616c3

Please sign in to comment.