@crawlee/core 4.0.0-beta.12 → 4.0.0-beta.121
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 +17 -13
- package/autoscaling/autoscaled_pool.d.ts +67 -172
- package/autoscaling/autoscaled_pool.js +165 -320
- package/autoscaling/client_load_signal.d.ts +55 -0
- package/autoscaling/client_load_signal.js +73 -0
- package/autoscaling/concurrency_system.d.ts +268 -0
- package/autoscaling/concurrency_system.js +351 -0
- package/autoscaling/cpu_load_signal.d.ts +43 -0
- package/autoscaling/cpu_load_signal.js +47 -0
- package/autoscaling/event_loop_load_signal.d.ts +51 -0
- package/autoscaling/event_loop_load_signal.js +60 -0
- package/autoscaling/index.d.ts +6 -1
- package/autoscaling/index.js +6 -1
- package/autoscaling/load_signal.d.ts +99 -0
- package/autoscaling/load_signal.js +104 -0
- package/autoscaling/memory_load_signal.d.ts +47 -0
- package/autoscaling/memory_load_signal.js +106 -0
- package/autoscaling/snapshotter.d.ts +58 -163
- package/autoscaling/snapshotter.js +45 -263
- package/autoscaling/system_status.d.ts +62 -84
- package/autoscaling/system_status.js +92 -122
- package/autoscaling/weighted_avg.d.ts +5 -0
- package/autoscaling/weighted_avg.js +14 -0
- package/byte_utils.d.ts +17 -0
- package/byte_utils.js +42 -0
- package/configuration.d.ts +96 -223
- package/configuration.js +170 -222
- package/cookie_utils.d.ts +4 -3
- package/cookie_utils.js +22 -13
- package/crawlers/context_pipeline.d.ts +10 -1
- package/crawlers/context_pipeline.js +31 -8
- package/crawlers/crawler_commons.d.ts +90 -83
- package/crawlers/crawler_commons.js +1 -116
- package/crawlers/error_snapshotter.d.ts +2 -5
- package/crawlers/error_snapshotter.js +7 -8
- package/crawlers/error_tracker.d.ts +0 -1
- package/crawlers/error_tracker.js +0 -1
- package/crawlers/index.d.ts +1 -3
- package/crawlers/index.js +0 -3
- package/crawlers/internals/types.d.ts +0 -1
- package/crawlers/internals/types.js +0 -1
- package/crawlers/statistics.d.ts +143 -59
- package/crawlers/statistics.js +243 -153
- package/debug.d.ts +36 -0
- package/debug.js +70 -0
- package/enqueue_links/enqueue_links.d.ts +59 -68
- package/enqueue_links/enqueue_links.js +57 -62
- package/enqueue_links/index.d.ts +0 -1
- package/enqueue_links/index.js +0 -1
- package/enqueue_links/shared.d.ts +40 -27
- package/enqueue_links/shared.js +90 -68
- package/errors.d.ts +72 -4
- package/errors.js +89 -5
- package/events/event_manager.d.ts +35 -9
- package/events/event_manager.js +10 -12
- package/events/index.d.ts +0 -1
- package/events/index.js +0 -1
- package/events/local_event_manager.d.ts +15 -3
- package/events/local_event_manager.js +39 -13
- package/http.d.ts +9 -0
- package/http.js +28 -0
- package/index.d.ts +7 -4
- package/index.js +6 -3
- package/iterables.d.ts +79 -0
- package/iterables.js +134 -0
- package/log.d.ts +82 -3
- package/log.js +106 -1
- package/memory-storage/consts.d.ts +4 -0
- package/memory-storage/consts.js +4 -0
- package/memory-storage/index.d.ts +1 -0
- package/memory-storage/index.js +1 -0
- package/memory-storage/memory-storage.d.ts +38 -0
- package/memory-storage/memory-storage.js +130 -0
- package/memory-storage/resource-clients/common/base-client.d.ts +4 -0
- package/memory-storage/resource-clients/common/base-client.js +6 -0
- package/memory-storage/resource-clients/dataset.d.ts +40 -0
- package/memory-storage/resource-clients/dataset.js +114 -0
- package/memory-storage/resource-clients/key-value-store.d.ts +63 -0
- package/memory-storage/resource-clients/key-value-store.js +204 -0
- package/memory-storage/resource-clients/request-queue.d.ts +77 -0
- package/memory-storage/resource-clients/request-queue.js +422 -0
- package/memory-storage/utils.d.ts +16 -0
- package/memory-storage/utils.js +41 -0
- package/owned_or_injected.d.ts +58 -0
- package/owned_or_injected.js +98 -0
- package/package.json +13 -12
- package/proxy_configuration.d.ts +24 -132
- package/proxy_configuration.js +24 -143
- package/recoverable_state.d.ts +140 -0
- package/recoverable_state.js +212 -0
- package/request.d.ts +86 -17
- package/request.js +120 -41
- package/router.d.ts +193 -21
- package/router.js +188 -43
- package/serialization.d.ts +0 -1
- package/serialization.js +9 -11
- package/service_locator.d.ts +165 -0
- package/service_locator.js +253 -0
- package/session_pool/consts.d.ts +1 -2
- package/session_pool/consts.js +1 -2
- package/session_pool/errors.d.ts +0 -1
- package/session_pool/errors.js +0 -1
- package/session_pool/fingerprint.d.ts +9 -0
- package/session_pool/fingerprint.js +30 -0
- package/session_pool/index.d.ts +0 -2
- package/session_pool/index.js +0 -2
- package/session_pool/session.d.ts +35 -89
- package/session_pool/session.js +82 -142
- package/session_pool/session_pool.d.ts +69 -90
- package/session_pool/session_pool.js +151 -150
- package/storages/batched_adds.d.ts +37 -0
- package/storages/batched_adds.js +73 -0
- package/storages/dataset.d.ts +114 -54
- package/storages/dataset.js +285 -144
- package/storages/index.d.ts +10 -8
- package/storages/index.js +8 -8
- package/storages/key_value_store.d.ts +185 -42
- package/storages/key_value_store.js +424 -151
- package/storages/key_value_store_codec.d.ts +32 -0
- package/storages/key_value_store_codec.js +113 -0
- package/storages/request_dedup_cache.d.ts +22 -0
- package/storages/request_dedup_cache.js +48 -0
- package/storages/request_list.d.ts +52 -116
- package/storages/request_list.js +159 -133
- package/storages/request_loader.d.ts +101 -0
- package/storages/request_loader.js +1 -0
- package/storages/request_manager.d.ts +33 -0
- package/storages/request_manager.js +1 -0
- package/storages/request_manager_tandem.d.ts +97 -0
- package/storages/request_manager_tandem.js +197 -0
- package/storages/request_queue.d.ts +290 -47
- package/storages/request_queue.js +757 -216
- package/storages/{sitemap_request_list.d.ts → sitemap_request_loader.d.ts} +37 -88
- package/storages/{sitemap_request_list.js → sitemap_request_loader.js} +137 -143
- package/storages/storage_instance_manager.d.ts +87 -0
- package/storages/storage_instance_manager.js +256 -0
- package/storages/storage_stats.d.ts +48 -0
- package/storages/storage_stats.js +29 -0
- package/storages/throttling_request_manager.d.ts +216 -0
- package/storages/throttling_request_manager.js +453 -0
- package/storages/transaction.d.ts +252 -0
- package/storages/transaction.js +251 -0
- package/storages/utils.d.ts +58 -11
- package/storages/utils.js +64 -13
- package/system-info/cpu-info.d.ts +67 -0
- package/system-info/cpu-info.js +216 -0
- package/system-info/memory-info.d.ts +31 -0
- package/system-info/memory-info.js +115 -0
- package/system-info/ps-tree.d.ts +17 -0
- package/system-info/ps-tree.js +144 -0
- package/system-info/runtime.d.ts +14 -0
- package/system-info/runtime.js +80 -0
- package/typedefs.d.ts +0 -6
- package/typedefs.js +0 -1
- package/url.d.ts +9 -0
- package/url.js +11 -0
- package/validators.d.ts +8 -1
- package/validators.js +10 -3
- package/autoscaling/autoscaled_pool.d.ts.map +0 -1
- package/autoscaling/autoscaled_pool.js.map +0 -1
- package/autoscaling/index.d.ts.map +0 -1
- package/autoscaling/index.js.map +0 -1
- package/autoscaling/snapshotter.d.ts.map +0 -1
- package/autoscaling/snapshotter.js.map +0 -1
- package/autoscaling/system_status.d.ts.map +0 -1
- package/autoscaling/system_status.js.map +0 -1
- package/configuration.d.ts.map +0 -1
- package/configuration.js.map +0 -1
- package/cookie_utils.d.ts.map +0 -1
- package/cookie_utils.js.map +0 -1
- package/crawlers/context_pipeline.d.ts.map +0 -1
- package/crawlers/context_pipeline.js.map +0 -1
- package/crawlers/crawler_commons.d.ts.map +0 -1
- package/crawlers/crawler_commons.js.map +0 -1
- package/crawlers/crawler_utils.d.ts +0 -10
- package/crawlers/crawler_utils.d.ts.map +0 -1
- package/crawlers/crawler_utils.js +0 -12
- package/crawlers/crawler_utils.js.map +0 -1
- package/crawlers/error_snapshotter.d.ts.map +0 -1
- package/crawlers/error_snapshotter.js.map +0 -1
- package/crawlers/error_tracker.d.ts.map +0 -1
- package/crawlers/error_tracker.js.map +0 -1
- package/crawlers/index.d.ts.map +0 -1
- package/crawlers/index.js.map +0 -1
- package/crawlers/internals/types.d.ts.map +0 -1
- package/crawlers/internals/types.js.map +0 -1
- package/crawlers/statistics.d.ts.map +0 -1
- package/crawlers/statistics.js.map +0 -1
- package/enqueue_links/enqueue_links.d.ts.map +0 -1
- package/enqueue_links/enqueue_links.js.map +0 -1
- package/enqueue_links/index.d.ts.map +0 -1
- package/enqueue_links/index.js.map +0 -1
- package/enqueue_links/shared.d.ts.map +0 -1
- package/enqueue_links/shared.js.map +0 -1
- package/errors.d.ts.map +0 -1
- package/errors.js.map +0 -1
- package/events/event_manager.d.ts.map +0 -1
- package/events/event_manager.js.map +0 -1
- package/events/index.d.ts.map +0 -1
- package/events/index.js.map +0 -1
- package/events/local_event_manager.d.ts.map +0 -1
- package/events/local_event_manager.js.map +0 -1
- package/http_clients/base-http-client.d.ts +0 -134
- package/http_clients/base-http-client.d.ts.map +0 -1
- package/http_clients/base-http-client.js +0 -33
- package/http_clients/base-http-client.js.map +0 -1
- package/http_clients/form-data-like.d.ts +0 -67
- package/http_clients/form-data-like.d.ts.map +0 -1
- package/http_clients/form-data-like.js +0 -5
- package/http_clients/form-data-like.js.map +0 -1
- package/http_clients/got-scraping-http-client.d.ts +0 -15
- package/http_clients/got-scraping-http-client.d.ts.map +0 -1
- package/http_clients/got-scraping-http-client.js +0 -69
- package/http_clients/got-scraping-http-client.js.map +0 -1
- package/http_clients/index.d.ts +0 -3
- package/http_clients/index.d.ts.map +0 -1
- package/http_clients/index.js +0 -3
- package/http_clients/index.js.map +0 -1
- package/index.d.ts.map +0 -1
- package/index.js.map +0 -1
- package/log.d.ts.map +0 -1
- package/log.js.map +0 -1
- package/proxy_configuration.d.ts.map +0 -1
- package/proxy_configuration.js.map +0 -1
- package/request.d.ts.map +0 -1
- package/request.js.map +0 -1
- package/router.d.ts.map +0 -1
- package/router.js.map +0 -1
- package/serialization.d.ts.map +0 -1
- package/serialization.js.map +0 -1
- package/session_pool/consts.d.ts.map +0 -1
- package/session_pool/consts.js.map +0 -1
- package/session_pool/errors.d.ts.map +0 -1
- package/session_pool/errors.js.map +0 -1
- package/session_pool/events.d.ts +0 -3
- package/session_pool/events.d.ts.map +0 -1
- package/session_pool/events.js +0 -3
- package/session_pool/events.js.map +0 -1
- package/session_pool/index.d.ts.map +0 -1
- package/session_pool/index.js.map +0 -1
- package/session_pool/session.d.ts.map +0 -1
- package/session_pool/session.js.map +0 -1
- package/session_pool/session_pool.d.ts.map +0 -1
- package/session_pool/session_pool.js.map +0 -1
- package/storages/access_checking.d.ts +0 -13
- package/storages/access_checking.d.ts.map +0 -1
- package/storages/access_checking.js +0 -14
- package/storages/access_checking.js.map +0 -1
- package/storages/dataset.d.ts.map +0 -1
- package/storages/dataset.js.map +0 -1
- package/storages/index.d.ts.map +0 -1
- package/storages/index.js.map +0 -1
- package/storages/key_value_store.d.ts.map +0 -1
- package/storages/key_value_store.js.map +0 -1
- package/storages/request_list.d.ts.map +0 -1
- package/storages/request_list.js.map +0 -1
- package/storages/request_provider.d.ts +0 -308
- package/storages/request_provider.d.ts.map +0 -1
- package/storages/request_provider.js +0 -555
- package/storages/request_provider.js.map +0 -1
- package/storages/request_queue.d.ts.map +0 -1
- package/storages/request_queue.js.map +0 -1
- package/storages/request_queue_v2.d.ts +0 -87
- package/storages/request_queue_v2.d.ts.map +0 -1
- package/storages/request_queue_v2.js +0 -438
- package/storages/request_queue_v2.js.map +0 -1
- package/storages/sitemap_request_list.d.ts.map +0 -1
- package/storages/sitemap_request_list.js.map +0 -1
- package/storages/storage_manager.d.ts +0 -58
- package/storages/storage_manager.d.ts.map +0 -1
- package/storages/storage_manager.js +0 -105
- package/storages/storage_manager.js.map +0 -1
- package/storages/utils.d.ts.map +0 -1
- package/storages/utils.js.map +0 -1
- package/tsconfig.build.tsbuildinfo +0 -1
- package/typedefs.d.ts.map +0 -1
- package/typedefs.js.map +0 -1
- package/validators.d.ts.map +0 -1
- package/validators.js.map +0 -1
package/crawlers/statistics.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import ow from 'ow';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { RecoverableState } from '../recoverable_state.js';
|
|
4
|
+
import { serviceLocator } from '../service_locator.js';
|
|
5
5
|
import { ErrorTracker } from './error_tracker.js';
|
|
6
6
|
/**
|
|
7
7
|
* @ignore
|
|
8
8
|
*/
|
|
9
9
|
class Job {
|
|
10
|
-
lastRunAt = null;
|
|
11
|
-
durationMillis;
|
|
10
|
+
#lastRunAt = null;
|
|
11
|
+
#durationMillis;
|
|
12
12
|
run() {
|
|
13
|
-
this
|
|
13
|
+
this.#lastRunAt = Date.now();
|
|
14
14
|
}
|
|
15
15
|
finish() {
|
|
16
|
-
this
|
|
17
|
-
return this
|
|
16
|
+
this.#durationMillis = Date.now() - this.#lastRunAt;
|
|
17
|
+
return this.#durationMillis;
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
const errorTrackerConfig = {
|
|
@@ -25,17 +25,127 @@ const errorTrackerConfig = {
|
|
|
25
25
|
showErrorMessage: true,
|
|
26
26
|
showFullMessage: false,
|
|
27
27
|
};
|
|
28
|
+
/**
|
|
29
|
+
* The persisted record, in the order it is written - the schema rebuilds the object on the way out, so the field
|
|
30
|
+
* order here *is* the record's field order (guarded by a test).
|
|
31
|
+
*
|
|
32
|
+
* JSON has no infinity, so the three fields that are `Infinity` until the first request settles are written as
|
|
33
|
+
* `null`. Both {@link Statistics.serializeState} and {@link Statistics.deserializeState} run through this,
|
|
34
|
+
* which is what keeps them describing the same record.
|
|
35
|
+
*
|
|
36
|
+
* Nothing is optional on purpose: `serializeState` has always written every field, so a record missing one is not
|
|
37
|
+
* one of ours and is discarded whole rather than partially trusted - a counter restored as a string would poison
|
|
38
|
+
* every later increment.
|
|
39
|
+
*/
|
|
40
|
+
const persistedStatisticState = z
|
|
41
|
+
.object({
|
|
42
|
+
requestsFinished: z.number(),
|
|
43
|
+
requestsFailed: z.number(),
|
|
44
|
+
requestsRetries: z.number(),
|
|
45
|
+
requestsFailedPerMinute: z.number().nullable(),
|
|
46
|
+
requestsFinishedPerMinute: z.number().nullable(),
|
|
47
|
+
requestMinDurationMillis: z.number().nullable(),
|
|
48
|
+
requestMaxDurationMillis: z.number(),
|
|
49
|
+
requestTotalFailedDurationMillis: z.number(),
|
|
50
|
+
requestTotalFinishedDurationMillis: z.number(),
|
|
51
|
+
crawlerStartedAt: z.string().nullable(),
|
|
52
|
+
crawlerFinishedAt: z.string().nullable(),
|
|
53
|
+
statsPersistedAt: z.string(),
|
|
54
|
+
crawlerRuntimeMillis: z.number(),
|
|
55
|
+
crawlerLastStartTimestamp: z.number(),
|
|
56
|
+
// A retry count that never occurred leaves a hole in the live histogram, written out as a `null`. We
|
|
57
|
+
// once saw a record whose histogram was not an array at all and crashed the crawler on load.
|
|
58
|
+
requestRetryHistogram: z.array(z.number().nullable()),
|
|
59
|
+
statsId: z.string(),
|
|
60
|
+
requestAvgFailedDurationMillis: z.number().nullable(),
|
|
61
|
+
requestAvgFinishedDurationMillis: z.number().nullable(),
|
|
62
|
+
requestTotalDurationMillis: z.number(),
|
|
63
|
+
requestsTotal: z.number(),
|
|
64
|
+
requestsWithStatusCode: z.record(z.string(), z.number()),
|
|
65
|
+
errors: z.record(z.string(), z.unknown()),
|
|
66
|
+
retryErrors: z.record(z.string(), z.unknown()),
|
|
67
|
+
})
|
|
68
|
+
// A subclass tracking extra fields spreads them into the record; they are none of this schema's business,
|
|
69
|
+
// but they must not be dropped on the way through it.
|
|
70
|
+
.catchall(z.unknown());
|
|
71
|
+
/** `Infinity` is what the statistics use for "nothing to average yet"; JSON has only `null` for it. */
|
|
72
|
+
function finiteOrNull(value) {
|
|
73
|
+
return Number.isFinite(value) ? value : null;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The conversion between the live state and the record above, in both directions.
|
|
77
|
+
*
|
|
78
|
+
* Built per instance rather than kept as a constant because a record carries three things the state does not: the
|
|
79
|
+
* instance `id`, the derived aggregates of the overridable {@link Statistics.calculate}, and - on the way back -
|
|
80
|
+
* the fields that are rebuilt from {@link Statistics.defaultState} rather than restored, the error trackers
|
|
81
|
+
* among them.
|
|
82
|
+
*
|
|
83
|
+
* The model side is deliberately opaque: zod rebuilds what it validates, and `state.errors` has to stay the very
|
|
84
|
+
* object the error trackers write into, not a copy of it.
|
|
85
|
+
*/
|
|
86
|
+
function buildStatisticStateCodec(statistics) {
|
|
87
|
+
return z.codec(persistedStatisticState, z.custom(), {
|
|
88
|
+
decode: (record) => ({
|
|
89
|
+
...statistics.defaultState(),
|
|
90
|
+
requestsFinished: record.requestsFinished,
|
|
91
|
+
requestsFailed: record.requestsFailed,
|
|
92
|
+
requestsRetries: record.requestsRetries,
|
|
93
|
+
requestTotalFailedDurationMillis: record.requestTotalFailedDurationMillis,
|
|
94
|
+
requestTotalFinishedDurationMillis: record.requestTotalFinishedDurationMillis,
|
|
95
|
+
// Restoring the `null` as-is would make every later `duration < min` comparison fail, leaving the
|
|
96
|
+
// minimum `null` for the rest of the run.
|
|
97
|
+
requestMinDurationMillis: record.requestMinDurationMillis ?? Infinity,
|
|
98
|
+
requestMaxDurationMillis: record.requestMaxDurationMillis,
|
|
99
|
+
crawlerRuntimeMillis: record.crawlerRuntimeMillis,
|
|
100
|
+
// A `null` stands for the zero requests that reached that retry count - restore it as such.
|
|
101
|
+
requestRetryHistogram: record.requestRetryHistogram.map((count) => count ?? 0),
|
|
102
|
+
// The record keeps ISO strings, the live state keeps `Date`s.
|
|
103
|
+
crawlerStartedAt: record.crawlerStartedAt === null ? null : new Date(record.crawlerStartedAt),
|
|
104
|
+
crawlerFinishedAt: record.crawlerFinishedAt === null ? null : new Date(record.crawlerFinishedAt),
|
|
105
|
+
statsPersistedAt: new Date(record.statsPersistedAt),
|
|
106
|
+
// Rebased so that the runtime reported by `calculate()` spans the migration instead of restarting.
|
|
107
|
+
instanceStart: Date.now() - (new Date(record.statsPersistedAt).getTime() - record.crawlerLastStartTimestamp),
|
|
108
|
+
}),
|
|
109
|
+
encode: (state) => {
|
|
110
|
+
const { requestsWithStatusCode, errors, retryErrors, requestRetryHistogram, instanceStart, ...counters } = state;
|
|
111
|
+
// Every rate and average `calculate()` derives is `Infinity` until the run is long enough, or until
|
|
112
|
+
// something has finished or failed, to divide by.
|
|
113
|
+
const { requestAvgFailedDurationMillis, requestAvgFinishedDurationMillis, requestsFailedPerMinute, requestsFinishedPerMinute, ...aggregates } = statistics.calculate();
|
|
114
|
+
return {
|
|
115
|
+
...counters,
|
|
116
|
+
requestMinDurationMillis: finiteOrNull(state.requestMinDurationMillis),
|
|
117
|
+
crawlerStartedAt: state.crawlerStartedAt ? new Date(state.crawlerStartedAt).toISOString() : null,
|
|
118
|
+
crawlerFinishedAt: state.crawlerFinishedAt ? new Date(state.crawlerFinishedAt).toISOString() : null,
|
|
119
|
+
statsPersistedAt: new Date().toISOString(),
|
|
120
|
+
crawlerLastStartTimestamp: instanceStart,
|
|
121
|
+
// `Array.from`, not `map` - a hole left by a retry count no request reached is skipped by `map`
|
|
122
|
+
// and would stay a hole, which is not something the record can carry.
|
|
123
|
+
requestRetryHistogram: Array.from(requestRetryHistogram, (count) => count ?? null),
|
|
124
|
+
statsId: statistics.statsId,
|
|
125
|
+
...aggregates,
|
|
126
|
+
requestAvgFailedDurationMillis: finiteOrNull(requestAvgFailedDurationMillis),
|
|
127
|
+
requestAvgFinishedDurationMillis: finiteOrNull(requestAvgFinishedDurationMillis),
|
|
128
|
+
requestsFailedPerMinute: finiteOrNull(requestsFailedPerMinute),
|
|
129
|
+
requestsFinishedPerMinute: finiteOrNull(requestsFinishedPerMinute),
|
|
130
|
+
requestsWithStatusCode,
|
|
131
|
+
errors,
|
|
132
|
+
retryErrors,
|
|
133
|
+
};
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
}
|
|
28
137
|
/**
|
|
29
138
|
* The statistics class provides an interface to collecting and logging run
|
|
30
139
|
* statistics for requests.
|
|
31
140
|
*
|
|
32
141
|
* All statistic information is saved on key value store
|
|
33
|
-
* under the key `
|
|
142
|
+
* under the key `CRAWLEE_CRAWLER_STATISTICS_*`, persists between
|
|
34
143
|
* migrations and abort/resurrect
|
|
35
144
|
*
|
|
36
145
|
* @category Crawlers
|
|
37
146
|
*/
|
|
38
147
|
export class Statistics {
|
|
148
|
+
// kept as TS-private: statistics tests read the static counter directly
|
|
39
149
|
static id = 0;
|
|
40
150
|
/**
|
|
41
151
|
* An error tracker for final retry errors.
|
|
@@ -48,32 +158,30 @@ export class Statistics {
|
|
|
48
158
|
/**
|
|
49
159
|
* Statistic instance id.
|
|
50
160
|
*/
|
|
51
|
-
id
|
|
161
|
+
id;
|
|
162
|
+
persistStateKey;
|
|
163
|
+
#stateCodec;
|
|
164
|
+
#recoverableState;
|
|
165
|
+
#logIntervalMillis;
|
|
166
|
+
#logMessage;
|
|
167
|
+
#requestsInProgress = new Map();
|
|
168
|
+
log;
|
|
169
|
+
#logInterval;
|
|
52
170
|
/**
|
|
53
171
|
* Current statistic state used for doing calculations on {@link Statistics.calculate} calls
|
|
54
172
|
*/
|
|
55
|
-
state
|
|
173
|
+
get state() {
|
|
174
|
+
return this.#recoverableState.currentValue;
|
|
175
|
+
}
|
|
56
176
|
/**
|
|
57
177
|
* Contains the current retries histogram. Index 0 means 0 retries, index 2, 2 retries, and so on
|
|
58
178
|
*/
|
|
59
|
-
requestRetryHistogram
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
*/
|
|
63
|
-
config;
|
|
64
|
-
keyValueStore = undefined;
|
|
65
|
-
persistStateKey = `SDK_CRAWLER_STATISTICS_${this.id}`;
|
|
66
|
-
logIntervalMillis;
|
|
67
|
-
logMessage;
|
|
68
|
-
listener;
|
|
69
|
-
requestsInProgress = new Map();
|
|
70
|
-
log;
|
|
71
|
-
instanceStart;
|
|
72
|
-
logInterval;
|
|
73
|
-
events;
|
|
74
|
-
persistenceOptions;
|
|
179
|
+
get requestRetryHistogram() {
|
|
180
|
+
return this.state.requestRetryHistogram;
|
|
181
|
+
}
|
|
75
182
|
/**
|
|
76
|
-
*
|
|
183
|
+
* Construct a statistics instance to pass to a crawler via its `statistics` option, e.g. to preconfigure
|
|
184
|
+
* persistence or error snapshots, share it across sequential runs, or subclass it to track extra fields.
|
|
77
185
|
*/
|
|
78
186
|
constructor(options = {}) {
|
|
79
187
|
ow(options, ow.object.exactShape({
|
|
@@ -81,33 +189,56 @@ export class Statistics {
|
|
|
81
189
|
logMessage: ow.optional.string,
|
|
82
190
|
log: ow.optional.object,
|
|
83
191
|
keyValueStore: ow.optional.object,
|
|
84
|
-
config: ow.optional.object,
|
|
85
192
|
persistenceOptions: ow.optional.object,
|
|
86
193
|
saveErrorSnapshots: ow.optional.boolean,
|
|
194
|
+
id: ow.optional.any(ow.number, ow.string),
|
|
87
195
|
}));
|
|
88
|
-
const { logIntervalSecs = 60, logMessage = 'Statistics', keyValueStore,
|
|
196
|
+
const { logIntervalSecs = 60, logMessage = 'Statistics', keyValueStore, persistenceOptions = {
|
|
89
197
|
enable: true,
|
|
90
|
-
}, saveErrorSnapshots = false, } = options;
|
|
91
|
-
this.
|
|
198
|
+
}, saveErrorSnapshots = false, id, } = options;
|
|
199
|
+
this.id = id ?? String(Statistics.id++);
|
|
200
|
+
this.persistStateKey = `CRAWLEE_CRAWLER_STATISTICS_${this.id}`;
|
|
201
|
+
this.log = (options.log ?? serviceLocator.getLogger()).child({ prefix: 'Statistics' });
|
|
92
202
|
this.errorTracker = new ErrorTracker({ ...errorTrackerConfig, saveErrorSnapshots });
|
|
93
203
|
this.errorTrackerRetry = new ErrorTracker({ ...errorTrackerConfig, saveErrorSnapshots });
|
|
94
|
-
this
|
|
95
|
-
this
|
|
96
|
-
|
|
97
|
-
this
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
204
|
+
this.#logIntervalMillis = logIntervalSecs * 1000;
|
|
205
|
+
this.#logMessage = logMessage;
|
|
206
|
+
// Late-bound on purpose - both hooks are override points, and a subclass's must be the ones that run.
|
|
207
|
+
this.#stateCodec = buildStatisticStateCodec({
|
|
208
|
+
statsId: this.id,
|
|
209
|
+
defaultState: () => this.defaultState(),
|
|
210
|
+
calculate: () => this.calculate(),
|
|
211
|
+
});
|
|
212
|
+
this.#recoverableState = new RecoverableState({
|
|
213
|
+
persistStateKey: this.persistStateKey,
|
|
214
|
+
persistenceEnabled: persistenceOptions.enable,
|
|
215
|
+
keyValueStore,
|
|
216
|
+
logger: this.log,
|
|
217
|
+
defaultState: () => this.defaultState(),
|
|
218
|
+
serialize: (state) => this.serializeState(state),
|
|
219
|
+
deserialize: (persistedState) => this.deserializeState(persistedState),
|
|
220
|
+
});
|
|
101
221
|
// initialize by "resetting"
|
|
102
222
|
this.reset();
|
|
103
223
|
}
|
|
104
224
|
/**
|
|
105
|
-
* Set the current statistic instance to pristine values
|
|
225
|
+
* Set the current statistic instance to pristine values.
|
|
226
|
+
*
|
|
227
|
+
* The persisted record is left alone - use {@link Statistics.resetStore} to clear that as well.
|
|
106
228
|
*/
|
|
107
229
|
reset() {
|
|
108
230
|
this.errorTracker.reset();
|
|
109
231
|
this.errorTrackerRetry.reset();
|
|
110
|
-
this.
|
|
232
|
+
this.#recoverableState.reset();
|
|
233
|
+
this.#requestsInProgress.clear();
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* The pristine state a new instance starts with and {@link Statistics.reset} restores.
|
|
237
|
+
*
|
|
238
|
+
* A subclass tracking extra fields declares their initial values here.
|
|
239
|
+
*/
|
|
240
|
+
defaultState() {
|
|
241
|
+
return {
|
|
111
242
|
requestsFinished: 0,
|
|
112
243
|
requestsFailed: 0,
|
|
113
244
|
requestsRetries: 0,
|
|
@@ -122,25 +253,20 @@ export class Statistics {
|
|
|
122
253
|
statsPersistedAt: null,
|
|
123
254
|
crawlerRuntimeMillis: 0,
|
|
124
255
|
requestsWithStatusCode: {},
|
|
256
|
+
// Aliases, not copies - the trackers keep writing into these objects.
|
|
125
257
|
errors: this.errorTracker.result,
|
|
126
258
|
retryErrors: this.errorTrackerRetry.result,
|
|
259
|
+
requestRetryHistogram: [],
|
|
260
|
+
instanceStart: Date.now(),
|
|
127
261
|
};
|
|
128
|
-
this.requestRetryHistogram.length = 0;
|
|
129
|
-
this.requestsInProgress.clear();
|
|
130
|
-
this.instanceStart = Date.now();
|
|
131
|
-
this._teardown();
|
|
132
262
|
}
|
|
133
263
|
/**
|
|
134
|
-
*
|
|
264
|
+
* Clear the persisted statistics record, leaving the in-memory state alone.
|
|
265
|
+
*
|
|
266
|
+
* Throws while capturing - the next PERSIST_STATE event would write the record straight back.
|
|
135
267
|
*/
|
|
136
|
-
async resetStore(
|
|
137
|
-
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
if (!this.keyValueStore) {
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
await this.keyValueStore.setValue(this.persistStateKey, null);
|
|
268
|
+
async resetStore() {
|
|
269
|
+
await this.#recoverableState.resetStore();
|
|
144
270
|
}
|
|
145
271
|
/**
|
|
146
272
|
* Increments the status code counter.
|
|
@@ -157,49 +283,57 @@ export class Statistics {
|
|
|
157
283
|
* @ignore
|
|
158
284
|
*/
|
|
159
285
|
startJob(id) {
|
|
160
|
-
let job = this
|
|
286
|
+
let job = this.#requestsInProgress.get(id);
|
|
161
287
|
if (!job)
|
|
162
288
|
job = new Job();
|
|
163
289
|
job.run();
|
|
164
|
-
this
|
|
290
|
+
this.#requestsInProgress.set(id, job);
|
|
165
291
|
}
|
|
166
292
|
/**
|
|
167
293
|
* Mark job as finished and sets the state
|
|
168
294
|
* @ignore
|
|
169
295
|
*/
|
|
170
296
|
finishJob(id, retryCount) {
|
|
171
|
-
const job = this
|
|
297
|
+
const job = this.#requestsInProgress.get(id);
|
|
172
298
|
if (!job)
|
|
173
299
|
return;
|
|
174
300
|
const jobDurationMillis = job.finish();
|
|
175
301
|
this.state.requestsFinished++;
|
|
176
302
|
this.state.requestTotalFinishedDurationMillis += jobDurationMillis;
|
|
177
|
-
this.
|
|
303
|
+
this.saveRetryCountForJob(retryCount);
|
|
178
304
|
if (jobDurationMillis < this.state.requestMinDurationMillis)
|
|
179
305
|
this.state.requestMinDurationMillis = jobDurationMillis;
|
|
180
306
|
if (jobDurationMillis > this.state.requestMaxDurationMillis)
|
|
181
307
|
this.state.requestMaxDurationMillis = jobDurationMillis;
|
|
182
|
-
this
|
|
308
|
+
this.#requestsInProgress.delete(id);
|
|
183
309
|
}
|
|
184
310
|
/**
|
|
185
311
|
* Mark job as failed and sets the state
|
|
186
312
|
* @ignore
|
|
187
313
|
*/
|
|
188
314
|
failJob(id, retryCount) {
|
|
189
|
-
const job = this
|
|
315
|
+
const job = this.#requestsInProgress.get(id);
|
|
190
316
|
if (!job)
|
|
191
317
|
return;
|
|
192
318
|
this.state.requestTotalFailedDurationMillis += job.finish();
|
|
193
319
|
this.state.requestsFailed++;
|
|
194
|
-
this.
|
|
195
|
-
this
|
|
320
|
+
this.saveRetryCountForJob(retryCount);
|
|
321
|
+
this.#requestsInProgress.delete(id);
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Discards a started job without affecting the finished/failed counters, e.g. when a request
|
|
325
|
+
* turns out to be skipped (robots.txt, enqueue strategy) after `startJob` was already called for it.
|
|
326
|
+
* @ignore
|
|
327
|
+
*/
|
|
328
|
+
discardJob(id) {
|
|
329
|
+
this.#requestsInProgress.delete(id);
|
|
196
330
|
}
|
|
197
331
|
/**
|
|
198
332
|
* Calculate the current statistics
|
|
199
333
|
*/
|
|
200
334
|
calculate() {
|
|
201
335
|
const { requestsFailed, requestsFinished, requestTotalFailedDurationMillis, requestTotalFinishedDurationMillis, } = this.state;
|
|
202
|
-
const totalMillis = Date.now() - this.instanceStart;
|
|
336
|
+
const totalMillis = Date.now() - this.state.instanceStart;
|
|
203
337
|
const totalMinutes = totalMillis / 1000 / 60;
|
|
204
338
|
return {
|
|
205
339
|
requestAvgFailedDurationMillis: Math.round(requestTotalFailedDurationMillis / requestsFailed) || Infinity,
|
|
@@ -216,131 +350,87 @@ export class Statistics {
|
|
|
216
350
|
* displaying the current state in predefined intervals
|
|
217
351
|
*/
|
|
218
352
|
async startCapturing() {
|
|
219
|
-
|
|
353
|
+
// A single instance drives one logging interval and one PERSIST_STATE listener, so a second concurrent
|
|
354
|
+
// capture (e.g. sharing one instance across crawlers running at once) would orphan the first. Fail loudly.
|
|
355
|
+
if (this.#logInterval) {
|
|
356
|
+
throw new Error('Statistics.startCapturing() was already called - this instance is already capturing.');
|
|
357
|
+
}
|
|
358
|
+
await this.#recoverableState.initialize();
|
|
359
|
+
// After the load, so that a restored record keeps the timestamp of the run it belongs to.
|
|
220
360
|
if (this.state.crawlerStartedAt === null) {
|
|
221
361
|
this.state.crawlerStartedAt = new Date();
|
|
222
362
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
this.events.on("persistState" /* EventType.PERSIST_STATE */, this.listener);
|
|
226
|
-
}
|
|
227
|
-
this.logInterval = setInterval(() => {
|
|
228
|
-
this.log.info(this.logMessage, {
|
|
363
|
+
this.#logInterval = setInterval(() => {
|
|
364
|
+
this.log.info(this.#logMessage, {
|
|
229
365
|
...this.calculate(),
|
|
230
366
|
retryHistogram: this.requestRetryHistogram,
|
|
231
367
|
});
|
|
232
|
-
}, this
|
|
368
|
+
}, this.#logIntervalMillis);
|
|
233
369
|
}
|
|
234
370
|
/**
|
|
235
371
|
* Stops logging and remove event listeners, then persist
|
|
236
372
|
*/
|
|
237
373
|
async stopCapturing() {
|
|
238
|
-
this
|
|
374
|
+
this.#stopLogging();
|
|
239
375
|
this.state.crawlerFinishedAt = new Date();
|
|
240
|
-
await this.
|
|
376
|
+
await this.#recoverableState.teardown();
|
|
241
377
|
}
|
|
242
|
-
|
|
378
|
+
saveRetryCountForJob(retryCount) {
|
|
243
379
|
if (retryCount > 0)
|
|
244
380
|
this.state.requestsRetries++;
|
|
245
381
|
this.requestRetryHistogram[retryCount] ??= 0;
|
|
246
382
|
this.requestRetryHistogram[retryCount]++;
|
|
247
383
|
}
|
|
248
384
|
/**
|
|
249
|
-
* Persist internal state to the key value store
|
|
250
|
-
*
|
|
385
|
+
* Persist internal state to the key value store.
|
|
386
|
+
*
|
|
387
|
+
* Statistics are bookkeeping - a store that refuses the write is worth a warning, not a failed crawl. The
|
|
388
|
+
* crawler calls this from its migration handler, where a rejection would go unhandled.
|
|
251
389
|
*/
|
|
252
|
-
async persistState(
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}
|
|
256
|
-
// this might be called before startCapturing was called without using await, should not crash
|
|
257
|
-
if (!this.keyValueStore) {
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
this.log.debug('Persisting state', { persistStateKey: this.persistStateKey });
|
|
261
|
-
// use half the interval of `persistState` to avoid race conditions
|
|
262
|
-
const persistStateIntervalMillis = this.config.get('persistStateIntervalMillis');
|
|
263
|
-
const timeoutSecs = persistStateIntervalMillis / 2_000;
|
|
264
|
-
await this.keyValueStore
|
|
265
|
-
.setValue(this.persistStateKey, this.toJSON(), {
|
|
266
|
-
timeoutSecs,
|
|
267
|
-
doNotRetryTimeouts: true,
|
|
268
|
-
})
|
|
390
|
+
async persistState() {
|
|
391
|
+
await this.#recoverableState
|
|
392
|
+
.persistState()
|
|
269
393
|
.catch((error) => this.log.warning(`Failed to persist the statistics to ${this.persistStateKey}`, { error }));
|
|
270
394
|
}
|
|
271
395
|
/**
|
|
272
|
-
*
|
|
396
|
+
* Rebuilds the state from a persisted record.
|
|
397
|
+
*
|
|
398
|
+
* A subclass tracking extra fields restores them here, on top of the result of `super.deserializeState()`.
|
|
273
399
|
*/
|
|
274
|
-
|
|
275
|
-
//
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
// We saw a run where the requestRetryHistogram was not iterable and crashed
|
|
283
|
-
// the crawler. Adding some logging to monitor this problem in the future.
|
|
284
|
-
if (!Array.isArray(savedState.requestRetryHistogram)) {
|
|
285
|
-
this.log.warning('Received invalid state from Key-value store.', {
|
|
400
|
+
deserializeState(persistedState) {
|
|
401
|
+
// The cast is the index signature the `catchall` puts on the schema and an interface cannot have - the
|
|
402
|
+
// record is an unvalidated blob off the key-value store either way, which is what the decode is for.
|
|
403
|
+
const restored = z.safeDecode(this.#stateCodec, persistedState);
|
|
404
|
+
if (!restored.success) {
|
|
405
|
+
// Statistics are bookkeeping - a record that cannot be made sense of is worth a warning and a fresh
|
|
406
|
+
// start, not a failed crawl.
|
|
407
|
+
this.log.warning('Received invalid state from Key-value store, starting the statistics from scratch.', {
|
|
286
408
|
persistStateKey: this.persistStateKey,
|
|
287
|
-
|
|
409
|
+
issues: restored.error.issues,
|
|
288
410
|
});
|
|
411
|
+
return this.defaultState();
|
|
289
412
|
}
|
|
290
413
|
this.log.debug('Recreating state from KeyValueStore', { persistStateKey: this.persistStateKey });
|
|
291
|
-
|
|
292
|
-
// `RangeError: Maximum call stack size exceeded` if we use `a.push(...b)`
|
|
293
|
-
savedState.requestRetryHistogram.forEach((idx) => this.requestRetryHistogram.push(idx));
|
|
294
|
-
this.state.requestsFinished = savedState.requestsFinished;
|
|
295
|
-
this.state.requestsFailed = savedState.requestsFailed;
|
|
296
|
-
this.state.requestsRetries = savedState.requestsRetries;
|
|
297
|
-
this.state.requestTotalFailedDurationMillis = savedState.requestTotalFailedDurationMillis;
|
|
298
|
-
this.state.requestTotalFinishedDurationMillis = savedState.requestTotalFinishedDurationMillis;
|
|
299
|
-
this.state.requestMinDurationMillis = savedState.requestMinDurationMillis;
|
|
300
|
-
this.state.requestMaxDurationMillis = savedState.requestMaxDurationMillis;
|
|
301
|
-
// persisted state uses ISO date strings
|
|
302
|
-
this.state.crawlerFinishedAt = savedState.crawlerFinishedAt ? new Date(savedState.crawlerFinishedAt) : null;
|
|
303
|
-
this.state.crawlerStartedAt = savedState.crawlerStartedAt ? new Date(savedState.crawlerStartedAt) : null;
|
|
304
|
-
this.state.statsPersistedAt = savedState.statsPersistedAt ? new Date(savedState.statsPersistedAt) : null;
|
|
305
|
-
this.state.crawlerRuntimeMillis = savedState.crawlerRuntimeMillis;
|
|
306
|
-
this.instanceStart = Date.now() - (+this.state.statsPersistedAt - savedState.crawlerLastStartTimestamp);
|
|
307
|
-
this.log.debug('Loaded from KeyValueStore');
|
|
414
|
+
return { ...this.defaultState(), ...restored.data };
|
|
308
415
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
clearInterval(this.logInterval);
|
|
314
|
-
this.logInterval = null;
|
|
416
|
+
#stopLogging() {
|
|
417
|
+
if (this.#logInterval) {
|
|
418
|
+
clearInterval(this.#logInterval);
|
|
419
|
+
this.#logInterval = null;
|
|
315
420
|
}
|
|
316
421
|
}
|
|
422
|
+
/**
|
|
423
|
+
* Builds the record written to the key value store, merging in the derived aggregates so that a consumer
|
|
424
|
+
* reading the record does not have to reconstruct them.
|
|
425
|
+
*/
|
|
426
|
+
serializeState(state) {
|
|
427
|
+
return z.encode(this.#stateCodec, state);
|
|
428
|
+
}
|
|
317
429
|
/**
|
|
318
430
|
* Make this class serializable when called with `JSON.stringify(statsInstance)` directly
|
|
319
431
|
* or through `keyValueStore.setValue('KEY', statsInstance)`
|
|
320
432
|
*/
|
|
321
433
|
toJSON() {
|
|
322
|
-
|
|
323
|
-
// without the need to reconstruct for the sake of stats.calculate()
|
|
324
|
-
// omit duplicated information
|
|
325
|
-
const result = {
|
|
326
|
-
...this.state,
|
|
327
|
-
crawlerLastStartTimestamp: this.instanceStart,
|
|
328
|
-
crawlerFinishedAt: this.state.crawlerFinishedAt
|
|
329
|
-
? new Date(this.state.crawlerFinishedAt).toISOString()
|
|
330
|
-
: null,
|
|
331
|
-
crawlerStartedAt: this.state.crawlerStartedAt ? new Date(this.state.crawlerStartedAt).toISOString() : null,
|
|
332
|
-
requestRetryHistogram: this.requestRetryHistogram,
|
|
333
|
-
statsId: this.id,
|
|
334
|
-
statsPersistedAt: new Date().toISOString(),
|
|
335
|
-
...this.calculate(),
|
|
336
|
-
};
|
|
337
|
-
Reflect.deleteProperty(result, 'requestsWithStatusCode');
|
|
338
|
-
Reflect.deleteProperty(result, 'errors');
|
|
339
|
-
Reflect.deleteProperty(result, 'retryErrors');
|
|
340
|
-
result.requestsWithStatusCode = this.state.requestsWithStatusCode;
|
|
341
|
-
result.errors = this.state.errors;
|
|
342
|
-
result.retryErrors = this.state.retryErrors;
|
|
343
|
-
return result;
|
|
434
|
+
return this.serializeState(this.state);
|
|
344
435
|
}
|
|
345
436
|
}
|
|
346
|
-
//# sourceMappingURL=statistics.js.map
|
package/debug.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { IncomingMessage } from 'node:http';
|
|
2
|
+
import type { Dictionary } from '@crawlee/types';
|
|
3
|
+
import type { Request } from './request.js';
|
|
4
|
+
interface BrowserResponseLike {
|
|
5
|
+
status(): number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Creates a standardized debug info from request and response. This info is usually added to dataset under the hidden `#debug` field.
|
|
9
|
+
*
|
|
10
|
+
* @param request [Request](https://sdk.apify.com/docs/api/request) object.
|
|
11
|
+
* @param [response]
|
|
12
|
+
* Puppeteer [`Response`](https://pptr.dev/#?product=Puppeteer&version=v1.11.0&show=api-class-response)
|
|
13
|
+
* or NodeJS [`http.IncomingMessage`](https://nodejs.org/api/http.html#http_class_http_serverresponse).
|
|
14
|
+
* @param [additionalFields] Object containing additional fields to be added.
|
|
15
|
+
*
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
export declare function createRequestDebugInfo(request: Request, response?: IncomingMessage | Partial<BrowserResponseLike>, additionalFields?: Dictionary): Dictionary;
|
|
19
|
+
/**
|
|
20
|
+
* Returns a human-readable label for an unknown value,
|
|
21
|
+
* suitable for embedding in error messages and log output.
|
|
22
|
+
*
|
|
23
|
+
* Returns `constructor.name` when available (e.g. `"Configuration"`, `"Number"`),
|
|
24
|
+
* otherwise falls back to `util.inspect` (e.g. for `null`, `undefined`).
|
|
25
|
+
*
|
|
26
|
+
* @internal
|
|
27
|
+
*/
|
|
28
|
+
export declare function inspectValue(value: unknown): string;
|
|
29
|
+
/**
|
|
30
|
+
* Returns the type of a value as a lowercase string, with `Date`, `Buffer` and `RegExp` reported
|
|
31
|
+
* by their constructor name. Used for building validation error messages.
|
|
32
|
+
*
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
export declare function getObjectType(value: unknown): string;
|
|
36
|
+
export {};
|
package/debug.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { inspect } from 'node:util';
|
|
2
|
+
import ow from 'ow';
|
|
3
|
+
/**
|
|
4
|
+
* Creates a standardized debug info from request and response. This info is usually added to dataset under the hidden `#debug` field.
|
|
5
|
+
*
|
|
6
|
+
* @param request [Request](https://sdk.apify.com/docs/api/request) object.
|
|
7
|
+
* @param [response]
|
|
8
|
+
* Puppeteer [`Response`](https://pptr.dev/#?product=Puppeteer&version=v1.11.0&show=api-class-response)
|
|
9
|
+
* or NodeJS [`http.IncomingMessage`](https://nodejs.org/api/http.html#http_class_http_serverresponse).
|
|
10
|
+
* @param [additionalFields] Object containing additional fields to be added.
|
|
11
|
+
*
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
export function createRequestDebugInfo(request, response = {}, additionalFields = {}) {
|
|
15
|
+
ow(request, ow.object);
|
|
16
|
+
ow(response, ow.object);
|
|
17
|
+
ow(additionalFields, ow.object);
|
|
18
|
+
return {
|
|
19
|
+
requestId: request.id,
|
|
20
|
+
url: request.url,
|
|
21
|
+
loadedUrl: request.loadedUrl,
|
|
22
|
+
method: request.method,
|
|
23
|
+
retryCount: request.retryCount,
|
|
24
|
+
errorMessages: request.errorMessages,
|
|
25
|
+
// Puppeteer response has .status() function and NodeJS response, statusCode property.
|
|
26
|
+
statusCode: 'status' in response && response.status instanceof Function
|
|
27
|
+
? response.status()
|
|
28
|
+
: response.statusCode,
|
|
29
|
+
...additionalFields,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Returns a human-readable label for an unknown value,
|
|
34
|
+
* suitable for embedding in error messages and log output.
|
|
35
|
+
*
|
|
36
|
+
* Returns `constructor.name` when available (e.g. `"Configuration"`, `"Number"`),
|
|
37
|
+
* otherwise falls back to `util.inspect` (e.g. for `null`, `undefined`).
|
|
38
|
+
*
|
|
39
|
+
* @internal
|
|
40
|
+
*/
|
|
41
|
+
export function inspectValue(value) {
|
|
42
|
+
if (typeof value === 'object' && value !== null && value.constructor?.name) {
|
|
43
|
+
return value.constructor.name;
|
|
44
|
+
}
|
|
45
|
+
return inspect(value, {
|
|
46
|
+
depth: 0,
|
|
47
|
+
compact: true,
|
|
48
|
+
maxStringLength: 64,
|
|
49
|
+
breakLength: Infinity,
|
|
50
|
+
colors: false,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Returns the type of a value as a lowercase string, with `Date`, `Buffer` and `RegExp` reported
|
|
55
|
+
* by their constructor name. Used for building validation error messages.
|
|
56
|
+
*
|
|
57
|
+
* @internal
|
|
58
|
+
*/
|
|
59
|
+
export function getObjectType(value) {
|
|
60
|
+
const simple = typeof value;
|
|
61
|
+
if (['string', 'number', 'boolean', 'bigint'].includes(simple)) {
|
|
62
|
+
return simple;
|
|
63
|
+
}
|
|
64
|
+
const objectType = Object.prototype.toString.call(value);
|
|
65
|
+
const type = /\[object (\w+)]/.exec(objectType)[1];
|
|
66
|
+
if (type === 'Uint8Array') {
|
|
67
|
+
return 'Buffer';
|
|
68
|
+
}
|
|
69
|
+
return ['Date', 'Buffer', 'RegExp'].includes(type) ? type : type.toLowerCase();
|
|
70
|
+
}
|