Skip to content

Commit 4e79890

Browse files
authored
fix(serverless-api): add request url to API error logs (#241)
We wrap error events from the API/Got into a dedicated class. The URL helps support to debug issues but wasn't in the debug logs since this change. This commit adds the URL back fix #167
1 parent ad6ba06 commit 4e79890

File tree

2 files changed

+76
-7
lines changed

2 files changed

+76
-7
lines changed

packages/serverless-api/src/utils/__tests__/error.test.ts

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
import nock from 'nock';
22
import util from 'util';
3+
import { AccountSidConfig, UsernameConfig } from '../../../dist';
34
import { createGotClient } from '../../client';
5+
import {
6+
DEFAULT_TEST_CLIENT_CONFIG,
7+
DEFAULT_TEST_CLIENT_CONFIG_USERNAME_PASSWORD,
8+
} from '../../__fixtures__/base-fixtures';
49
import { ClientApiError } from '../error';
5-
import { DEFAULT_TEST_CLIENT_CONFIG } from '../../__fixtures__/base-fixtures';
610

711
describe('ClientApiError', () => {
812
let apiNock = nock('https://serverless.twilio.com');
9-
let config = DEFAULT_TEST_CLIENT_CONFIG;
13+
let config:
14+
| UsernameConfig
15+
| AccountSidConfig = DEFAULT_TEST_CLIENT_CONFIG_USERNAME_PASSWORD;
1016
let apiClient = createGotClient(config);
1117

1218
beforeEach(() => {
1319
apiNock = nock('https://serverless.twilio.com');
20+
config = DEFAULT_TEST_CLIENT_CONFIG_USERNAME_PASSWORD;
1421
apiClient = createGotClient(config);
1522
});
1623

@@ -40,12 +47,73 @@ describe('ClientApiError', () => {
4047
status: 400,
4148
});
4249

50+
config = DEFAULT_TEST_CLIENT_CONFIG;
51+
apiClient = createGotClient(config);
52+
53+
try {
54+
const resp = await apiClient.get('Services');
55+
} catch (err) {
56+
const newError = new ClientApiError(err);
57+
const stringVersion = util.inspect(newError);
58+
expect(stringVersion.includes(config.authToken)).toBe(false);
59+
}
60+
});
61+
62+
test('does not contain any password', async () => {
63+
const scope = apiNock.get('/v1/Services').reply(400, {
64+
code: 20001,
65+
message: 'Services are limited to less than or equal to 9000',
66+
more_info: 'https://www.twilio.com/docs/errors/20001',
67+
status: 400,
68+
});
69+
4370
try {
4471
const resp = await apiClient.get('Services');
4572
} catch (err) {
4673
const newError = new ClientApiError(err);
4774
const stringVersion = util.inspect(newError);
48-
expect(stringVersion).not.toContain(config.authToken);
75+
expect(stringVersion.includes((config as UsernameConfig).password)).toBe(
76+
false
77+
);
78+
}
79+
});
80+
81+
test('contains URL and status code for basic HTTP Errors', async () => {
82+
const scope = apiNock.get('/v1/Services/ZS1111').reply(404, {});
83+
84+
try {
85+
const resp = await apiClient.get('Services/ZS1111');
86+
} catch (err) {
87+
const newError = new ClientApiError(err);
88+
const expectedUrl = 'https://serverless.twilio.com/v1/Services/ZS1111';
89+
expect(newError.code).toEqual(404);
90+
expect(newError.url).toEqual(expectedUrl);
91+
92+
const stringVersion = util.inspect(newError);
93+
expect(stringVersion.includes(expectedUrl)).toBe(true);
94+
expect(stringVersion.includes('404')).toBe(true);
95+
}
96+
});
97+
98+
test('contains URL and API code for Twilio API Errrors', async () => {
99+
const scope = apiNock.get('/v1/Services/ZS1111').reply(400, {
100+
code: 20001,
101+
message: 'Services are limited to less than or equal to 9000',
102+
more_info: 'https://www.twilio.com/docs/errors/20001',
103+
status: 400,
104+
});
105+
106+
try {
107+
const resp = await apiClient.get('Services/ZS1111');
108+
} catch (err) {
109+
const newError = new ClientApiError(err);
110+
const expectedUrl = 'https://serverless.twilio.com/v1/Services/ZS1111';
111+
expect(newError.code).toEqual(20001);
112+
expect(newError.url).toEqual(expectedUrl);
113+
114+
const stringVersion = util.inspect(newError);
115+
expect(stringVersion.includes(expectedUrl)).toBe(true);
116+
expect(stringVersion.includes('20001')).toBe(true);
49117
}
50118
});
51119
});

packages/serverless-api/src/utils/error.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ function toTwilioApiError(response: unknown): TwilioApiError | undefined {
2222
* Explictly removes username and password from a URL.
2323
* @param unfilteredUrl any URL string
2424
*/
25-
function filterUrl(unfilteredUrl: string | undefined): string {
25+
export function filterUrl(unfilteredUrl: string | undefined): string {
2626
if (!unfilteredUrl) {
2727
return '';
2828
}
@@ -49,8 +49,9 @@ export function convertApiErrorsAndThrow(err: any): never {
4949
* An Error wrapper to provide more useful error information without exposing credentials
5050
*/
5151
export class ClientApiError extends Error {
52-
public details?: TwilioApiError & { request_url?: string };
52+
public details?: TwilioApiError;
5353
public code?: number | string;
54+
public url?: string;
5455

5556
constructor(requestError: RequestError) {
5657
const twilioApiErrorInfo = toTwilioApiError(requestError.response?.body);
@@ -59,13 +60,13 @@ export class ClientApiError extends Error {
5960
Error.captureStackTrace(this, this.constructor);
6061
this.name = requestError.name;
6162
this.message = message;
62-
this.code = twilioApiErrorInfo?.code || requestError.code;
63+
this.code = twilioApiErrorInfo?.code || requestError.response?.statusCode;
64+
this.url = filterUrl(requestError.response?.requestUrl);
6365

6466
if (requestError.name === 'HTTPError' && twilioApiErrorInfo) {
6567
this.name = 'TwilioApiError';
6668
this.details = {
6769
...twilioApiErrorInfo,
68-
request_url: filterUrl(requestError.response?.requestUrl),
6970
};
7071
}
7172
}

0 commit comments

Comments
 (0)