Skip to content

Commit

Permalink
[Feat][DEVX-364] Record Custom Metric (Response Time) (#861)
Browse files Browse the repository at this point in the history
* feat(custom-metric): record custom metric

- record and send custom metrics to respective APMs

* chore(tests): fix tests

- fix tests for old (legacy) apm middleware

* chore(ts-sdk-apm): improve package

- improve typescript types for ts-sdk-apm package
- add more custom metric example snippets to example directory
- add tests to validate functionality

* chore(release-changeset): release changeset

- add release changeset

* chore(ts-sdk-apm): add missing package

- add node-fetch dependency to apm package

* chore(custom-metrics): rebase and update branch

- rebase and update branch
- move test to integration tests directory
- update dependencies

* chore(ts-sdk-apm): implement feedback

- implement PR review feedback

* chore(ts-sdk-apm): implement feedback

- implement PR review feedback

* chore(ts-sdk-apm): implement feedback

- implement feedback
ajimae authored Jan 15, 2025
1 parent a08c782 commit e8a0e92
Showing 26 changed files with 2,044 additions and 714 deletions.
5 changes: 5 additions & 0 deletions .changeset/strong-poets-retire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@commercetools/ts-sdk-apm': minor
---

Add custom metric
3 changes: 3 additions & 0 deletions .jest/setEnvVars.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
process.env.NEW_RELIC_ENABLED = false
process.env.NEW_RELIC_APP_NAME = 'newrelic-app-name'
process.env.NEW_RELIC_LICENSE_KEY = 'newrelic-license-key'
2 changes: 1 addition & 1 deletion examples/newrelic-express-apm/README.md
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@ Example to show how the Newrelic APM can be used in the TypeScript SDK.
1. After the installation completes, run `yarn start` to start the development server.
2. Curl or send a request using any HTTP client to the following endpoints:

```http
```
- GET https://localhost:9000/project
- GET https://localhost:9000/customers
- GET https://localhost:9000/products
6 changes: 3 additions & 3 deletions examples/newrelic-express-apm/package.json
Original file line number Diff line number Diff line change
@@ -7,9 +7,9 @@
"author": "chukwuemeka ajima",
"private": false,
"dependencies": {
"@commercetools/platform-sdk": "^4.11.0",
"@commercetools/sdk-client-v2": "^2.2.0",
"@commercetools/ts-sdk-apm": "^1.0.0",
"@commercetools/platform-sdk": "latest",
"@commercetools/ts-client": "latest",
"@commercetools/ts-sdk-apm": "latest",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.20.0",
5 changes: 3 additions & 2 deletions examples/newrelic-express-apm/src/app.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const { newrelic } = require('@commercetools/ts-sdk-apm')
const { config } = require('dotenv')
const express = require('express')
const cors = require('cors')
@@ -6,7 +7,6 @@ config()

const routes = require('./routes')
const request = require('./utils/request')
const agent = require('../agent')

const app = express()
const { NODE_ENV } = process.env
@@ -19,7 +19,8 @@ app.use(express.urlencoded({ extended: true }))

app.use(function (req, res, next) {
const total = count()
agent.recordMetric(`Commercetools/Client/Request/Total`, total)
newrelic.recordMetric(`Commercetools/Client/Request/Total`, total)
next()
})

// Global error handler
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const { newrelic } = require('@commercetools/ts-sdk-apm')
const ResponseHandler = require('../utils/response')

/**
@@ -10,8 +11,14 @@ class ProductController {
}

async getProducts(req, res) {
const start = performance.now()
const data = await this.productService.getProduct(req.body)

newrelic.recordMetric(
'GetProductsEndpoint/Response/Time/Total',
performance.now() - start
)

if (data.statusCode == 200) {
return ResponseHandler.successResponse(
res,
@@ -20,6 +27,7 @@ class ProductController {
data.body
)
}

return ResponseHandler.errorResponse(
res,
data.statusCode || data.body.statusCode,
2 changes: 1 addition & 1 deletion examples/newrelic-express-apm/src/routes/cart-discount.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const { Router } = require('express')
const { CartDiscountService } = require('../service')
const { CartDiscountController } = require('../controller')
const { apiRoot } = require('../sdk-v2/sdk')
const { apiRoot } = require('../sdk-v3/sdk')

const cartDiscountService = new CartDiscountService({ apiRoot })
const cartDiscountController = new CartDiscountController({
2 changes: 1 addition & 1 deletion examples/newrelic-express-apm/src/routes/cart.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const { Router } = require('express')
const { CartService } = require('../service')
const { CartController } = require('../controller')
const { apiRoot } = require('../sdk-v2/sdk')
const { apiRoot } = require('../sdk-v3/sdk')

const cartService = new CartService({ apiRoot })
const cartController = new CartController({ cartService })
2 changes: 1 addition & 1 deletion examples/newrelic-express-apm/src/routes/customer.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const { Router } = require('express')
const { CustomerService } = require('../service')
const { CustomerController } = require('../controller')
const { apiRoot } = require('../sdk-v2/sdk')
const { apiRoot } = require('../sdk-v3/sdk')

const customerService = new CustomerService({ apiRoot })
const customerController = new CustomerController({ customerService })
2 changes: 1 addition & 1 deletion examples/newrelic-express-apm/src/routes/product.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const { Router } = require('express')
const { ProductService } = require('../service')
const { ProductController } = require('../controller')
const { apiRoot } = require('../sdk-v2/sdk')
const { apiRoot } = require('../sdk-v3/sdk')

const productService = new ProductService({ apiRoot })
const productController = new ProductController({ productService })
2 changes: 1 addition & 1 deletion examples/newrelic-express-apm/src/routes/project.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const { Router } = require('express')
const { ProjectService } = require('../service')
const { ProjectController } = require('../controller')
const { apiRoot } = require('../sdk-v2/sdk')
const { apiRoot } = require('../sdk-v3/sdk')

const projectService = new ProjectService({ apiRoot })
const projectController = new ProjectController({ projectService })
49 changes: 49 additions & 0 deletions examples/newrelic-express-apm/src/sdk-v3/sdk.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
const { ClientBuilder } = require('@commercetools/sdk-client-v2')
const { createTelemetryMiddleware } = require('@commercetools/ts-sdk-apm')
const { createApiBuilderFromCtpClient } = require('@commercetools/platform-sdk')
const fetch = require('node-fetch')

const agent = require('../../agent')

const projectKey = process.env.CTP_PROJECT_KEY
const authMiddlewareOptions = {
host: 'https://auth.europe-west1.gcp.commercetools.com',
projectKey,
credentials: {
clientId: process.env.CTP_CLIENT_ID,
clientSecret: process.env.CTP_CLIENT_SECRET,
user: {
username: process.env.CTP_CLIENT_USERNAME,
password: process.env.CTP_CLIENT_PASSWORD,
},
},
scopes: [`manage_project:${projectKey}`],
httpClient: fetch,
}

const httpMiddlewareOptions = {
host: 'https://api.europe-west1.gcp.commercetools.com',
includeRequestInErrorResponse: false,
includeOriginalRequest: true,
httpClient: fetch,
}

// newrelic options
const telemetryOptions = {
createTelemetryMiddleware,
apm: () => agent,
}

const client = new ClientBuilder()
.withPasswordFlow(authMiddlewareOptions)
.withHttpMiddleware(httpMiddlewareOptions)
.withTelemetryMiddleware(telemetryOptions) // telemetry middleware
.build()

const apiRoot = createApiBuilderFromCtpClient(client).withProjectKey({
projectKey,
})

module.exports = {
apiRoot,
}
12 changes: 3 additions & 9 deletions examples/newrelic-express-apm/src/utils/response.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const agent = require('../../agent')
const { newrelic } = require('@commercetools/ts-sdk-apm')
/**
* @class Response
*
@@ -27,10 +27,7 @@ class ResponseHandler {
responseBody.data = data
}

agent.recordMetric(
`Commercetools/Client/Response/Success/${statusCode}`,
statusCode
)
newrelic.recordMetric(`Service/Response/Success/${statusCode}`, statusCode)

return response.status(statusCode).json({
...responseBody,
@@ -58,10 +55,7 @@ class ResponseHandler {
responseBody.data = data
}

agent.recordMetric(
`Commercetools/Client/Response/Error/${statusCode}`,
statusCode
)
newrelic.recordMetric(`Service/Response/Error/${statusCode}`, statusCode)

return response.status(statusCode).json({
...responseBody,
1,175 changes: 669 additions & 506 deletions examples/newrelic-express-apm/yarn.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -24,5 +24,6 @@ module.exports = {
]
: null,
].filter(Boolean),
setupFiles: ['<rootDir>/.jest/setEnvVars.js'],
setupFilesAfterEnv: ['jest-extended/all'],
}
178 changes: 178 additions & 0 deletions packages/platform-sdk/test/integration-tests/sdk-v3/telemetry.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import { ClientBuilder } from '@commercetools/ts-client'
import {
MiddlewareRequest,
OTelemetryMiddlewareOptions,
Next,
datadog,
newrelic,
} from '@commercetools/ts-sdk-apm'
import { createApiBuilderFromCtpClient } from '@commercetools/platform-sdk'

const time = () => performance.now()

// record for datadog
const recordDatadog = (
metric: number,
tags?: { [tag: string]: string | number }
): void => {
datadog
.init()
.dogstatsd.gauge(`Commercetools_Client_Response_Total`, metric, tags)
}

// record for newrelic
const recordNewrelic = (metric: number | newrelic.Metric): void => {
newrelic.recordMetric(`Commercetools/Client/Response/Total`, metric)
}

describe('custom metrics', () => {
const projectKey = process.env.CTP_PROJECT_KEY as string
const authMiddlewareOptions = {
host: 'https://auth.europe-west1.gcp.commercetools.com',
projectKey,
credentials: {
clientId: process.env.CTP_CLIENT_ID as string,
clientSecret: process.env.CTP_CLIENT_SECRET as string,
},
scopes: [`manage_project:${projectKey}`],
httpClient: fetch,
}

const httpMiddlewareOptions = {
host: 'https://api.europe-west1.gcp.commercetools.com',
httpClient: fetch,
}

it('should get request response time', async () => {
const _middleware = (options?: OTelemetryMiddlewareOptions) => {
return (next: Next) => {
return async (req: MiddlewareRequest) => {
const startTime = time()

const res = await next(req)

const endTime = time()
const responseTime = endTime - startTime
res['response_time'] = responseTime

// record stats
recordNewrelic(responseTime)
recordDatadog(responseTime)
return res
}
}
}

const telemetryOptions = {
userAgent: 'typescript-sdk-middleware-newrelic',
createTelemetryMiddleware: _middleware,
}

const client = new ClientBuilder()
.withTelemetryMiddleware(telemetryOptions)
.withAnonymousSessionFlow(authMiddlewareOptions)
.withHttpMiddleware(httpMiddlewareOptions)
.build()

const api = createApiBuilderFromCtpClient(client)
const result = await api.withProjectKey({ projectKey }).get().execute()

expect(typeof result).toEqual('object')
expect(result).toHaveProperty('response_time')
expect(typeof result['response_time']).toEqual('number')
})

it('should not record custom metric is option is not enabled', async () => {
const _middleware = (options?: OTelemetryMiddlewareOptions) => {
expect(options.customMetrics).toEqual(undefined)

return (next: Next) => {
return async (req: MiddlewareRequest) => {
const startTime = time()

const res = await next(req)

if (options?.customMetrics) {
const endTime = time()
const responseTime = endTime - startTime
res['response_time'] = responseTime

// simulate the actual `createTelemetryMiddleware` middleware
options.customMetrics.newrelic && recordNewrelic(responseTime)
options.customMetrics.datadog &&
recordDatadog(responseTime, { env: 'dev' })
}

return res
}
}
}

const telemetryOptions = {
userAgent: 'typescript-sdk-middleware-newrelic',
createTelemetryMiddleware: _middleware,
}

const client = new ClientBuilder()
.withTelemetryMiddleware(telemetryOptions)
.withAnonymousSessionFlow(authMiddlewareOptions)
.withHttpMiddleware(httpMiddlewareOptions)
.build()

const api = createApiBuilderFromCtpClient(client)
const result = await api.withProjectKey({ projectKey }).get().execute()

expect(typeof result).toEqual('object')
expect(result['response_time']).not.toBeDefined()
expect(typeof result['response_time']).toEqual('undefined')
})

it('should record custom metric is option is enabled', async () => {
const _middleware = (options?: OTelemetryMiddlewareOptions) => {
expect(typeof options.customMetrics).toEqual('object')
expect(options.customMetrics.newrelic).toEqual(true)

return (next: Next) => {
return async (req: MiddlewareRequest) => {
const startTime = time()

const res = await next(req)

if (options?.customMetrics) {
const endTime = time()
const responseTime = endTime - startTime
res['response_time'] = responseTime

// simulate the actual `createTelemetryMiddleware` middleware
options.customMetrics.newrelic && recordNewrelic(responseTime)
options.customMetrics.datadog &&
recordDatadog(responseTime, { env: 'dev' })
}

return res
}
}
}

const telemetryOptions = {
userAgent: 'typescript-sdk-middleware-newrelic',
createTelemetryMiddleware: _middleware,
customMetrics: {
newrelic: true,
},
}

const client = new ClientBuilder()
.withTelemetryMiddleware(telemetryOptions)
.withAnonymousSessionFlow(authMiddlewareOptions)
.withHttpMiddleware(httpMiddlewareOptions)
.build()

const api = createApiBuilderFromCtpClient(client)
const result = await api.withProjectKey({ projectKey }).get().execute()

expect(typeof result).toEqual('object')
expect(result).toHaveProperty('response_time')
expect(typeof result['response_time']).toEqual('number')
})
})
5 changes: 5 additions & 0 deletions packages/ts-sdk-apm/package.json
Original file line number Diff line number Diff line change
@@ -12,6 +12,8 @@
"@opentelemetry/auto-instrumentations-node": "^0.55.0",
"@opentelemetry/exporter-metrics-otlp-http": "^0.57.0",
"@opentelemetry/sdk-node": "^0.57.0",
"dd-trace": "^5.31.0",
"newrelic": "^12.10.0",
"uuid": "11.0.4"
},
"publishConfig": {
@@ -51,5 +53,8 @@
"organize_imports": "find src -type f -name '*.ts' | xargs organize-imports-cli",
"postbuild": "yarn organize_imports",
"post_process_generate": "yarn organize_imports"
},
"devDependencies": {
"@types/newrelic": "^9.14.6"
}
}
58 changes: 58 additions & 0 deletions packages/ts-sdk-apm/src/apm-legacy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type {
MiddlewareLegacy,
MiddlewareRequestLegacy,
MiddlewareResponseLegacy,
NextLegacy,
Next,
OTelemetryMiddlewareOptions,
} from '../types/types'

/**
* default newrelic APM and
* opentelemetry tracer modules
*/
const defaultOptions = {
/**
* if this is to be used with newrelic, then
* pass this (apm) as an option in the `createTelemetryMiddleware`
* function e.g createTelemetryMiddleware({ apm: () => require('newrelic'), ... })
* Note: don't forget to install newrelic agent in your project `yarn add newrelic`
*/
apm: () => {},
tracer: () => require('../opentelemetry'),
}

/**
* @deprecated use new `createTelemetryMiddleware`
* @param options
* @returns
*/
export default function createTelemetryMiddleware(
options: OTelemetryMiddlewareOptions
): MiddlewareLegacy {
// trace
function trace() {
// validate apm and tracer
if (!(options?.apm && typeof options.apm == 'function')) {
options.apm = defaultOptions.apm
}

if (!(options?.tracer && typeof options.tracer == 'function')) {
options.tracer = defaultOptions.tracer
}

options.apm()
options.tracer()
}

trace() // expose tracing modules
return (next: NextLegacy): NextLegacy =>
(request: MiddlewareRequestLegacy, response: MiddlewareResponseLegacy) => {
const nextRequest = {
...request,
...options,
}

next(nextRequest, response)
}
}
21 changes: 17 additions & 4 deletions packages/ts-sdk-apm/src/apm.ts
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ import type {
Next,
OTelemetryMiddlewareOptions,
} from '../types/types'
import { recordNewrelic, recordDatadog, time } from './helpers'

/**
* default newrelic APM and
@@ -15,9 +16,8 @@ const defaultOptions = {
* if this is to be used with newrelic, then
* pass this (apm) as an option in the `createTelemetryMiddleware`
* function e.g createTelemetryMiddleware({ apm: () => require('newrelic'), ... })
* Note: don't forget to install newrelic agent in your project `yarn add newrelic`
*/
apm: () => {},
apm: () => require('newrelic'),
tracer: () => require('../opentelemetry'),
}

@@ -41,12 +41,25 @@ export default function createTelemetryMiddleware(

trace() // expose tracing modules
return (next: Next): Next =>
(request: MiddlewareRequest, response: MiddlewareResponse) => {
async (request: MiddlewareRequest) => {
// get start (high resolution milliseconds) timestamp
const start = time()

const nextRequest = {
...request,
...options,
}

next(nextRequest, response)
const response: MiddlewareResponse = await next(nextRequest)
const response_time = time() - start

// send `response_time` to APM platforms
if (options?.customMetrics) {
options.customMetrics.newrelic && recordNewrelic(response_time)
options.customMetrics.datadog &&
recordDatadog(response_time, { env: 'dev' })
}

return response
}
}
18 changes: 18 additions & 0 deletions packages/ts-sdk-apm/src/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { newrelic, datadog } from './lib/agents'

export const time = () => performance.now()

// record for datadog
export const recordDatadog = (
metric: number,
tags?: { [tag: string]: string | number }
): void => {
datadog
.init()
.dogstatsd.gauge(`Commercetools_Client_Response_Total`, metric, tags)
}

// record for newrelic
export const recordNewrelic = (metric: number | newrelic.Metric): void => {
newrelic.recordMetric(`Commercetools/Client/Response/Total`, metric)
}
2 changes: 2 additions & 0 deletions packages/ts-sdk-apm/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export * from '../types/types.d'
export { default as createTelemetryMiddleware } from './apm'
export { default as createTelemetryMiddlewareLegacy } from './apm-legacy'
export * from './lib/agents';
4 changes: 4 additions & 0 deletions packages/ts-sdk-apm/src/lib/agents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import datadog from 'dd-trace'
import newrelic from 'newrelic'

export { datadog, newrelic }
91 changes: 91 additions & 0 deletions packages/ts-sdk-apm/test/apm.test/apm-legacy.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import {
type MiddlewareRequestLegacy,
type NextLegacy,
createTelemetryMiddlewareLegacy,
} from '../../src'

jest.mock('../../opentelemetry', () => {})

function createTestRequest(options): MiddlewareRequestLegacy {
return {
uri: '',
method: 'GET',
body: null,
headers: {},
...options,
}
}

function createTestResponse(options) {
return {
...options,
}
}

describe('apm', () => {
const request = createTestRequest({
headers: {
Authorization: '123',
},
})

// lagacy tests
describe('apm test - null tracer configurations', () => {
const response = createTestResponse({})
const telemetryMiddleware = createTelemetryMiddlewareLegacy({
apm: null as any,
tracer: null as any,
})

test('retains existing request (headers)', async () => {
const next = (req: MiddlewareRequestLegacy) => {
expect(req.headers?.Authorization).toBe('123')
}

await telemetryMiddleware(next)(request, response)
})

test('should use default apm and tracing configurations', async () => {
const next = (req: MiddlewareRequestLegacy) => {
expect(req['apm']).toBeTruthy()
expect(req['tracer']).toBeTruthy()

expect(typeof req['apm']).toEqual('function')
expect(typeof req['tracer']).toEqual('function')
}

await telemetryMiddleware(next)(request, response)
})
})

describe('apm test - non-null tracer configuration', () => {
const options = {
apm: jest.fn(() => ({ a: 'apm-module' })),
tracer: jest.fn(() => ({ t: 'tracer-module' })),
}

const response = createTestResponse({})
const telemetryMiddleware = createTelemetryMiddlewareLegacy(options)

test('adds an `apm` and `tracer` properties in request object', async () => {
const next = (req: MiddlewareRequestLegacy) => {
expect(req['apm']).toBeTruthy()
expect(req['tracer']).toBeTruthy()

expect(typeof req['apm']).toEqual('function')
expect(typeof req['tracer']).toEqual('function')

expect(req['apm']()).toEqual({ a: 'apm-module' })
expect(req['tracer']()).toEqual({ t: 'tracer-module' })

expect(req['apm']).toHaveReturned()
expect(req['tracer']).toHaveReturned()

expect(options.apm).toHaveBeenCalled()
expect(options.tracer).toHaveBeenCalled
}

await telemetryMiddleware(next)(request, response)
})
})
})
47 changes: 26 additions & 21 deletions packages/ts-sdk-apm/test/apm.test/apm.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import {
type MiddlewareRequest,
type MiddlewareResponse,
createTelemetryMiddleware,
} from '../../src'
import { ClientBuilder } from '@commercetools/ts-client'
import { type MiddlewareRequest, createTelemetryMiddleware } from '../../src'

jest.mock('../../opentelemetry', () => {})

@@ -16,7 +13,7 @@ function createTestRequest(options): MiddlewareRequest {
}
}

function createTestResponse(options): MiddlewareResponse {
function createTestResponse(options) {
return {
...options,
}
@@ -32,28 +29,34 @@ describe('apm', () => {
describe('apm test - null tracer configurations', () => {
const response = createTestResponse({})
const telemetryMiddleware = createTelemetryMiddleware({
apm: null,
tracer: null,
apm: null as any,
tracer: null as any,
})

const next = (req: MiddlewareRequest) => {
test('retains existing request (headers)', () => {
test('retains existing request (headers)', async () => {
const next = (req: MiddlewareRequest) => {
expect(req.headers?.Authorization).toBe('123')
})
return response
}

test('should use default apm and tracing configurations', () => {
await telemetryMiddleware(next)(request)
})

test('should use default apm and tracing configurations', async () => {
const next = (req: MiddlewareRequest) => {
expect(req['apm']).toBeTruthy()
expect(req['tracer']).toBeTruthy()

expect(typeof req['apm']).toEqual('function')
expect(typeof req['tracer']).toEqual('function')
})
}
return response
}

telemetryMiddleware(next)(request, response)
await telemetryMiddleware(next)(request)
})
})

describe('should use provided apm and tracer configurations', () => {
describe('apm test - non-null tracer configuration', () => {
const options = {
apm: jest.fn(() => ({ a: 'apm-module' })),
tracer: jest.fn(() => ({ t: 'tracer-module' })),
@@ -62,8 +65,8 @@ describe('apm', () => {
const response = createTestResponse({})
const telemetryMiddleware = createTelemetryMiddleware(options)

const next = (req: MiddlewareRequest) => {
test('adds an `apm` and `tracer` properties in request object', () => {
test('adds an `apm` and `tracer` properties in request object', () => {
const next = async (req: MiddlewareRequest) => {
expect(req['apm']).toBeTruthy()
expect(req['tracer']).toBeTruthy()

@@ -78,9 +81,11 @@ describe('apm', () => {

expect(options.apm).toHaveBeenCalled()
expect(options.tracer).toHaveBeenCalled
})
}

telemetryMiddleware(next)(request, response)
return response
}

telemetryMiddleware(next)(request)
})
})
})
147 changes: 92 additions & 55 deletions packages/ts-sdk-apm/types/types.d.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,57 @@
export type JsonObject<T = unknown> = { [key: string]: T }
export type MiddlewareRequest = ClientRequest
export type Middleware = (next: Dispatch) => Dispatch

export type Dispatch = (
request: MiddlewareRequest,
response: MiddlewareResponse
) => unknown
export type Middleware = (next: Next) => (request: MiddlewareRequest) => Promise<MiddlewareResponse>

export type Next = (
request: MiddlewareRequest,
response: MiddlewareResponse
) => unknown
// export type Dispatch = (next: Next) => (request: MiddlewareRequest) => Promise<MiddlewareResponse>
export type Next = (request: MiddlewareRequest) => Promise<MiddlewareResponse>

export type MiddlewareResponse = {
resolve(response: JsonObject): void
reject(error: JsonObject): void
body?: JsonObject
error?: HttpErrorType
statusCode: number
headers?: JsonObject<string>
request?: JsonObject
export type MiddlewareResponse<T = unknown> = {
resolve: Function;
reject: Function;
body: T;
error?: HttpErrorType;
statusCode: number;
headers?: Record<string, any>
originalRequest?: MiddlewareRequest;
}

export interface ClientRequest {
baseUri?: string
uri?: string
headers?: VariableMap
headers?: Record<string, any>
method: MethodType
uriTemplate?: string
pathVariables?: VariableMap
queryParams?: VariableMap
body?: any,
body?: Record<string, any> | string | Uint8Array;
response?: ClientResponse
resolve?: Function;
reject?: Function;
[key: string]: any
}

export type ClientResponse<T = any> = {
body: T
code?: number
statusCode?: number
headers?: Record<string, any>
error?: HttpErrorType
retryCount?: number
}

export type HttpErrorType = {
name: string
name?: string
message: string
code: number
status: number
code?: number
status?: number
method: MethodType
statusCode: number
originalRequest: ClientRequest
body?: JsonObject
originalRequest?: ClientRequest
body: JsonObject
retryCount?: number
headers?: JsonObject<string>
headers?: Record<string, any>
[key: string]: any
}

export type VariableMap = {
@@ -58,47 +67,75 @@ export type QueryParam =
| boolean[]
| undefined

export type MethodType =
| 'ACL'
| 'BIND'
| 'CHECKOUT'
| 'CONNECT'
| 'COPY'
| 'DELETE'
export type MethodType =
| 'GET'
| 'HEAD'
| 'LINK'
| 'LOCK'
| 'M-SEARCH'
| 'MERGE'
| 'MKACTIVITY'
| 'MKCALENDAR'
| 'MKCOL'
| 'MOVE'
| 'NOTIFY'
| 'OPTIONS'
| 'PATCH'
| 'POST'
| 'PROPFIND'
| 'PROPPATCH'
| 'PURGE'
| 'PUT'
| 'REBIND'
| 'REPORT'
| 'SEARCH'
| 'SOURCE'
| 'SUBSCRIBE'
| 'PATCH'
| 'DELETE'
| 'CONNECT'
| 'OPTIONS'
| 'TRACE'
| 'UNBIND'
| 'UNLINK'
| 'UNLOCK'
| 'UNSUBSCRIBE'

export type CustomMetric = {
newrelic?: boolean;
datadog?: boolean;
}

export type TelemetryMiddlewareOptions = {
apm?: Function;
tracer?: Function;
userAgent?: string;
customMetrics?: CustomMetric;
createTelemetryMiddleware: (options?: OTelemetryMiddlewareOptions) => Middleware
}

export type OTelemetryMiddlewareOptions = Omit<TelemetryMiddlewareOptions, 'createTelemetryMiddleware'>


// LEGACY TYPES
/**
* @deprecated
*/
export type NextLegacy = (
request: MiddlewareRequestLegacy,
response: MiddlewareResponseLegacy
) => unknown

/**
* @deprecated
*/
export type MiddlewareResponseLegacy = {
resolve(response: JsonObject): void
reject(error: JsonObject): void
body?: JsonObject
error?: HttpErrorType
statusCode: number
headers?: JsonObject<string>
request?: JsonObject
}

/**
* @deprecated
*/
export interface ClientRequestLegacy {
baseUri?: string
uri?: string
headers?: VariableMap
method: MethodType
uriTemplate?: string
pathVariables?: VariableMap
queryParams?: VariableMap
body?: any,
}

/**
* @deprecated
*/
export type MiddlewareLegacy = (next: NextLegacy) => NextLegacy

/**
* @deprecated
*/
export type MiddlewareRequestLegacy = ClientRequestLegacy
911 changes: 803 additions & 108 deletions yarn.lock

Large diffs are not rendered by default.

0 comments on commit e8a0e92

Please sign in to comment.