Skip to content

Commit 49d7686

Browse files
committed
ref(serverless): Convert GoogleCloudHttp to function
1 parent 990afcf commit 49d7686

File tree

3 files changed

+77
-52
lines changed

3 files changed

+77
-52
lines changed

packages/serverless/src/gcpfunction/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
import type { Integration, Options, SdkMetadata } from '@sentry/types';
99

1010
import { googleCloudGrpcIntegration } from '../google-cloud-grpc';
11-
import { GoogleCloudHttp } from '../google-cloud-http';
11+
import { googleCloudHttpIntegration } from '../google-cloud-http';
1212

1313
export * from './http';
1414
export * from './events';
@@ -18,15 +18,15 @@ export * from './cloud_events';
1818
export const defaultIntegrations: Integration[] = [
1919
// eslint-disable-next-line deprecation/deprecation
2020
...defaultNodeIntegrations,
21-
new GoogleCloudHttp({ optional: true }), // We mark this integration optional since '@google-cloud/common' module could be missing.
21+
googleCloudHttpIntegration({ optional: true }), // We mark this integration optional since '@google-cloud/common' module could be missing.
2222
googleCloudGrpcIntegration({ optional: true }), // We mark this integration optional since 'google-gax' module could be missing.
2323
];
2424

2525
/** Get the default integrations for the GCP SDK. */
2626
export function getDefaultIntegrations(options: Options): Integration[] {
2727
return [
2828
...getDefaultNodeIntegrations(options),
29-
new GoogleCloudHttp({ optional: true }), // We mark this integration optional since '@google-cloud/common' module could be missing.
29+
googleCloudHttpIntegration({ optional: true }), // We mark this integration optional since '@google-cloud/common' module could be missing.
3030
googleCloudGrpcIntegration({ optional: true }), // We mark this integration optional since 'google-gax' module could be missing.
3131
];
3232
}

packages/serverless/src/google-cloud-http.ts

Lines changed: 43 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
// '@google-cloud/common' import is expected to be type-only so it's erased in the final .js file.
2-
// When TypeScript compiler is upgraded, use `import type` syntax to explicitly assert that we don't want to load a module here.
31
import type * as common from '@google-cloud/common';
2+
import { convertIntegrationFnToClass, defineIntegration, getClient } from '@sentry/core';
43
import { startInactiveSpan } from '@sentry/node';
5-
import type { Integration } from '@sentry/types';
4+
import type { Client, Integration, IntegrationClass, IntegrationFn } from '@sentry/types';
65
import { fill } from '@sentry/utils';
76

87
type RequestOptions = common.DecorateRequestOptions;
@@ -12,51 +11,56 @@ interface RequestFunction extends CallableFunction {
1211
(reqOpts: RequestOptions, callback: ResponseCallback): void;
1312
}
1413

15-
/** Google Cloud Platform service requests tracking for RESTful APIs */
16-
export class GoogleCloudHttp implements Integration {
17-
/**
18-
* @inheritDoc
19-
*/
20-
public static id: string = 'GoogleCloudHttp';
14+
const INTEGRATION_NAME = 'GoogleCloudHttp';
2115

22-
/**
23-
* @inheritDoc
24-
*/
25-
public name: string;
16+
const SETUP_CLIENTS = new WeakMap<Client, boolean>();
2617

27-
private readonly _optional: boolean;
28-
29-
public constructor(options: { optional?: boolean } = {}) {
30-
this.name = GoogleCloudHttp.id;
18+
const _googleCloudHttpIntegration = ((options: { optional?: boolean } = {}) => {
19+
const optional = options.optional || false;
20+
return {
21+
name: INTEGRATION_NAME,
22+
setupOnce() {
23+
try {
24+
// eslint-disable-next-line @typescript-eslint/no-var-requires
25+
const commonModule = require('@google-cloud/common') as typeof common;
26+
fill(commonModule.Service.prototype, 'request', wrapRequestFunction);
27+
} catch (e) {
28+
if (!optional) {
29+
throw e;
30+
}
31+
}
32+
},
33+
setup(client) {
34+
SETUP_CLIENTS.set(client, true);
35+
},
36+
};
37+
}) satisfies IntegrationFn;
3138

32-
this._optional = options.optional || false;
33-
}
39+
export const googleCloudHttpIntegration = defineIntegration(_googleCloudHttpIntegration);
3440

35-
/**
36-
* @inheritDoc
37-
*/
38-
public setupOnce(): void {
39-
try {
40-
// eslint-disable-next-line @typescript-eslint/no-var-requires
41-
const commonModule = require('@google-cloud/common') as typeof common;
42-
fill(commonModule.Service.prototype, 'request', wrapRequestFunction);
43-
} catch (e) {
44-
if (!this._optional) {
45-
throw e;
46-
}
47-
}
48-
}
49-
}
41+
/**
42+
* Google Cloud Platform service requests tracking for RESTful APIs.
43+
*
44+
* @deprecated Use `googleCloudHttpIntegration()` instead.
45+
*/
46+
// eslint-disable-next-line deprecation/deprecation
47+
export const GoogleCloudHttp = convertIntegrationFnToClass(
48+
INTEGRATION_NAME,
49+
googleCloudHttpIntegration,
50+
) as IntegrationClass<Integration>;
5051

5152
/** Returns a wrapped function that makes a request with tracing enabled */
5253
function wrapRequestFunction(orig: RequestFunction): RequestFunction {
5354
return function (this: common.Service, reqOpts: RequestOptions, callback: ResponseCallback): void {
5455
const httpMethod = reqOpts.method || 'GET';
55-
const span = startInactiveSpan({
56-
name: `${httpMethod} ${reqOpts.uri}`,
57-
op: `http.client.${identifyService(this.apiEndpoint)}`,
58-
origin: 'auto.http.serverless',
59-
});
56+
// Only create span if integration is active on client
57+
const span = SETUP_CLIENTS.has(getClient() as Client)
58+
? startInactiveSpan({
59+
name: `${httpMethod} ${reqOpts.uri}`,
60+
op: `http.client.${identifyService(this.apiEndpoint)}`,
61+
origin: 'auto.http.serverless',
62+
})
63+
: undefined;
6064
orig.call(this, reqOpts, (...args: Parameters<ResponseCallback>) => {
6165
if (span) {
6266
span.end();

packages/serverless/test/google-cloud-http.test.ts

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,45 @@
11
import * as fs from 'fs';
22
import * as path from 'path';
33
import { BigQuery } from '@google-cloud/bigquery';
4-
import * as SentryNode from '@sentry/node';
54
import * as nock from 'nock';
65

7-
import { GoogleCloudHttp } from '../src/google-cloud-http';
6+
import { NodeClient, createTransport, setCurrentClient } from '@sentry/node';
7+
import { googleCloudHttpIntegration } from '../src/google-cloud-http';
8+
9+
const mockSpanEnd = jest.fn();
10+
const mockStartInactiveSpan = jest.fn(spanArgs => ({ ...spanArgs }));
11+
12+
jest.mock('@sentry/node', () => {
13+
return {
14+
...jest.requireActual('@sentry/node'),
15+
startInactiveSpan: (ctx: unknown) => {
16+
mockStartInactiveSpan(ctx);
17+
return { end: mockSpanEnd };
18+
},
19+
};
20+
});
821

922
describe('GoogleCloudHttp tracing', () => {
10-
beforeAll(() => {
11-
new GoogleCloudHttp().setupOnce();
23+
const mockClient = new NodeClient({
24+
tracesSampleRate: 1.0,
25+
integrations: [],
26+
dsn: 'https://withAWSServices@domain/123',
27+
transport: () => createTransport({ recordDroppedEvent: () => undefined }, _ => Promise.resolve({})),
28+
stackParser: () => [],
1229
});
30+
31+
const integration = googleCloudHttpIntegration();
32+
mockClient.addIntegration(integration);
33+
1334
beforeEach(() => {
1435
nock('https://www.googleapis.com')
1536
.post('/oauth2/v4/token')
1637
.reply(200, '{"access_token":"a.b.c","expires_in":3599,"token_type":"Bearer"}');
38+
setCurrentClient(mockClient);
39+
mockSpanEnd.mockClear();
40+
mockStartInactiveSpan.mockClear();
1741
});
18-
afterEach(() => {
19-
// @ts-expect-error see "Why @ts-expect-error" note
20-
SentryNode.resetMocks();
21-
});
42+
2243
afterAll(() => {
2344
nock.restore();
2445
});
@@ -50,12 +71,12 @@ describe('GoogleCloudHttp tracing', () => {
5071
);
5172
const resp = await bigquery.query('SELECT true AS foo');
5273
expect(resp).toEqual([[{ foo: true }]]);
53-
expect(SentryNode.startInactiveSpan).toBeCalledWith({
74+
expect(mockStartInactiveSpan).toBeCalledWith({
5475
op: 'http.client.bigquery',
5576
origin: 'auto.http.serverless',
5677
name: 'POST /jobs',
5778
});
58-
expect(SentryNode.startInactiveSpan).toBeCalledWith({
79+
expect(mockStartInactiveSpan).toBeCalledWith({
5980
op: 'http.client.bigquery',
6081
origin: 'auto.http.serverless',
6182
name: expect.stringMatching(/^GET \/queries\/.+/),

0 commit comments

Comments
 (0)