Skip to content

Commit 928dbcb

Browse files
committed
feat(logs): log polling backs off when it doesn't receive results
Fixes #251
1 parent ec2230d commit 928dbcb

File tree

2 files changed

+48
-10
lines changed

2 files changed

+48
-10
lines changed

packages/serverless-api/src/streams/logs.ts

+47-10
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,21 @@ import { listOnePageLogResources } from '../api/logs';
33
import { TwilioServerlessApiClient } from '../client';
44
import { Sid } from '../types';
55
import { LogsConfig } from '../types/logs';
6+
import debug from 'debug';
7+
8+
const log = debug('twilio-serverless-api:client:logs');
9+
10+
const pollsBeforeBackOff = 10;
11+
const defaultPollingFrequency = 1000;
12+
// This default max allows the command to get to polling once every 32 seconds
13+
const defaultMaxPollingFrequency = 30000;
14+
const defaultLogCacheSize = 1000;
615

716
export class LogsStream extends Readable {
17+
private _initialPollingFrequency: number;
818
private _pollingFrequency: number;
19+
private _maxPollingFrequency: number;
20+
private _pollsWithoutResults: number;
921
private _pollingCacheSize: number;
1022
private _interval: NodeJS.Timeout | undefined;
1123
private _viewedSids: Set<Sid>;
@@ -21,8 +33,12 @@ export class LogsStream extends Readable {
2133
this._interval = undefined;
2234
this._viewedSids = new Set();
2335
this._viewedLogs = [];
24-
this._pollingFrequency = config.pollingFrequency || 1000;
25-
this._pollingCacheSize = config.logCacheSize || 1000;
36+
this._pollingFrequency = this._initialPollingFrequency =
37+
config.pollingFrequency || defaultPollingFrequency;
38+
this._maxPollingFrequency =
39+
config.maxPollingFrequency || defaultMaxPollingFrequency;
40+
this._pollsWithoutResults = 0;
41+
this._pollingCacheSize = config.logCacheSize || defaultLogCacheSize;
2642
}
2743

2844
set pollingFrequency(frequency: number) {
@@ -46,12 +62,33 @@ export class LogsStream extends Readable {
4662
pageSize: this.config.limit,
4763
}
4864
);
49-
logs
50-
.filter(log => !this._viewedSids.has(log.sid))
51-
.reverse()
52-
.forEach(log => {
65+
const unviewedLogs = logs.filter((log) => !this._viewedSids.has(log.sid));
66+
if (unviewedLogs.length > 0) {
67+
this._pollsWithoutResults = 0;
68+
this.pollingFrequency = this._initialPollingFrequency;
69+
log(
70+
`New log received. Now polling once every ${this._pollingFrequency} milliseconds.`
71+
);
72+
unviewedLogs.reverse().forEach((log) => {
5373
this.push(log);
5474
});
75+
} else {
76+
if (this._pollsWithoutResults < pollsBeforeBackOff) {
77+
this._pollsWithoutResults++;
78+
} else {
79+
if (this._pollingFrequency < this._maxPollingFrequency) {
80+
log(
81+
`No new logs for ${
82+
this._pollsWithoutResults * this._pollingFrequency
83+
} milliseconds. Now polling once every ${
84+
this._pollingFrequency * 2
85+
} milliseconds.`
86+
);
87+
this.pollingFrequency = this._pollingFrequency * 2;
88+
this._pollsWithoutResults = 0;
89+
}
90+
}
91+
}
5592

5693
// The logs endpoint is not reliably returning logs in the same order
5794
// Therefore we need to keep a set of all previously seen log entries
@@ -68,22 +105,22 @@ export class LogsStream extends Readable {
68105
// and new logs by stringifying the sid and the date together.
69106
const viewedLogsSet = new Set([
70107
...this._viewedLogs.map(
71-
log => `${log.sid}-${log.dateCreated.toISOString()}`
108+
(log) => `${log.sid}-${log.dateCreated.toISOString()}`
72109
),
73-
...logs.map(log => `${log.sid}-${log.date_created}`),
110+
...logs.map((log) => `${log.sid}-${log.date_created}`),
74111
]);
75112
// Then we take that set, map over the logs and split them up into sid and
76113
// date again, sort them most to least recent and chop off the oldest if
77114
// they are beyond the polling cache size.
78115
this._viewedLogs = [...viewedLogsSet]
79-
.map(logString => {
116+
.map((logString) => {
80117
const [sid, dateCreated] = logString.split('-');
81118
return { sid, dateCreated: new Date(dateCreated) };
82119
})
83120
.sort((a, b) => b.dateCreated.valueOf() - a.dateCreated.valueOf())
84121
.slice(0, this._pollingCacheSize);
85122
// Finally we create a set of just SIDs to compare against.
86-
this._viewedSids = new Set(this._viewedLogs.map(log => log.sid));
123+
this._viewedSids = new Set(this._viewedLogs.map((log) => log.sid));
87124

88125
if (!this.config.tail) {
89126
this.push(null);

packages/serverless-api/src/types/logs.ts

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export type LogsConfig = {
99
limit?: number;
1010
filterByFunction?: string | Sid;
1111
pollingFrequency?: number;
12+
maxPollingFrequency?: number;
1213
logCacheSize?: number;
1314
};
1415

0 commit comments

Comments
 (0)