-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
Showing
26 changed files
with
2,044 additions
and
714 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@commercetools/ts-sdk-apm': minor | ||
--- | ||
|
||
Add custom metric |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
178 changes: 178 additions & 0 deletions
178
packages/platform-sdk/test/integration-tests/sdk-v3/telemetry.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters