@wdio/sumologic-reporter 9.32.0-alpha.3 → 9.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -38,6 +38,9 @@ module.exports = {
38
38
  ['sumologic', {
39
39
  // define sync interval how often logs get pushed to Sumologic
40
40
  syncInterval: 100,
41
+ maxRetries: 5,
42
+ requestTimeout: 30000,
43
+ shutdownTimeout: 4000,
41
44
  // endpoint of collector source
42
45
  sourceAddress: process.env.SUMO_SOURCE_ADDRESS
43
46
  }]
@@ -46,6 +49,14 @@ module.exports = {
46
49
  };
47
50
  ```
48
51
 
52
+ ### Delivery behaviour
53
+
54
+ Each collector request is limited to a positive `requestTimeout` in milliseconds (default: `30000`); non-positive or invalid values use the default. Transient failures — network errors, `408`, `429`, and `5xx` responses — are retried with backoff. `maxRetries` counts retries after the initial delivery attempt and defaults to `5`. A successful batch resets the retry counter.
55
+
56
+ Other unsuccessful HTTP responses disable the reporter immediately. When its retry limit is reached, the reporter stops its timer and discards its remaining queued logs. Delivery is best effort: logs may be lost, and retries can produce duplicates if the collector accepts a request before it times out.
57
+
58
+ After `runner:end`, `shutdownTimeout` limits the total time for remaining delivery attempts (default: `4000` milliseconds). Successful batches do not reset this deadline. When it expires, the reporter aborts the active request, stops its timers, discards pending logs, and logs their count. Non-positive or invalid values use the default; positive finite values are rounded up and capped at `2147483647`. Keep `shutdownTimeout` below WebdriverIO's `reporterSyncTimeout` (default: `5000`), with enough margin for `reporterSyncInterval` polling. A longer shutdown budget may require increasing both options. The shutdown deadline may expire before all configured retries are used.
59
+
49
60
  After running the first tests with the reporter you should be able to check out the tests logs with the following query:
50
61
 
51
62
  ```
package/build/index.d.ts CHANGED
@@ -6,10 +6,14 @@ import type { Options } from './types.js';
6
6
  */
7
7
  export default class SumoLogicReporter extends WDIOReporter {
8
8
  private _options;
9
- private _interval;
9
+ private _interval?;
10
+ private _shutdownTimer?;
11
+ private _requestController?;
10
12
  private _unsynced;
11
13
  private _isSynchronising;
14
+ private _isDisabled;
12
15
  private _hasRunnerEnd;
16
+ private _retryAttempts;
13
17
  private _retryDelay;
14
18
  private _nextRetryAt;
15
19
  constructor(options: Options);
@@ -24,6 +28,13 @@ export default class SumoLogicReporter extends WDIOReporter {
24
28
  onSuiteEnd(suite: SuiteStats): void;
25
29
  onRunnerEnd(runner: RunnerStats): void;
26
30
  sync(): Promise<void>;
31
+ private _isRetryableStatus;
32
+ private _retry;
33
+ private _getMaxRetries;
34
+ private _getRequestTimeout;
35
+ private _disable;
36
+ private _getShutdownTimeout;
37
+ private _stopInterval;
27
38
  }
28
39
  export * from './types.js';
29
40
  declare global {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AACxE,OAAO,YAAY,MAAM,gBAAgB,CAAA;AAGzC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AA6BzC;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,iBAAkB,SAAQ,YAAY;IACvD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,SAAS,CAAgB;IAEjC,OAAO,CAAC,SAAS,CAAe;IAChC,OAAO,CAAC,gBAAgB,CAAQ;IAChC,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,WAAW,CAAI;IACvB,OAAO,CAAC,YAAY,CAAI;gBAEZ,OAAO,EAAE,OAAO;IAmB5B,IAAI,cAAc,YAEjB;IAED,aAAa,CAAC,MAAM,EAAE,WAAW;IAQjC,YAAY,CAAC,KAAK,EAAE,UAAU;IAQ9B,WAAW,CAAC,IAAI,EAAE,SAAS;IAQ3B,UAAU,CAAC,IAAI,EAAE,SAAS;IAQ1B,UAAU,CAAC,IAAI,EAAE,SAAS;IAQ1B,UAAU,CAAC,IAAI,EAAE,SAAS;IAQ1B,SAAS,CAAC,IAAI,EAAE,SAAS;IAQzB,UAAU,CAAC,KAAK,EAAE,UAAU;IAQ5B,WAAW,CAAC,MAAM,EAAE,WAAW;IASzB,IAAI;CA8Db;AAED,cAAc,YAAY,CAAA;AAE1B,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,WAAW,CAAC;QAClB,UAAU,cAAe,SAAQ,OAAO;SAAG;KAC9C;CACJ"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AACxE,OAAO,YAAY,MAAM,gBAAgB,CAAA;AAGzC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAiCzC;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,iBAAkB,SAAQ,YAAY;IACvD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,SAAS,CAAC,CAAgB;IAClC,OAAO,CAAC,cAAc,CAAC,CAAgB;IACvC,OAAO,CAAC,kBAAkB,CAAC,CAAiB;IAE5C,OAAO,CAAC,SAAS,CAAe;IAChC,OAAO,CAAC,gBAAgB,CAAQ;IAChC,OAAO,CAAC,WAAW,CAAQ;IAC3B,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,cAAc,CAAI;IAC1B,OAAO,CAAC,WAAW,CAAI;IACvB,OAAO,CAAC,YAAY,CAAI;gBAEZ,OAAO,EAAE,OAAO;IAoB5B,IAAI,cAAc,YAEjB;IAED,aAAa,CAAC,MAAM,EAAE,WAAW;IAYjC,YAAY,CAAC,KAAK,EAAE,UAAU;IAY9B,WAAW,CAAC,IAAI,EAAE,SAAS;IAY3B,UAAU,CAAC,IAAI,EAAE,SAAS;IAY1B,UAAU,CAAC,IAAI,EAAE,SAAS;IAY1B,UAAU,CAAC,IAAI,EAAE,SAAS;IAY1B,SAAS,CAAC,IAAI,EAAE,SAAS;IAYzB,UAAU,CAAC,KAAK,EAAE,UAAU;IAY5B,WAAW,CAAC,MAAM,EAAE,WAAW;IAkBzB,IAAI;IA0FV,OAAO,CAAC,kBAAkB;IAI1B,OAAO,CAAC,MAAM;IAkBd,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,kBAAkB;IAO1B,OAAO,CAAC,QAAQ;IAShB,OAAO,CAAC,mBAAmB;IAO3B,OAAO,CAAC,aAAa;CAUxB;AAED,cAAc,YAAY,CAAA;AAE1B,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,WAAW,CAAC;QAClB,UAAU,cAAe,SAAQ,OAAO;SAAG;KAC9C;CACJ"}
package/build/index.js CHANGED
@@ -5,6 +5,10 @@ import logger from "@wdio/logger";
5
5
  var log = logger("@wdio/sumologic-reporter");
6
6
  var MAX_LINES = 100;
7
7
  var MAX_RETRY_DELAY = 1e3;
8
+ var DEFAULT_MAX_RETRIES = 5;
9
+ var DEFAULT_REQUEST_TIMEOUT = 3e4;
10
+ var DEFAULT_SHUTDOWN_TIMEOUT = 4e3;
11
+ var MAX_REQUEST_TIMEOUT = 2147483647;
8
12
  function formatDate(date) {
9
13
  const year = date.getFullYear();
10
14
  const month = String(date.getMonth() + 1).padStart(2, "0");
@@ -23,9 +27,13 @@ function formatDate(date) {
23
27
  var SumoLogicReporter = class extends WDIOReporter {
24
28
  _options;
25
29
  _interval;
30
+ _shutdownTimer;
31
+ _requestController;
26
32
  _unsynced = [];
27
33
  _isSynchronising = false;
34
+ _isDisabled = false;
28
35
  _hasRunnerEnd = false;
36
+ _retryAttempts = 0;
29
37
  _retryDelay = 0;
30
38
  _nextRetryAt = 0;
31
39
  constructor(options) {
@@ -38,16 +46,20 @@ var SumoLogicReporter = class extends WDIOReporter {
38
46
  // endpoint of collector source
39
47
  sourceAddress: process.env.SUMO_SOURCE_ADDRESS
40
48
  }, options);
41
- if (typeof this._options.sourceAddress !== "string") {
42
- log.error('Sumo Logic requires "sourceAddress" paramater');
49
+ if (typeof this._options.sourceAddress !== "string" || this._options.sourceAddress.trim() === "") {
50
+ this._disable('Sumo Logic reporter disabled: a non-empty "sourceAddress" is required');
51
+ return;
43
52
  }
44
53
  this._interval = global.setInterval(this.sync.bind(this), this._options.syncInterval);
45
54
  }
46
55
  // @ts-ignore
47
56
  get isSynchronised() {
48
- return this._unsynced.length === 0;
57
+ return this._isDisabled || this._unsynced.length === 0;
49
58
  }
50
59
  onRunnerStart(runner) {
60
+ if (this._isDisabled) {
61
+ return;
62
+ }
51
63
  this._unsynced.push(stringify({
52
64
  time: formatDate(/* @__PURE__ */ new Date()),
53
65
  event: "runner:start",
@@ -55,6 +67,9 @@ var SumoLogicReporter = class extends WDIOReporter {
55
67
  }));
56
68
  }
57
69
  onSuiteStart(suite) {
70
+ if (this._isDisabled) {
71
+ return;
72
+ }
58
73
  this._unsynced.push(stringify({
59
74
  time: formatDate(/* @__PURE__ */ new Date()),
60
75
  event: "suite:start",
@@ -62,6 +77,9 @@ var SumoLogicReporter = class extends WDIOReporter {
62
77
  }));
63
78
  }
64
79
  onTestStart(test) {
80
+ if (this._isDisabled) {
81
+ return;
82
+ }
65
83
  this._unsynced.push(stringify({
66
84
  time: formatDate(/* @__PURE__ */ new Date()),
67
85
  event: "test:start",
@@ -69,6 +87,9 @@ var SumoLogicReporter = class extends WDIOReporter {
69
87
  }));
70
88
  }
71
89
  onTestSkip(test) {
90
+ if (this._isDisabled) {
91
+ return;
92
+ }
72
93
  this._unsynced.push(stringify({
73
94
  time: formatDate(/* @__PURE__ */ new Date()),
74
95
  event: "test:skip",
@@ -76,6 +97,9 @@ var SumoLogicReporter = class extends WDIOReporter {
76
97
  }));
77
98
  }
78
99
  onTestPass(test) {
100
+ if (this._isDisabled) {
101
+ return;
102
+ }
79
103
  this._unsynced.push(stringify({
80
104
  time: formatDate(/* @__PURE__ */ new Date()),
81
105
  event: "test:pass",
@@ -83,6 +107,9 @@ var SumoLogicReporter = class extends WDIOReporter {
83
107
  }));
84
108
  }
85
109
  onTestFail(test) {
110
+ if (this._isDisabled) {
111
+ return;
112
+ }
86
113
  this._unsynced.push(stringify({
87
114
  time: formatDate(/* @__PURE__ */ new Date()),
88
115
  event: "test:fail",
@@ -90,6 +117,9 @@ var SumoLogicReporter = class extends WDIOReporter {
90
117
  }));
91
118
  }
92
119
  onTestEnd(test) {
120
+ if (this._isDisabled) {
121
+ return;
122
+ }
93
123
  this._unsynced.push(stringify({
94
124
  time: formatDate(/* @__PURE__ */ new Date()),
95
125
  event: "test:end",
@@ -97,6 +127,9 @@ var SumoLogicReporter = class extends WDIOReporter {
97
127
  }));
98
128
  }
99
129
  onSuiteEnd(suite) {
130
+ if (this._isDisabled) {
131
+ return;
132
+ }
100
133
  this._unsynced.push(stringify({
101
134
  time: formatDate(/* @__PURE__ */ new Date()),
102
135
  event: "suite:end",
@@ -105,45 +138,127 @@ var SumoLogicReporter = class extends WDIOReporter {
105
138
  }
106
139
  onRunnerEnd(runner) {
107
140
  this._hasRunnerEnd = true;
141
+ if (this._isDisabled) {
142
+ return;
143
+ }
108
144
  this._unsynced.push(stringify({
109
145
  time: formatDate(/* @__PURE__ */ new Date()),
110
146
  event: "runner:end",
111
147
  data: runner
112
148
  }));
149
+ if (!this._shutdownTimer) {
150
+ this._shutdownTimer = setTimeout(() => {
151
+ this._disable(`Sumo Logic reporter shutdown timeout reached; discarding ${this._unsynced.length} queued events`);
152
+ }, this._getShutdownTimeout());
153
+ }
113
154
  }
114
155
  async sync() {
156
+ if (this._isDisabled) {
157
+ return;
158
+ }
115
159
  if (this._hasRunnerEnd && this._unsynced.length === 0) {
116
- clearInterval(this._interval);
160
+ this._stopInterval();
161
+ return;
162
+ }
163
+ if (this._isSynchronising || this._unsynced.length === 0 || Date.now() < this._nextRetryAt) {
164
+ return;
117
165
  }
118
- if (this._isSynchronising || this._unsynced.length === 0 || typeof this._options.sourceAddress !== "string" || Date.now() < this._nextRetryAt) {
166
+ const sourceAddress = this._options.sourceAddress;
167
+ if (typeof sourceAddress !== "string" || sourceAddress.trim() === "") {
168
+ this._disable('Sumo Logic reporter disabled: a non-empty "sourceAddress" is required');
119
169
  return;
120
170
  }
121
171
  const logLines = this._unsynced.slice(0, MAX_LINES).join("\n");
122
172
  this._isSynchronising = true;
123
173
  log.debug("start synchronization");
174
+ const controller = new AbortController();
175
+ this._requestController = controller;
176
+ const requestTimer = setTimeout(() => controller.abort(), this._getRequestTimeout());
124
177
  try {
125
- const resp = await fetch(this._options.sourceAddress, {
178
+ const resp = await fetch(sourceAddress, {
126
179
  method: "POST",
127
- body: JSON.stringify(logLines)
180
+ body: JSON.stringify(logLines),
181
+ signal: controller.signal
128
182
  });
183
+ if (this._isDisabled) {
184
+ return;
185
+ }
129
186
  if (!resp.ok) {
130
- throw new Error(`Sumo Logic responded with ${resp.status}`);
187
+ if (!this._isRetryableStatus(resp.status)) {
188
+ this._disable(`failed to send data to Sumo Logic (HTTP ${resp.status}); disabling reporter`);
189
+ return;
190
+ }
191
+ this._retry(resp.status);
192
+ return;
131
193
  }
194
+ this._retryAttempts = 0;
132
195
  this._retryDelay = 0;
133
196
  this._nextRetryAt = 0;
134
197
  this._unsynced.splice(0, MAX_LINES);
198
+ if (this._hasRunnerEnd && this._unsynced.length === 0) {
199
+ this._stopInterval();
200
+ }
135
201
  return log.debug(`synchronised collector data, server status: ${resp.status}`);
136
- } catch (err) {
137
- this._retryDelay = Math.min(
138
- Math.max(this._options.syncInterval ?? 100, this._retryDelay * 2),
139
- MAX_RETRY_DELAY
140
- );
141
- this._nextRetryAt = Date.now() + this._retryDelay;
142
- return log.error("failed send data to Sumo Logic:\n", err.stack);
202
+ } catch {
203
+ if (!this._isDisabled) {
204
+ this._retry();
205
+ }
143
206
  } finally {
207
+ clearTimeout(requestTimer);
208
+ controller.abort();
209
+ this._requestController = void 0;
144
210
  this._isSynchronising = false;
145
211
  }
146
212
  }
213
+ _isRetryableStatus(status) {
214
+ return status === 408 || status === 429 || status >= 500 && status <= 599;
215
+ }
216
+ _retry(status) {
217
+ const maxRetries = this._getMaxRetries();
218
+ if (this._retryAttempts >= maxRetries) {
219
+ const statusMessage2 = status === void 0 ? "" : ` (HTTP ${status})`;
220
+ this._disable(`failed to send data to Sumo Logic${statusMessage2}; retry limit of ${maxRetries} reached, disabling reporter`);
221
+ return;
222
+ }
223
+ this._retryAttempts++;
224
+ this._retryDelay = Math.min(
225
+ Math.max(this._options.syncInterval ?? 100, this._retryDelay * 2),
226
+ MAX_RETRY_DELAY
227
+ );
228
+ this._nextRetryAt = Date.now() + this._retryDelay;
229
+ const statusMessage = status === void 0 ? "" : ` (HTTP ${status})`;
230
+ log.error(`failed to send data to Sumo Logic${statusMessage}; retrying (${this._retryAttempts}/${maxRetries})`);
231
+ }
232
+ _getMaxRetries() {
233
+ const { maxRetries } = this._options;
234
+ return typeof maxRetries === "number" && Number.isFinite(maxRetries) && maxRetries >= 0 ? Math.floor(maxRetries) : DEFAULT_MAX_RETRIES;
235
+ }
236
+ _getRequestTimeout() {
237
+ const { requestTimeout } = this._options;
238
+ return typeof requestTimeout === "number" && Number.isFinite(requestTimeout) && requestTimeout > 0 ? Math.min(Math.ceil(requestTimeout), MAX_REQUEST_TIMEOUT) : DEFAULT_REQUEST_TIMEOUT;
239
+ }
240
+ _disable(message) {
241
+ this._isDisabled = true;
242
+ this._unsynced = [];
243
+ this._nextRetryAt = 0;
244
+ this._stopInterval();
245
+ this._requestController?.abort();
246
+ log.error(message);
247
+ }
248
+ _getShutdownTimeout() {
249
+ const { shutdownTimeout } = this._options;
250
+ return typeof shutdownTimeout === "number" && Number.isFinite(shutdownTimeout) && shutdownTimeout > 0 ? Math.min(Math.ceil(shutdownTimeout), MAX_REQUEST_TIMEOUT) : DEFAULT_SHUTDOWN_TIMEOUT;
251
+ }
252
+ _stopInterval() {
253
+ if (this._shutdownTimer) {
254
+ clearTimeout(this._shutdownTimer);
255
+ this._shutdownTimer = void 0;
256
+ }
257
+ if (this._interval) {
258
+ clearInterval(this._interval);
259
+ this._interval = void 0;
260
+ }
261
+ }
147
262
  };
148
263
  export {
149
264
  SumoLogicReporter as default
package/build/types.d.ts CHANGED
@@ -5,6 +5,25 @@ export interface Options extends Partial<Reporters.Options> {
5
5
  * @default 100
6
6
  */
7
7
  syncInterval?: number;
8
+ /**
9
+ * maximum number of retries after the initial delivery attempt
10
+ * non-negative finite values are rounded down; all other values use the default
11
+ * @default 5
12
+ */
13
+ maxRetries?: number;
14
+ /**
15
+ * maximum time in milliseconds to wait for a collector response
16
+ * positive finite values are rounded up and capped at 2147483647; all other values use the default
17
+ * @default 30000
18
+ */
19
+ requestTimeout?: number;
20
+ /**
21
+ * total time in milliseconds to flush queued events after runner:end, including requests and retries
22
+ * keep below WebdriverIO's reporterSyncTimeout, allowing time for its polling interval
23
+ * positive finite values are rounded up and capped at 2147483647; all other values use the default
24
+ * @default 4000
25
+ */
26
+ shutdownTimeout?: number;
8
27
  /**
9
28
  * endpoint of collector source
10
29
  * @default
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAE5C,MAAM,WAAW,OAAQ,SAAQ,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC;IACvD;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;CACzB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAE5C,MAAM,WAAW,OAAQ,SAAQ,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC;IACvD;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;CACzB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wdio/sumologic-reporter",
3
- "version": "9.32.0-alpha.3+d8b6a29ba",
3
+ "version": "9.32.0",
4
4
  "description": "A WebdriverIO reporter that sends test results to Sumologic for data analyses",
5
5
  "author": "Christian Bromann <mail@bromann.dev>",
6
6
  "homepage": "https://github.com/webdriverio/webdriverio/tree/main/packages/wdio-sumologic-reporter",
@@ -32,9 +32,9 @@
32
32
  },
33
33
  "typeScriptVersion": "3.8.3",
34
34
  "dependencies": {
35
- "@wdio/logger": "9.32.0-alpha.3+d8b6a29ba",
36
- "@wdio/reporter": "9.32.0-alpha.3+d8b6a29ba",
37
- "@wdio/types": "9.32.0-alpha.3+d8b6a29ba",
35
+ "@wdio/logger": "9.29.1",
36
+ "@wdio/reporter": "9.32.0",
37
+ "@wdio/types": "9.32.0",
38
38
  "json-stringify-safe": "^5.0.1"
39
39
  },
40
40
  "devDependencies": {
@@ -43,5 +43,5 @@
43
43
  "publishConfig": {
44
44
  "access": "public"
45
45
  },
46
- "gitHead": "d8b6a29ba73af54edb002d2a2e1a55b64849a8ef"
46
+ "gitHead": "5c2a318d9a5e8424a2f2de95df9374b89bda7936"
47
47
  }