diff --git a/libraries/fetch-utils/index.js b/libraries/fetch-utils/index.js index 60e8180c7d..57cce2ce85 100644 --- a/libraries/fetch-utils/index.js +++ b/libraries/fetch-utils/index.js @@ -5,6 +5,12 @@ const fetch = require('node-fetch') const http = require('node:http') const https = require('node:https') +let logger + +function setLogger(loggerInstance) { + logger = loggerInstance +} + /** * @import { Response } from 'node-fetch' */ @@ -23,7 +29,7 @@ async function fetchJson(url, opts = {}) { } async function fetchJsonWithResponse(url, opts = {}) { - const { fetchOpts, detachSignal } = parseOpts(opts) + const { fetchOpts, detachSignal } = parseOpts(opts, url) fetchOpts.headers = fetchOpts.headers ?? {} fetchOpts.headers.Accept = fetchOpts.headers.Accept ?? 'application/json' @@ -53,7 +59,7 @@ async function fetchStream(url, opts = {}) { } async function fetchStreamWithResponse(url, opts = {}) { - const { fetchOpts, abortController, detachSignal } = parseOpts(opts) + const { fetchOpts, abortController, detachSignal } = parseOpts(opts, url) const response = await performRequest(url, fetchOpts, detachSignal) if (!response.ok) { @@ -76,7 +82,7 @@ async function fetchStreamWithResponse(url, opts = {}) { * @throws {RequestFailedError} if the response has a failure status code */ async function fetchNothing(url, opts = {}) { - const { fetchOpts, detachSignal } = parseOpts(opts) + const { fetchOpts, detachSignal } = parseOpts(opts, url) const response = await performRequest(url, fetchOpts, detachSignal) if (!response.ok) { const body = await maybeGetResponseBody(response) @@ -108,7 +114,7 @@ async function fetchRedirect(url, opts = {}) { * @throws {RequestFailedError} if the response has a non redirect status code or missing Location header */ async function fetchRedirectWithResponse(url, opts = {}) { - const { fetchOpts, detachSignal } = parseOpts(opts) + const { fetchOpts, detachSignal } = parseOpts(opts, url) fetchOpts.redirect = 'manual' const response = await performRequest(url, fetchOpts, detachSignal) if (response.status < 300 || response.status >= 400) { @@ -142,7 +148,7 @@ async function fetchString(url, opts = {}) { } async function fetchStringWithResponse(url, opts = {}) { - const { fetchOpts, detachSignal } = parseOpts(opts) + const { fetchOpts, detachSignal } = parseOpts(opts, url) const response = await performRequest(url, fetchOpts, detachSignal) if (!response.ok) { const body = await maybeGetResponseBody(response) @@ -167,7 +173,7 @@ class RequestFailedError extends OError { } } -function parseOpts(opts) { +function parseOpts(opts, url) { const fetchOpts = _.omit(opts, ['json', 'signal', 'basicAuth']) if (opts.json) { setupJsonBody(fetchOpts, opts.json) @@ -178,9 +184,21 @@ function parseOpts(opts) { const abortController = new AbortController() fetchOpts.signal = abortController.signal - let detachSignal = () => {} + let detachSignal if (opts.signal) { detachSignal = abortOnSignal(abortController, opts.signal) + } else { + const timeout = setTimeout(() => { + if (logger) { + logger.warn( + { url, method: opts.method ?? 'GET' }, + 'Fetch request did not complete within 120 seconds' + ) + } + }, 120000) + detachSignal = () => { + clearTimeout(timeout) + } } if (opts.body instanceof Readable) { abortOnDestroyedRequest(abortController, fetchOpts.body) @@ -323,4 +341,5 @@ module.exports = { ConnectTimeoutError, CustomHttpAgent, CustomHttpsAgent, + setLogger, } diff --git a/libraries/logger/logging-manager.js b/libraries/logger/logging-manager.js index 9fb4f28405..f379a66e9d 100644 --- a/libraries/logger/logging-manager.js +++ b/libraries/logger/logging-manager.js @@ -6,6 +6,7 @@ const { FileLogLevelChecker, GCEMetadataLogLevelChecker, } = require('./log-level-checker') +const { setLogger } = require('@overleaf/fetch-utils') const LoggingManager = { /** @@ -31,6 +32,7 @@ const LoggingManager = { }) this._setupRingBuffer() this._setupLogLevelChecker() + setLogger(this) return this }, diff --git a/libraries/logger/test/unit/logging-manager-tests.js b/libraries/logger/test/unit/logging-manager-tests.js index 9b571c3ce5..a7404490ea 100644 --- a/libraries/logger/test/unit/logging-manager-tests.js +++ b/libraries/logger/test/unit/logging-manager-tests.js @@ -43,9 +43,13 @@ describe('LoggingManager', function () { .stub() .returns(this.GCEMetadataLogLevelChecker), } + this.fetchUtils = { + setLogger: sinon.stub(), + } this.LoggingManager = SandboxedModule.require(MODULE_PATH, { requires: { bunyan: this.Bunyan, + '@overleaf/fetch-utils': this.fetchUtils, './log-level-checker': this.LogLevelChecker, }, })