You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-07-31 15:24:23 +03:00
Improve logging of http requests to aid debugging (#3485)
* Simple request logging with status and duration * remove url params from logs * superfluous toString() * Add tests * Apply suggestions from code review * update snapshots * update log format * Apply suggestions from code review Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> * update snapshot --------- Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Co-authored-by: Richard van der Hoff <richard@matrix.org>
This commit is contained in:
@ -14,11 +14,14 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { mocked } from "jest-mock";
|
||||||
|
|
||||||
import { FetchHttpApi } from "../../../src/http-api/fetch";
|
import { FetchHttpApi } from "../../../src/http-api/fetch";
|
||||||
import { TypedEventEmitter } from "../../../src/models/typed-event-emitter";
|
import { TypedEventEmitter } from "../../../src/models/typed-event-emitter";
|
||||||
import { ClientPrefix, HttpApiEvent, HttpApiEventHandlerMap, IdentityPrefix, IHttpOpts, Method } from "../../../src";
|
import { ClientPrefix, HttpApiEvent, HttpApiEventHandlerMap, IdentityPrefix, IHttpOpts, Method } from "../../../src";
|
||||||
import { emitPromise } from "../../test-utils/test-utils";
|
import { emitPromise } from "../../test-utils/test-utils";
|
||||||
import { QueryDict } from "../../../src/utils";
|
import { defer, QueryDict } from "../../../src/utils";
|
||||||
|
import { logger } from "../../../src/logger";
|
||||||
|
|
||||||
describe("FetchHttpApi", () => {
|
describe("FetchHttpApi", () => {
|
||||||
const baseUrl = "http://baseUrl";
|
const baseUrl = "http://baseUrl";
|
||||||
@ -290,4 +293,30 @@ describe("FetchHttpApi", () => {
|
|||||||
runTests(baseUrlWithTrailingSlash);
|
runTests(baseUrlWithTrailingSlash);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should not log query parameters", async () => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
const deferred = defer<Response>();
|
||||||
|
const fetchFn = jest.fn().mockReturnValue(deferred.promise);
|
||||||
|
jest.spyOn(logger, "debug").mockImplementation(() => {});
|
||||||
|
const api = new FetchHttpApi(new TypedEventEmitter<any, any>(), { baseUrl, prefix, fetchFn });
|
||||||
|
const prom = api.requestOtherUrl(Method.Get, "https://server:8448/some/path#fragment?query=param");
|
||||||
|
jest.advanceTimersByTime(1234);
|
||||||
|
deferred.resolve({ ok: true, status: 200, text: () => Promise.resolve("RESPONSE") } as Response);
|
||||||
|
await prom;
|
||||||
|
expect(logger.debug).not.toHaveBeenCalledWith("fragment");
|
||||||
|
expect(logger.debug).not.toHaveBeenCalledWith("query");
|
||||||
|
expect(logger.debug).not.toHaveBeenCalledWith("param");
|
||||||
|
expect(logger.debug).toHaveBeenCalledTimes(2);
|
||||||
|
expect(mocked(logger.debug).mock.calls[0]).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
"FetchHttpApi: --> GET https://server:8448/some/path",
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
expect(mocked(logger.debug).mock.calls[1]).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
"FetchHttpApi: <-- GET https://server:8448/some/path [1234ms 200]",
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -25,6 +25,7 @@ import { ConnectionError, MatrixError } from "./errors";
|
|||||||
import { HttpApiEvent, HttpApiEventHandlerMap, IHttpOpts, IRequestOpts } from "./interface";
|
import { HttpApiEvent, HttpApiEventHandlerMap, IHttpOpts, IRequestOpts } from "./interface";
|
||||||
import { anySignal, parseErrorResponse, timeoutSignal } from "./utils";
|
import { anySignal, parseErrorResponse, timeoutSignal } from "./utils";
|
||||||
import { QueryDict } from "../utils";
|
import { QueryDict } from "../utils";
|
||||||
|
import { logger } from "../logger";
|
||||||
|
|
||||||
type Body = Record<string, any> | BodyInit;
|
type Body = Record<string, any> | BodyInit;
|
||||||
|
|
||||||
@ -225,6 +226,9 @@ export class FetchHttpApi<O extends IHttpOpts> {
|
|||||||
body?: Body,
|
body?: Body,
|
||||||
opts: Pick<IRequestOpts, "headers" | "json" | "localTimeoutMs" | "keepAlive" | "abortSignal"> = {},
|
opts: Pick<IRequestOpts, "headers" | "json" | "localTimeoutMs" | "keepAlive" | "abortSignal"> = {},
|
||||||
): Promise<ResponseType<T, O>> {
|
): Promise<ResponseType<T, O>> {
|
||||||
|
const urlForLogs = this.clearUrlParamsForLogs(url);
|
||||||
|
logger.debug(`FetchHttpApi: --> ${method} ${urlForLogs}`);
|
||||||
|
|
||||||
const headers = Object.assign({}, opts.headers || {});
|
const headers = Object.assign({}, opts.headers || {});
|
||||||
const json = opts.json ?? true;
|
const json = opts.json ?? true;
|
||||||
// We can't use getPrototypeOf here as objects made in other contexts e.g. over postMessage won't have same ref
|
// We can't use getPrototypeOf here as objects made in other contexts e.g. over postMessage won't have same ref
|
||||||
@ -260,6 +264,7 @@ export class FetchHttpApi<O extends IHttpOpts> {
|
|||||||
const { signal, cleanup } = anySignal(signals);
|
const { signal, cleanup } = anySignal(signals);
|
||||||
|
|
||||||
let res: Response;
|
let res: Response;
|
||||||
|
const start = Date.now();
|
||||||
try {
|
try {
|
||||||
res = await this.fetch(url, {
|
res = await this.fetch(url, {
|
||||||
signal,
|
signal,
|
||||||
@ -274,7 +279,10 @@ export class FetchHttpApi<O extends IHttpOpts> {
|
|||||||
credentials: "omit", // we send credentials via headers
|
credentials: "omit", // we send credentials via headers
|
||||||
keepalive: keepAlive,
|
keepalive: keepAlive,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
logger.debug(`FetchHttpApi: <-- ${method} ${urlForLogs} [${Date.now() - start}ms ${res.status}]`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
logger.debug(`FetchHttpApi: <-- ${method} ${urlForLogs} [${Date.now() - start}ms ${e}]`);
|
||||||
if ((<Error>e).name === "AbortError") {
|
if ((<Error>e).name === "AbortError") {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
@ -293,6 +301,22 @@ export class FetchHttpApi<O extends IHttpOpts> {
|
|||||||
return res as ResponseType<T, O>;
|
return res as ResponseType<T, O>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private clearUrlParamsForLogs(url: URL | string): string {
|
||||||
|
try {
|
||||||
|
let asUrl: URL;
|
||||||
|
if (typeof url === "string") {
|
||||||
|
asUrl = new URL(url);
|
||||||
|
} else {
|
||||||
|
asUrl = url;
|
||||||
|
}
|
||||||
|
// get just the path to remove any potential url param that could have
|
||||||
|
// some potential secrets
|
||||||
|
return asUrl.origin + asUrl.pathname;
|
||||||
|
} catch (error) {
|
||||||
|
// defensive coding for malformed url
|
||||||
|
return "??";
|
||||||
|
}
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Form and return a homeserver request URL based on the given path params and prefix.
|
* Form and return a homeserver request URL based on the given path params and prefix.
|
||||||
* @param path - The HTTP path <b>after</b> the supplied prefix e.g. "/createRoom".
|
* @param path - The HTTP path <b>after</b> the supplied prefix e.g. "/createRoom".
|
||||||
|
Reference in New Issue
Block a user