apify-client 3.0.0-beta.8 → 3.0.0-rc.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 +10 -2
- package/dist/apify_api_error.d.ts +11 -26
- package/dist/apify_api_error.js +31 -22
- package/dist/apify_client.d.ts +125 -10
- package/dist/apify_client.js +133 -48
- package/dist/base/api_client.d.ts +7 -7
- package/dist/base/api_client.js +36 -24
- package/dist/base/resource_client.d.ts +21 -8
- package/dist/base/resource_client.js +67 -33
- package/dist/base/resource_collection_client.d.ts +12 -4
- package/dist/base/resource_collection_client.js +34 -11
- package/dist/body_parser.d.ts +3 -4
- package/dist/body_parser.js +19 -26
- package/dist/bundle.js +27 -22
- package/dist/bundle.js.map +1 -1
- package/dist/generated/api.d.ts +785 -290
- package/dist/generated/schemas.d.ts +720 -747
- package/dist/generated/schemas.js +166 -162
- package/dist/http_clients/axios.d.ts +60 -0
- package/dist/http_clients/axios.js +117 -0
- package/dist/http_clients/base.d.ts +331 -0
- package/dist/http_clients/base.js +563 -0
- package/dist/http_clients/index.d.ts +4 -0
- package/dist/http_clients/index.js +2 -0
- package/dist/http_compressors/base.d.ts +40 -0
- package/dist/http_compressors/base.js +9 -0
- package/dist/http_compressors/brotli.d.ts +33 -0
- package/dist/http_compressors/brotli.js +39 -0
- package/dist/http_compressors/gzip.d.ts +33 -0
- package/dist/http_compressors/gzip.js +39 -0
- package/dist/http_compressors/index.d.ts +6 -0
- package/dist/http_compressors/index.js +2 -0
- package/dist/http_compressors/resolve.d.ts +23 -0
- package/dist/http_compressors/resolve.js +27 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +3 -1
- package/dist/invalid_response_body_error.d.ts +13 -0
- package/dist/invalid_response_body_error.js +17 -0
- package/dist/models.d.ts +162 -194
- package/dist/models.js +6 -4
- package/dist/resource_clients/actor.d.ts +74 -34
- package/dist/resource_clients/actor.js +87 -56
- package/dist/resource_clients/actor_collection.d.ts +18 -12
- package/dist/resource_clients/actor_collection.js +11 -3
- package/dist/resource_clients/actor_env_var.d.ts +11 -4
- package/dist/resource_clients/actor_env_var.js +16 -6
- package/dist/resource_clients/actor_env_var_collection.d.ts +9 -4
- package/dist/resource_clients/actor_env_var_collection.js +11 -4
- package/dist/resource_clients/actor_version.d.ts +20 -5
- package/dist/resource_clients/actor_version.js +19 -9
- package/dist/resource_clients/actor_version_collection.d.ts +11 -4
- package/dist/resource_clients/actor_version_collection.js +13 -4
- package/dist/resource_clients/build.d.ts +20 -7
- package/dist/resource_clients/build.js +35 -15
- package/dist/resource_clients/build_collection.d.ts +7 -5
- package/dist/resource_clients/build_collection.js +4 -1
- package/dist/resource_clients/dataset.d.ts +53 -35
- package/dist/resource_clients/dataset.js +105 -80
- package/dist/resource_clients/dataset_collection.d.ts +10 -6
- package/dist/resource_clients/dataset_collection.js +13 -2
- package/dist/resource_clients/key_value_store.d.ts +62 -20
- package/dist/resource_clients/key_value_store.js +78 -40
- package/dist/resource_clients/key_value_store_collection.d.ts +10 -6
- package/dist/resource_clients/key_value_store_collection.js +13 -2
- package/dist/resource_clients/log.d.ts +13 -20
- package/dist/resource_clients/log.js +141 -59
- package/dist/resource_clients/request_queue.d.ts +68 -31
- package/dist/resource_clients/request_queue.js +173 -91
- package/dist/resource_clients/request_queue_collection.d.ts +9 -5
- package/dist/resource_clients/request_queue_collection.js +9 -3
- package/dist/resource_clients/run.d.ts +119 -28
- package/dist/resource_clients/run.js +190 -49
- package/dist/resource_clients/run_collection.d.ts +5 -3
- package/dist/resource_clients/run_collection.js +4 -1
- package/dist/resource_clients/schedule.d.ts +16 -7
- package/dist/resource_clients/schedule.js +30 -22
- package/dist/resource_clients/schedule_collection.d.ts +6 -2
- package/dist/resource_clients/schedule_collection.js +9 -3
- package/dist/resource_clients/store_collection.d.ts +6 -4
- package/dist/resource_clients/store_collection.js +4 -1
- package/dist/resource_clients/task.d.ts +36 -13
- package/dist/resource_clients/task.js +79 -46
- package/dist/resource_clients/task_collection.d.ts +9 -5
- package/dist/resource_clients/task_collection.js +9 -3
- package/dist/resource_clients/user.d.ts +19 -10
- package/dist/resource_clients/user.js +42 -38
- package/dist/resource_clients/webhook.d.ts +17 -8
- package/dist/resource_clients/webhook.js +30 -22
- package/dist/resource_clients/webhook_collection.d.ts +8 -4
- package/dist/resource_clients/webhook_collection.js +9 -3
- package/dist/resource_clients/webhook_dispatch.d.ts +4 -1
- package/dist/resource_clients/webhook_dispatch.js +7 -2
- package/dist/resource_clients/webhook_dispatch_collection.d.ts +3 -1
- package/dist/resource_clients/webhook_dispatch_collection.js +4 -1
- package/dist/runtime/node.d.ts +6 -0
- package/dist/runtime/node.js +49 -0
- package/dist/runtime/types.d.ts +52 -0
- package/dist/runtime/types.js +1 -0
- package/dist/runtime/web.d.ts +5 -0
- package/dist/runtime/web.js +18 -0
- package/dist/schemas.d.ts +1 -1
- package/dist/schemas.js +1 -1
- package/dist/statistics.d.ts +1 -6
- package/dist/statistics.js +2 -2
- package/dist/timeouts.d.ts +84 -0
- package/dist/timeouts.js +37 -0
- package/dist/timezones.d.ts +1 -1
- package/dist/utils.d.ts +82 -45
- package/dist/utils.js +197 -146
- package/dist/wait_for_resources.d.ts +8 -0
- package/dist/wait_for_resources.js +39 -0
- package/package.json +32 -29
- package/dist/http_client.d.ts +0 -88
- package/dist/http_client.js +0 -255
- package/dist/interceptors.d.ts +0 -19
- package/dist/interceptors.js +0 -105
|
@@ -2,12 +2,14 @@ import { z } from 'zod';
|
|
|
2
2
|
import { STORAGE_OWNERSHIP_FILTER } from '@apify/consts';
|
|
3
3
|
import { ResourceCollectionClient } from '../base/resource_collection_client.js';
|
|
4
4
|
import * as schemas from '../schemas.js';
|
|
5
|
+
import { optionalSignalSchema, optionalTimeoutSchema, timeoutOptionsShape } from '../timeouts.js';
|
|
5
6
|
import { anyObjectSchema, paginationOptionsShape, parseArgument } from '../utils.js';
|
|
6
7
|
const listOptionsSchema = z.strictObject({
|
|
7
8
|
unnamed: z.boolean().optional(),
|
|
8
9
|
...paginationOptionsShape,
|
|
9
10
|
desc: z.boolean().optional(),
|
|
10
11
|
ownership: z.enum(STORAGE_OWNERSHIP_FILTER).optional(),
|
|
12
|
+
...timeoutOptionsShape,
|
|
11
13
|
});
|
|
12
14
|
const nameSchema = z.string().optional();
|
|
13
15
|
const schemaSchema = anyObjectSchema.optional();
|
|
@@ -58,24 +60,33 @@ export class KeyValueStoreCollectionClient extends ResourceCollectionClient {
|
|
|
58
60
|
* ```
|
|
59
61
|
*
|
|
60
62
|
* @param options - Pagination options.
|
|
63
|
+
* @param options.timeoutSecs - Timeout for each API request. Default is `'medium'`.
|
|
61
64
|
* @returns A paginated iterator of Key-value stores.
|
|
62
65
|
* @see https://docs.apify.com/api/v2/key-value-stores-get
|
|
63
66
|
*/
|
|
64
67
|
list(options = {}) {
|
|
65
68
|
const parsed = parseArgument(options, listOptionsSchema, 'KeyValueStoreCollectionClientListOptions');
|
|
66
|
-
return this.
|
|
69
|
+
return this.listResourcesPaginated(schemas.ListOfKeyValueStores(), parsed, 'medium');
|
|
67
70
|
}
|
|
68
71
|
/**
|
|
69
72
|
* Gets or creates a key-value store with the specified name.
|
|
70
73
|
*
|
|
71
74
|
* @param name - Name of the key-value store. If not provided, a default store is used.
|
|
72
75
|
* @param options - Additional options like schema.
|
|
76
|
+
* @param options.schema - Schema of the key-value store.
|
|
77
|
+
* @param options.timeoutSecs - Timeout for the API request. Default is `'short'`.
|
|
73
78
|
* @returns The key-value store object.
|
|
74
79
|
* @see https://docs.apify.com/api/v2/key-value-stores-post
|
|
75
80
|
*/
|
|
76
81
|
async getOrCreate(name, options) {
|
|
77
82
|
parseArgument(name, nameSchema);
|
|
78
83
|
parseArgument(options?.schema, schemaSchema); // TODO: Add schema validation
|
|
79
|
-
|
|
84
|
+
parseArgument(options?.timeoutSecs, optionalTimeoutSchema);
|
|
85
|
+
parseArgument(options?.signal, optionalSignalSchema);
|
|
86
|
+
// `timeoutSecs` and `signal` are not part of the resource, so the body carries only the rest, and stays
|
|
87
|
+
// absent when there is nothing else to send.
|
|
88
|
+
const { timeoutSecs = 'short', signal, ...resource } = options ?? {};
|
|
89
|
+
const hasResource = Object.keys(resource).length > 0;
|
|
90
|
+
return this.getOrCreateResource(schemas.KeyValueStoreResource(), name, hasResource ? resource : undefined, timeoutSecs, signal);
|
|
80
91
|
}
|
|
81
92
|
}
|
|
@@ -3,6 +3,7 @@ import type { Log } from '@apify/log';
|
|
|
3
3
|
import { Logger, LogLevel } from '@apify/log';
|
|
4
4
|
import type { ApiClientSubResourceOptions } from '../base/api_client.js';
|
|
5
5
|
import { ResourceClient } from '../base/resource_client.js';
|
|
6
|
+
import type { TimeoutOptions } from '../timeouts.js';
|
|
6
7
|
/**
|
|
7
8
|
* Client for accessing Actor run or build logs.
|
|
8
9
|
*
|
|
@@ -35,7 +36,9 @@ export declare class LogClient extends ResourceClient {
|
|
|
35
36
|
*
|
|
36
37
|
* @param options - Log retrieval options.
|
|
37
38
|
* @param options.raw - If `true`, returns raw log content without any processing. Default is `false`.
|
|
38
|
-
* @
|
|
39
|
+
* @param options.timeoutSecs - Timeout for the API request. Default is `'long'`.
|
|
40
|
+
* @returns The log content as a string, or `undefined` if it does not exist. A chained client such as
|
|
41
|
+
* `run.log()` throws an `ApifyApiError` on a 404, since the run itself may be what is missing.
|
|
39
42
|
* @see https://docs.apify.com/api/v2/log-get
|
|
40
43
|
*/
|
|
41
44
|
get(options?: LogOptions): Promise<string | undefined>;
|
|
@@ -44,7 +47,9 @@ export declare class LogClient extends ResourceClient {
|
|
|
44
47
|
*
|
|
45
48
|
* @param options - Log retrieval options.
|
|
46
49
|
* @param options.raw - If `true`, returns raw log content without any processing. Default is `false`.
|
|
47
|
-
* @
|
|
50
|
+
* @param options.timeoutSecs - Timeout for the API request. Default is `'long'`.
|
|
51
|
+
* @returns The log content as a Readable stream, or `undefined` if it does not exist. A chained client such as
|
|
52
|
+
* `run.log()` throws an `ApifyApiError` on a 404, since the run itself may be what is missing.
|
|
48
53
|
* @see https://docs.apify.com/api/v2/log-get
|
|
49
54
|
*/
|
|
50
55
|
stream(options?: LogOptions): Promise<Readable | undefined>;
|
|
@@ -52,7 +57,7 @@ export declare class LogClient extends ResourceClient {
|
|
|
52
57
|
/**
|
|
53
58
|
* @since Added in 2.20.0
|
|
54
59
|
*/
|
|
55
|
-
export interface LogOptions {
|
|
60
|
+
export interface LogOptions extends TimeoutOptions {
|
|
56
61
|
/** @default false */
|
|
57
62
|
raw?: boolean;
|
|
58
63
|
}
|
|
@@ -69,31 +74,17 @@ export declare class LoggerActorRedirect extends Logger {
|
|
|
69
74
|
* @since Added in 2.20.0
|
|
70
75
|
*/
|
|
71
76
|
export declare class StreamedLog {
|
|
72
|
-
private
|
|
73
|
-
private streamBuffer;
|
|
74
|
-
private splitMarker;
|
|
75
|
-
private relevancyTimeLimit;
|
|
76
|
-
private logClient;
|
|
77
|
-
private streamingTask;
|
|
78
|
-
private stopLogging;
|
|
77
|
+
#private;
|
|
79
78
|
constructor(options: StreamedLogOptions);
|
|
80
79
|
/**
|
|
81
80
|
* Start log redirection.
|
|
82
81
|
*/
|
|
83
82
|
start(): void;
|
|
84
83
|
/**
|
|
85
|
-
* Stop log redirection.
|
|
84
|
+
* Stop log redirection. Waits up to one second for lines already in flight, then aborts the log stream request. If
|
|
85
|
+
* no stream has delivered anything yet, reads the whole log in one request instead.
|
|
86
86
|
*/
|
|
87
87
|
stop(): Promise<void>;
|
|
88
|
-
/**
|
|
89
|
-
* Get log stream from response and redirect it to another log.
|
|
90
|
-
*/
|
|
91
|
-
private streamLog;
|
|
92
|
-
private logStreamChunks;
|
|
93
|
-
/**
|
|
94
|
-
* Parse the buffer and log complete messages.
|
|
95
|
-
*/
|
|
96
|
-
private logBufferContent;
|
|
97
88
|
}
|
|
98
89
|
/**
|
|
99
90
|
* @since Added in 2.20.0
|
|
@@ -105,4 +96,6 @@ export interface StreamedLogOptions {
|
|
|
105
96
|
toLog: Log;
|
|
106
97
|
/** Whether to redirect all logs from Actor run start (even logs from the past). */
|
|
107
98
|
fromStart?: boolean;
|
|
99
|
+
/** Ends the log stream once it aborts. */
|
|
100
|
+
signal?: AbortSignal;
|
|
108
101
|
}
|
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
import c from 'ansi-colors';
|
|
2
|
+
import { z } from 'zod';
|
|
2
3
|
import log, { Logger, LogLevel } from '@apify/log';
|
|
3
4
|
import { ResourceClient } from '../base/resource_client.js';
|
|
4
|
-
import {
|
|
5
|
+
import { timeoutOptionsShape } from '../timeouts.js';
|
|
6
|
+
import { cast, catchNotFoundForResourceOrThrow, concatBytes, parseArgument, sleep } from '../utils.js';
|
|
7
|
+
/**
|
|
8
|
+
* Pause before the first reopen of a log stream that ended before the run logged anything. Each further reopen doubles
|
|
9
|
+
* the pause up to `EMPTY_LOG_STREAM_MAX_RETRY_MILLIS`, which bounds the request rate while a run waits long to start,
|
|
10
|
+
* for example for free memory.
|
|
11
|
+
*/
|
|
12
|
+
const EMPTY_LOG_STREAM_RETRY_MILLIS = 500;
|
|
13
|
+
/** Upper bound on the pause between reopens of an empty log stream. */
|
|
14
|
+
const EMPTY_LOG_STREAM_MAX_RETRY_MILLIS = 5_000;
|
|
15
|
+
const logOptionsSchema = z.strictObject({ raw: z.boolean().optional(), ...timeoutOptionsShape });
|
|
16
|
+
/** How long `StreamedLog.stop()` waits for lines already in flight before aborting the log stream request. */
|
|
17
|
+
const STOP_GRACE_MILLIS = 1000;
|
|
5
18
|
/**
|
|
6
19
|
* Client for accessing Actor run or build logs.
|
|
7
20
|
*
|
|
@@ -39,21 +52,26 @@ export class LogClient extends ResourceClient {
|
|
|
39
52
|
*
|
|
40
53
|
* @param options - Log retrieval options.
|
|
41
54
|
* @param options.raw - If `true`, returns raw log content without any processing. Default is `false`.
|
|
42
|
-
* @
|
|
55
|
+
* @param options.timeoutSecs - Timeout for the API request. Default is `'long'`.
|
|
56
|
+
* @returns The log content as a string, or `undefined` if it does not exist. A chained client such as
|
|
57
|
+
* `run.log()` throws an `ApifyApiError` on a 404, since the run itself may be what is missing.
|
|
43
58
|
* @see https://docs.apify.com/api/v2/log-get
|
|
44
59
|
*/
|
|
45
60
|
async get(options = {}) {
|
|
61
|
+
const { timeoutSecs = 'long', signal, ...params } = parseArgument(options, logOptionsSchema, 'LogOptions');
|
|
46
62
|
const requestOpts = {
|
|
47
|
-
url: this.
|
|
63
|
+
url: this.buildUrl(),
|
|
48
64
|
method: 'GET',
|
|
49
|
-
params: this.
|
|
65
|
+
params: this.buildParams(params),
|
|
66
|
+
timeoutSecs,
|
|
67
|
+
signal,
|
|
50
68
|
};
|
|
51
69
|
try {
|
|
52
70
|
const response = await this.httpClient.call(requestOpts);
|
|
53
71
|
return cast(response.data);
|
|
54
72
|
}
|
|
55
73
|
catch (err) {
|
|
56
|
-
|
|
74
|
+
catchNotFoundForResourceOrThrow(err, this.id);
|
|
57
75
|
}
|
|
58
76
|
return undefined;
|
|
59
77
|
}
|
|
@@ -62,26 +80,31 @@ export class LogClient extends ResourceClient {
|
|
|
62
80
|
*
|
|
63
81
|
* @param options - Log retrieval options.
|
|
64
82
|
* @param options.raw - If `true`, returns raw log content without any processing. Default is `false`.
|
|
65
|
-
* @
|
|
83
|
+
* @param options.timeoutSecs - Timeout for the API request. Default is `'long'`.
|
|
84
|
+
* @returns The log content as a Readable stream, or `undefined` if it does not exist. A chained client such as
|
|
85
|
+
* `run.log()` throws an `ApifyApiError` on a 404, since the run itself may be what is missing.
|
|
66
86
|
* @see https://docs.apify.com/api/v2/log-get
|
|
67
87
|
*/
|
|
68
88
|
async stream(options = {}) {
|
|
89
|
+
const { timeoutSecs = 'long', signal, raw } = parseArgument(options, logOptionsSchema, 'LogOptions');
|
|
69
90
|
const params = {
|
|
70
91
|
stream: true,
|
|
71
|
-
raw
|
|
92
|
+
raw,
|
|
72
93
|
};
|
|
73
94
|
const requestOpts = {
|
|
74
|
-
url: this.
|
|
95
|
+
url: this.buildUrl(),
|
|
75
96
|
method: 'GET',
|
|
76
|
-
params: this.
|
|
97
|
+
params: this.buildParams(params),
|
|
77
98
|
responseType: 'stream',
|
|
99
|
+
timeoutSecs,
|
|
100
|
+
signal,
|
|
78
101
|
};
|
|
79
102
|
try {
|
|
80
103
|
const response = await this.httpClient.call(requestOpts);
|
|
81
104
|
return cast(response.data);
|
|
82
105
|
}
|
|
83
106
|
catch (err) {
|
|
84
|
-
|
|
107
|
+
catchNotFoundForResourceOrThrow(err, this.id);
|
|
85
108
|
}
|
|
86
109
|
return undefined;
|
|
87
110
|
}
|
|
@@ -119,39 +142,50 @@ export class LoggerActorRedirect extends Logger {
|
|
|
119
142
|
* @since Added in 2.20.0
|
|
120
143
|
*/
|
|
121
144
|
export class StreamedLog {
|
|
122
|
-
destinationLog;
|
|
123
|
-
streamBuffer = [];
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
145
|
+
#destinationLog;
|
|
146
|
+
#streamBuffer = [];
|
|
147
|
+
#decoder = new TextDecoder();
|
|
148
|
+
#splitMarker = /(?:\n|^)(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z)/g;
|
|
149
|
+
#relevancyTimeLimit;
|
|
150
|
+
#logClient;
|
|
151
|
+
#signal;
|
|
152
|
+
#streamingTask = null;
|
|
153
|
+
#stopLogging = false;
|
|
154
|
+
#stopController = new AbortController();
|
|
155
|
+
#wakeController = new AbortController();
|
|
129
156
|
constructor(options) {
|
|
130
|
-
const { toLog, logClient, fromStart = true } = options;
|
|
131
|
-
this
|
|
132
|
-
this
|
|
133
|
-
this
|
|
157
|
+
const { toLog, logClient, fromStart = true, signal } = options;
|
|
158
|
+
this.#destinationLog = toLog;
|
|
159
|
+
this.#logClient = logClient;
|
|
160
|
+
this.#signal = signal;
|
|
161
|
+
this.#relevancyTimeLimit = fromStart ? null : new Date();
|
|
134
162
|
}
|
|
135
163
|
/**
|
|
136
164
|
* Start log redirection.
|
|
137
165
|
*/
|
|
138
166
|
start() {
|
|
139
|
-
if (this
|
|
167
|
+
if (this.#streamingTask) {
|
|
140
168
|
throw new Error('Streaming task already active');
|
|
141
169
|
}
|
|
142
|
-
this
|
|
143
|
-
this
|
|
170
|
+
this.#stopLogging = false;
|
|
171
|
+
this.#stopController = new AbortController();
|
|
172
|
+
this.#wakeController = new AbortController();
|
|
173
|
+
this.#streamingTask = this.#streamLog();
|
|
144
174
|
}
|
|
145
175
|
/**
|
|
146
|
-
* Stop log redirection.
|
|
176
|
+
* Stop log redirection. Waits up to one second for lines already in flight, then aborts the log stream request. If
|
|
177
|
+
* no stream has delivered anything yet, reads the whole log in one request instead.
|
|
147
178
|
*/
|
|
148
179
|
async stop() {
|
|
149
|
-
if (!this
|
|
180
|
+
if (!this.#streamingTask) {
|
|
150
181
|
throw new Error('Streaming task is not active');
|
|
151
182
|
}
|
|
152
|
-
this
|
|
183
|
+
this.#stopLogging = true;
|
|
184
|
+
this.#wakeController.abort();
|
|
185
|
+
const stopController = this.#stopController;
|
|
186
|
+
const abortTimeout = setTimeout(() => stopController.abort(), STOP_GRACE_MILLIS);
|
|
153
187
|
try {
|
|
154
|
-
await this
|
|
188
|
+
await this.#streamingTask;
|
|
155
189
|
}
|
|
156
190
|
catch (err) {
|
|
157
191
|
if (!(err instanceof Error && err.name === 'AbortError')) {
|
|
@@ -159,73 +193,121 @@ export class StreamedLog {
|
|
|
159
193
|
}
|
|
160
194
|
}
|
|
161
195
|
finally {
|
|
162
|
-
|
|
196
|
+
clearTimeout(abortTimeout);
|
|
197
|
+
this.#streamingTask = null;
|
|
163
198
|
}
|
|
164
199
|
}
|
|
165
200
|
/**
|
|
166
201
|
* Get log stream from response and redirect it to another log.
|
|
167
202
|
*/
|
|
168
|
-
async streamLog() {
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
203
|
+
async #streamLog() {
|
|
204
|
+
const signal = this.#signal
|
|
205
|
+
? AbortSignal.any([this.#signal, this.#stopController.signal])
|
|
206
|
+
: this.#stopController.signal;
|
|
173
207
|
try {
|
|
174
|
-
|
|
208
|
+
let lastChunkRemainder;
|
|
209
|
+
let retryMillis = EMPTY_LOG_STREAM_RETRY_MILLIS;
|
|
210
|
+
// The API serves the log of a run that has not logged anything yet as an empty stream that ends at once,
|
|
211
|
+
// so reopen it until the first bytes arrive. If stopped before any bytes arrive, fetch it in one request.
|
|
212
|
+
while (!lastChunkRemainder) {
|
|
213
|
+
if (this.#stopLogging) {
|
|
214
|
+
// `signal` is aborted once the stop grace period ends, which would cut this read short.
|
|
215
|
+
const logContent = await this.#logClient.get({ raw: true, signal: this.#signal });
|
|
216
|
+
lastChunkRemainder = await this.#logStreamChunks([new TextEncoder().encode(logContent ?? '')], signal);
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
let logStream;
|
|
220
|
+
try {
|
|
221
|
+
logStream = await this.#logClient.stream({ raw: true, signal });
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
// The stop grace period ended while the stream was connecting, so read the log in one request.
|
|
225
|
+
if (this.#stopLogging && !this.#signal?.aborted)
|
|
226
|
+
continue;
|
|
227
|
+
throw err;
|
|
228
|
+
}
|
|
229
|
+
if (!logStream) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
// A stream opened during stop() would be cut after its first chunk, so read the log in one request.
|
|
233
|
+
if (this.#stopLogging) {
|
|
234
|
+
logStream.destroy();
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
lastChunkRemainder = await this.#logStreamChunks(logStream, signal);
|
|
238
|
+
if (!lastChunkRemainder) {
|
|
239
|
+
await sleep(retryMillis, AbortSignal.any([signal, this.#wakeController.signal]));
|
|
240
|
+
retryMillis = Math.min(retryMillis * 2, EMPTY_LOG_STREAM_MAX_RETRY_MILLIS);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
175
243
|
// Process whatever is left when exiting. Maybe it is incomplete, maybe it is last log without EOL.
|
|
176
|
-
const lastMessage =
|
|
244
|
+
const lastMessage = this.#decoder.decode(lastChunkRemainder).trim();
|
|
177
245
|
if (lastMessage.length) {
|
|
178
|
-
this
|
|
246
|
+
this.#destinationLog.info(lastMessage);
|
|
179
247
|
}
|
|
180
248
|
}
|
|
181
249
|
catch (err) {
|
|
250
|
+
if (signal.aborted)
|
|
251
|
+
return;
|
|
182
252
|
log.warning(`Log redirection stopped due to error`, err);
|
|
183
253
|
}
|
|
184
254
|
}
|
|
185
|
-
|
|
255
|
+
/**
|
|
256
|
+
* Redirect every complete message in the chunks and return the incomplete rest, or `undefined` when there were no
|
|
257
|
+
* chunks at all.
|
|
258
|
+
*/
|
|
259
|
+
async #logStreamChunks(logStream, signal) {
|
|
186
260
|
// Chunk may be incomplete. Keep remainder for next chunk.
|
|
187
|
-
let previousChunkRemainder
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
261
|
+
let previousChunkRemainder;
|
|
262
|
+
try {
|
|
263
|
+
for await (const chunk of logStream) {
|
|
264
|
+
// Handle possible leftover incomplete line from previous chunk.
|
|
265
|
+
// Everything before last end of line is complete.
|
|
266
|
+
previousChunkRemainder ??= new Uint8Array();
|
|
267
|
+
const chunkWithPreviousRemainder = new Uint8Array(previousChunkRemainder.length + chunk.length);
|
|
268
|
+
chunkWithPreviousRemainder.set(previousChunkRemainder, 0);
|
|
269
|
+
chunkWithPreviousRemainder.set(chunk, previousChunkRemainder.length);
|
|
270
|
+
const lastCompleteMessageIndex = chunkWithPreviousRemainder.lastIndexOf(0x0a);
|
|
271
|
+
previousChunkRemainder = chunkWithPreviousRemainder.slice(lastCompleteMessageIndex);
|
|
272
|
+
// Push complete part of the chunk to the buffer
|
|
273
|
+
this.#streamBuffer.push(chunkWithPreviousRemainder.slice(0, lastCompleteMessageIndex));
|
|
274
|
+
this.#logBufferContent();
|
|
275
|
+
// Keep processing the new data until stopped
|
|
276
|
+
if (this.#stopLogging) {
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
202
279
|
}
|
|
203
280
|
}
|
|
281
|
+
catch (err) {
|
|
282
|
+
// An aborted stream still hands back its unterminated last line, so the caller can flush it.
|
|
283
|
+
if (!signal.aborted)
|
|
284
|
+
throw err;
|
|
285
|
+
}
|
|
204
286
|
return previousChunkRemainder;
|
|
205
287
|
}
|
|
206
288
|
/**
|
|
207
289
|
* Parse the buffer and log complete messages.
|
|
208
290
|
*/
|
|
209
|
-
logBufferContent() {
|
|
210
|
-
const allParts =
|
|
291
|
+
#logBufferContent() {
|
|
292
|
+
const allParts = this.#decoder.decode(concatBytes(this.#streamBuffer)).split(this.#splitMarker).slice(1);
|
|
211
293
|
// Parse the buffer parts into complete messages
|
|
212
294
|
const messageMarkers = allParts.filter((_, i) => i % 2 === 0);
|
|
213
295
|
const messageContents = allParts.filter((_, i) => i % 2 !== 0);
|
|
214
|
-
this
|
|
296
|
+
this.#streamBuffer = [];
|
|
215
297
|
messageMarkers.forEach((marker, index) => {
|
|
216
298
|
const decodedMarker = marker;
|
|
217
299
|
const decodedContent = messageContents[index];
|
|
218
|
-
if (this
|
|
300
|
+
if (this.#relevancyTimeLimit) {
|
|
219
301
|
// Log only relevant messages. Ignore too old log messages.
|
|
220
302
|
const logTime = new Date(decodedMarker);
|
|
221
|
-
if (logTime < this
|
|
303
|
+
if (logTime < this.#relevancyTimeLimit) {
|
|
222
304
|
return;
|
|
223
305
|
}
|
|
224
306
|
}
|
|
225
307
|
const message = decodedMarker + decodedContent;
|
|
226
308
|
// Original log level information is not available. Log all on info level. Log level could be guessed for
|
|
227
309
|
// some logs, but for any multiline logs such guess would be probably correct only for the first line.
|
|
228
|
-
this
|
|
310
|
+
this.#destinationLog.info(message.trim());
|
|
229
311
|
});
|
|
230
312
|
}
|
|
231
313
|
}
|