Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed

- log fetch now repects the log API request limit (1 per second)
- log fetch now passes transaction id and filter expression to backend log API

## [3.3.0] - 2025-06-17

## [3.2.0] - 2025-06-17
Expand Down
37 changes: 37 additions & 0 deletions src/api/BaseApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,43 @@ export function generateLogApi({

const request = createAxiosInstance(state, requestConfig);

// add a response interceptor for HTTP 429 errors from log API
request.interceptors.response.use(
(response) => {
// If the response is successful, simply return it
return response;
},
async (error) => {
const originalRequest = error.config;
const status = error.response ? error.response.status : null;

// Check if the error is a 429 Too Many Requests
// and if the Retry-After header is present
if (
status === 429 &&
error.response.headers['retry-after'] &&
!originalRequest._retry
) {
originalRequest._retry = true; // Mark the request as retried to prevent infinite loops

const retryAfterSeconds = parseInt(
error.response.headers['retry-after'],
10
);
const delayMs = (retryAfterSeconds + 1) * 1000;

// Wait for the specified duration
await new Promise((resolve) => setTimeout(resolve, delayMs));

// Retry the original request
return request(originalRequest);
}

// For other errors, or if no Retry-After header is found,
// or if the request has already been retried, reject the promise
return Promise.reject(error);
}
);
// enable curlirizer output in debug mode
if (state.getCurlirize()) {
curlirize(request, state);
Expand Down
23 changes: 18 additions & 5 deletions src/api/cloud/LogApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import {
import { generateLogApi, generateLogKeysApi } from '../BaseApi';

const logsTailURLTemplate = '%s/monitoring/logs/tail?source=%s';
const logsFetchURLTemplate =
'%s/monitoring/logs?source=%s&beginTime=%s&endTime=%s';
const logsFetchURLTemplate = '%s/monitoring/logs?source=%s';
const logsSourcesURLTemplate = '%s/monitoring/logs/sources';
const logsCreateAPIKeyAndSecretURLTemplate = '%s/keys?_action=create';
const logsGetAPIKeysURLTemplate = '%s/keys';
Expand Down Expand Up @@ -208,28 +207,42 @@ export async function tail({
* @param {string} startTs start timestamp
* @param {string} endTs end timestamp
* @param {string} cookie paged results cookie
* @param {string} transactionId transaction id
* @param {string} queryFilter query filter
* @returns {Promise<PagedResult<LogEventSkeleton>>} promise resolving to paged log event result
*/
export async function fetch({
source,
startTs,
endTs,
cookie,
txid,
filter,
state,
}: {
source: string;
startTs: string;
endTs: string;
cookie: string;
txid: string;
filter: string;
state: State;
}): Promise<PagedResult<LogEventSkeleton>> {
let urlString = util.format(
logsFetchURLTemplate,
getHostOnlyUrl(state.getHost()),
encodeURIComponent(source),
startTs,
endTs
encodeURIComponent(source)
);
if (startTs && endTs) {
urlString += `&beginTime=${startTs}&endTime=${endTs}`;
}
if (txid) {
urlString += `&transactionId=${txid}`;
}
if (filter) {
const filterParam = `_queryFilter=${filter}`;
urlString += `&${encodeURIComponent(filterParam)}`;
}
if (cookie) {
urlString += `&_pagedResultsCookie=${encodeURIComponent(cookie)}`;
}
Expand Down
20 changes: 16 additions & 4 deletions src/ops/cloud/LogOps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,17 @@ export type Log = {
* @param {string} startTs start timestamp
* @param {string} endTs end timestamp
* @param {string} cookie paged results cookie
* @param {string} txid transaction id
* @param {string} filter query filter
* @returns {Promise<PagedResult<LogEventSkeleton>>} promise resolving to paged log event result
*/
fetch(
source: string,
startTs: string,
endTs: string,
cookie: string
cookie: string,
txid: string,
filter: string
): Promise<PagedResult<LogEventSkeleton>>;
};

Expand Down Expand Up @@ -138,9 +142,11 @@ export default (state: State): Log => {
source: string,
startTs: string,
endTs: string,
cookie: string
cookie: string,
txid: string,
filter: string
): Promise<PagedResult<LogEventSkeleton>> {
return fetch({ source, startTs, endTs, cookie, state });
return fetch({ source, startTs, endTs, cookie, txid, filter, state });
},
};
};
Expand Down Expand Up @@ -575,23 +581,29 @@ export async function tail({
* @param {string} startTs start timestamp
* @param {string} endTs end timestamp
* @param {string} cookie paged results cookie
* @param {string} txid transaction id
* @param {string} filter query filter
* @returns {Promise<PagedResult<LogEventSkeleton>>} promise resolving to paged log event result
*/
export async function fetch({
source,
startTs,
endTs,
cookie,
txid,
filter,
state,
}: {
source: string;
startTs: string;
endTs: string;
cookie: string;
txid: string;
filter: string;
state: State;
}): Promise<PagedResult<LogEventSkeleton>> {
try {
return _fetch({ source, startTs, endTs, cookie, state });
return _fetch({ source, startTs, endTs, cookie, txid, filter, state });
} catch (error) {
throw new FrodoError(`Error fetching logs`, error);
}
Expand Down
4 changes: 3 additions & 1 deletion src/shared/State.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ export type State = {
getPassword(): string;
setRealm(realm: string): void;
getRealm(): string;
setUseRealmPrefixOnManagedObjects(useRealmPrefixOnManagedObjects: boolean): void;
setUseRealmPrefixOnManagedObjects(
useRealmPrefixOnManagedObjects: boolean
): void;
getUseRealmPrefixOnManagedObjects(): boolean;
setDeploymentType(type: string): void;
getDeploymentType(): string;
Expand Down