sliftutils 1.7.104 → 1.7.106
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/dist/treeSummary.ts.cache +2 -2
- package/index.d.ts +3 -2
- package/misc/dist/environment.ts.cache +2 -2
- package/misc/dist/getSecret.ts.cache +2 -2
- package/misc/dist/strings.ts.cache +2 -2
- package/misc/dist/zip.ts.cache +2 -2
- package/misc/https/dist/certs.ts.cache +2 -2
- package/misc/https/dist/persistentLocalStorage.ts.cache +2 -2
- package/package.json +1 -1
- package/render-utils/dist/observer.tsx.cache +2 -2
- package/storage/StreamingLogs.ts +5 -3
- package/storage/dist/ArchivesDisk.ts.cache +2 -2
- package/storage/dist/FileFolderAPI.tsx.cache +2 -2
- package/storage/dist/IArchives.ts.cache +2 -2
- package/storage/dist/IndexedDBFileFolderAPI.ts.cache +2 -2
- package/storage/dist/JSONStorage.ts.cache +2 -2
- package/storage/dist/PendingManager.tsx.cache +2 -2
- package/storage/dist/StreamingLogs.ts.cache +8 -6
- package/storage/dist/TransactionFile.ts.cache +2 -2
- package/storage/dist/TransactionStorage.ts.cache +2 -2
- package/storage/dist/archiveHelpers.ts.cache +2 -2
- package/storage/dist/backblaze.ts.cache +2 -2
- package/storage/dist/fileSystemPointer.ts.cache +2 -2
- package/storage/dist/remoteFileStorage.ts.cache +2 -2
- package/storage/remoteStorage/dist/ArchivesDelayed.ts.cache +2 -2
- package/storage/remoteStorage/dist/ArchivesRemote.ts.cache +2 -2
- package/storage/remoteStorage/dist/ArchivesUrl.ts.cache +2 -2
- package/storage/remoteStorage/dist/accessStats.ts.cache +2 -2
- package/storage/remoteStorage/dist/blobStore.ts.cache +2 -2
- package/storage/remoteStorage/dist/bucketDisk.ts.cache +2 -2
- package/storage/remoteStorage/dist/certTrustModal.tsx.cache +2 -2
- package/storage/remoteStorage/dist/chainStartup.ts.cache +2 -2
- package/storage/remoteStorage/dist/cliArgs.ts.cache +2 -2
- package/storage/remoteStorage/dist/createArchives.ts.cache +2 -2
- package/storage/remoteStorage/dist/deployTakeover.ts.cache +2 -2
- package/storage/remoteStorage/dist/intermediateManagement.ts.cache +2 -2
- package/storage/remoteStorage/dist/intermediateSources.ts.cache +2 -2
- package/storage/remoteStorage/dist/remoteConfig.ts.cache +2 -2
- package/storage/remoteStorage/dist/serverConfig.ts.cache +2 -2
- package/storage/remoteStorage/dist/sourceWrapper.ts.cache +2 -2
- package/storage/remoteStorage/dist/sourcesList.ts.cache +2 -2
- package/storage/remoteStorage/dist/storageClientController.ts.cache +2 -2
- package/storage/remoteStorage/dist/storageController.ts.cache +2 -2
- package/storage/remoteStorage/dist/storageLogs.ts.cache +45 -35
- package/storage/remoteStorage/dist/storageServerState.ts.cache +2 -2
- package/storage/remoteStorage/dist/storeConfig.ts.cache +2 -2
- package/storage/remoteStorage/dist/storePlan.ts.cache +2 -2
- package/storage/remoteStorage/dist/storeSources.ts.cache +2 -2
- package/storage/remoteStorage/dist/storeSync.ts.cache +29 -29
- package/storage/remoteStorage/dist/validation.ts.cache +2 -2
- package/storage/remoteStorage/storageLogs.d.ts +3 -2
- package/storage/remoteStorage/storageLogs.ts +37 -26
- package/storage/remoteStorage/storeSync.ts +26 -25
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";
|
|
1
|
+
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true , configurable: true});
|
|
3
3
|
//exports.assertValidArgs = exports.assertValidPath = exports.assertValidSourceName = exports.assertValidName = void 0;
|
|
4
4
|
function assertValidName(value, kind) {
|
|
@@ -47,4 +47,4 @@ function assertValidArgs(target, key, descriptor) {
|
|
|
47
47
|
}
|
|
48
48
|
exports.assertValidArgs = assertValidArgs;
|
|
49
49
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmFsaWRhdGlvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInZhbGlkYXRpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEsU0FBZ0IsZUFBZSxDQUFDLEtBQWEsRUFBRSxJQUFZO0lBQ3ZELElBQUksQ0FBQyxlQUFlLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUM7UUFDL0IsTUFBTSxJQUFJLEtBQUssQ0FBQyxXQUFXLElBQUksSUFBSSxJQUFJLENBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQywrREFBK0QsQ0FBQyxDQUFDO0lBQzdILENBQUM7QUFDTCxDQUFDO0FBSkQsMENBSUM7QUFFRCx1VEFBdVQ7QUFDdlQsU0FBZ0IscUJBQXFCLENBQUMsS0FBYTtJQUMvQyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUM7UUFDaEMsTUFBTSxJQUFJLEtBQUssQ0FBQyx1QkFBdUIsSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsbUVBQW1FLENBQUMsQ0FBQztJQUNySSxDQUFDO0lBQ0QsSUFBSSxPQUFPLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUM7UUFDdEIsTUFBTSxJQUFJLEtBQUssQ0FBQyx1QkFBdUIsSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsb0VBQW9FLENBQUMsQ0FBQztJQUN0SSxDQUFDO0FBQ0wsQ0FBQztBQVBELHNEQU9DO0FBRUQsU0FBZ0IsZUFBZSxDQUFDLElBQVk7SUFDeEMsSUFBSSxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxNQUFNLENBQUMsQ0FBQyxNQUFNLEdBQUcsSUFBSSxFQUFFLENBQUM7UUFDMUMsTUFBTSxJQUFJLEtBQUssQ0FBQyxrQkFBa0IsSUFBSSxDQUFDLE1BQU0sNkJBQTZCLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLEdBQUcsQ0FBQyxFQUFFLENBQUMsQ0FBQztJQUNwRyxDQUFDO0lBQ0QsSUFBSSxDQUFDLElBQUksSUFBSSxJQUFJLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxJQUFJLElBQUksQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLElBQUksSUFBSSxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsSUFBSSxJQUFJLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxJQUFJLElBQUksQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQztRQUM3SCxNQUFNLElBQUksS0FBSyxDQUFDLGdCQUFnQixJQUFJLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLEdBQUcsQ0FBQyxDQUFDLDhGQUE4RixDQUFDLENBQUM7SUFDdEssQ0FBQztJQUNELElBQUksSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLEtBQUssR0FBRyxJQUFJLElBQUksS0FBSyxJQUFJLENBQUMsRUFBRSxDQUFDO1FBQzlELE1BQU0sSUFBSSxLQUFLLENBQUMsZ0JBQWdCLElBQUksQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsR0FBRyxDQUFDLENBQUMseUNBQXlDLENBQUMsQ0FBQztJQUNqSCxDQUFDO0FBQ0wsQ0FBQztBQVZELDBDQVVDO0FBRUQsNFdBQTRXO0FBQzVXLFNBQWdCLGVBQWUsQ0FBQyxNQUFlLEVBQUUsR0FBVyxFQUFFLFVBQThCO0lBQ3hGLElBQUksUUFBUSxHQUFHLFVBQVUsQ0FBQyxLQUF3QyxDQUFDO0lBQ25FLFVBQVUsQ0FBQyxLQUFLLEdBQUcsVUFBVSxHQUFHLElBQWU7UUFDM0MsSUFBSSxNQUFNLEdBQUcsSUFBSSxDQUFDLENBQUMsQ0FBeUUsQ0FBQztRQUM3RixJQUFJLE1BQU0sSUFBSSxPQUFPLE1BQU0sS0FBSyxRQUFRLEVBQUUsQ0FBQztZQUN2QyxJQUFJLFNBQVMsSUFBSSxNQUFNO2dCQUFFLGVBQWUsQ0FBQyxNQUFNLENBQUMsT0FBaUIsRUFBRSxTQUFTLENBQUMsQ0FBQztZQUM5RSxJQUFJLFlBQVksSUFBSSxNQUFNO2dCQUFFLGVBQWUsQ0FBQyxNQUFNLENBQUMsVUFBb0IsRUFBRSxhQUFhLENBQUMsQ0FBQztZQUN4RixJQUFJLE1BQU0sSUFBSSxNQUFNO2dCQUFFLGVBQWUsQ0FBQyxNQUFNLENBQUMsSUFBYyxDQUFDLENBQUM7UUFDakUsQ0FBQztRQUNELE9BQU8sUUFBUSxDQUFDLEtBQUssQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLENBQUM7SUFDdEMsQ0FBQyxDQUFDO0FBQ04sQ0FBQztBQVhELDBDQVdDIiwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IGZ1bmN0aW9uIGFzc2VydFZhbGlkTmFtZSh2YWx1ZTogc3RyaW5nLCBraW5kOiBzdHJpbmcpOiB2b2lkIHtcbiAgICBpZiAoIS9eW1xcdy1dezEsNjR9JC8udGVzdCh2YWx1ZSkpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBJbnZhbGlkICR7a2luZH0gJHtKU09OLnN0cmluZ2lmeSh2YWx1ZSl9LCBleHBlY3RlZCAxLTY0IGNoYXJhY3RlcnMgb2YgbGV0dGVycy9udW1iZXJzL3VuZGVyc2NvcmUvZGFzaGApO1xuICAgIH1cbn1cblxuLyoqIEEgc3RvcmUncyBuYW1lIChzZWUgQ29tbW9uQ29uZmlnLm5hbWUpLCB3aGljaCBhbHNvIGFsbG93cyBkb3RzIC0gYSBuYW1lIGlzIG9mdGVuIGEgaG9zdCBvciBhIHZlcnNpb24sIGFuZCBib3RoIHJlYWQgd3Jvbmcgd2l0aG91dCB0aGVtLiBJdCBpcyBvbmUgcGF0aCBzZWdtZW50IG9mIHRoZSBzdG9yZSdzIGZvbGRlciwgc28gdGhlIHR3byBuYW1lcyB0aGF0IHdvdWxkIG1lYW4gYSBkaWZmZXJlbnQgZm9sZGVyIGVudGlyZWx5IGFyZSByZWplY3RlZDogZXZlcnl0aGluZyBlbHNlIGNvbnRhaW5pbmcgZG90cyBpcyBqdXN0IGEgbmFtZS4gKi9cbmV4cG9ydCBmdW5jdGlvbiBhc3NlcnRWYWxpZFNvdXJjZU5hbWUodmFsdWU6IHN0cmluZyk6IHZvaWQge1xuICAgIGlmICghL15bXFx3Li1dezEsNjR9JC8udGVzdCh2YWx1ZSkpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBJbnZhbGlkIHNvdXJjZSBuYW1lICR7SlNPTi5zdHJpbmdpZnkodmFsdWUpfSwgZXhwZWN0ZWQgMS02NCBjaGFyYWN0ZXJzIG9mIGxldHRlcnMvbnVtYmVycy91bmRlcnNjb3JlL2Rhc2gvZG90YCk7XG4gICAgfVxuICAgIGlmICgvXlxcLiskLy50ZXN0KHZhbHVlKSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEludmFsaWQgc291cmNlIG5hbWUgJHtKU09OLnN0cmluZ2lmeSh2YWx1ZSl9OiBhIG5hbWUgb2Ygb25seSBkb3RzIHJlZmVycyB0byBhIGRpcmVjdG9yeSByYXRoZXIgdGhhbiBuYW1pbmcgb25lYCk7XG4gICAgfVxufVxuXG5leHBvcnQgZnVuY3Rpb24gYXNzZXJ0VmFsaWRQYXRoKHBhdGg6IHN0cmluZyk6IHZvaWQge1xuICAgIGlmIChCdWZmZXIuZnJvbShwYXRoLCBcInV0ZjhcIikubGVuZ3RoID4gMTAwMCkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYFBhdGggdG9vIGxvbmc6ICR7cGF0aC5sZW5ndGh9IGNoYXJhY3RlcnMgPiAxMDAwLiBQYXRoOiAke3BhdGguc2xpY2UoMCwgMjAwKX1gKTtcbiAgICB9XG4gICAgaWYgKCFwYXRoIHx8IHBhdGguc3RhcnRzV2l0aChcIi9cIikgfHwgcGF0aC5lbmRzV2l0aChcIi9cIikgfHwgcGF0aC5pbmNsdWRlcyhcIi8vXCIpIHx8IHBhdGguaW5jbHVkZXMoXCJcXFxcXCIpIHx8IHBhdGguaW5jbHVkZXMoXCJcXHgwMFwiKSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEludmFsaWQgcGF0aCAke0pTT04uc3RyaW5naWZ5KHBhdGguc2xpY2UoMCwgMjAwKSl9LCBwYXRocyBjYW5ub3QgYmUgZW1wdHksIHN0YXJ0IG9yIGVuZCB3aXRoIC8sIG9yIGNvbnRhaW4gLy8sIGJhY2tzbGFzaGVzLCBvciBudWxsIGNoYXJhY3RlcnNgKTtcbiAgICB9XG4gICAgaWYgKHBhdGguc3BsaXQoXCIvXCIpLnNvbWUocGFydCA9PiBwYXJ0ID09PSBcIi5cIiB8fCBwYXJ0ID09PSBcIi4uXCIpKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihgSW52YWxpZCBwYXRoICR7SlNPTi5zdHJpbmdpZnkocGF0aC5zbGljZSgwLCAyMDApKX0sIHBhdGhzIGNhbm5vdCBjb250YWluIC4gb3IgLi4gc2VnbWVudHNgKTtcbiAgICB9XG59XG5cbi8qKiBNZXRob2QgZGVjb3JhdG9yOiB2YWxpZGF0ZXMgdGhlIHdlbGwta25vd24gZmllbGRzIG9mIHRoZSBtZXRob2QncyBzaW5nbGUgY29uZmlnLW9iamVjdCBhcmd1bWVudCAtIGFjY291bnQvYnVja2V0TmFtZSBhcyBuYW1lcywgcGF0aCBhcyBhIHBhdGggLSBiZWZvcmUgdGhlIG1ldGhvZCBydW5zLiBGaWVsZHMgdGhlIGNvbmZpZyBkb2Vzbid0IGhhdmUgYXJlIHNraXBwZWQsIHNvIGl0IGFwcGxpZXMgdG8gZXZlcnkgQVBJIG1ldGhvZCB1bmlmb3JtbHkuIHByZWZpeCBpcyBkZWxpYmVyYXRlbHkgTk9UIHZhbGlkYXRlZDogcHJlZml4ZXMgbWF5IGJlIGVtcHR5IG9yIGVuZCB3aXRoIFwiL1wiLCBib3RoIGludmFsaWQgZm9yIHBhdGhzLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGFzc2VydFZhbGlkQXJncyh0YXJnZXQ6IHVua25vd24sIGtleTogc3RyaW5nLCBkZXNjcmlwdG9yOiBQcm9wZXJ0eURlc2NyaXB0b3IpOiB2b2lkIHtcbiAgICBsZXQgb3JpZ2luYWwgPSBkZXNjcmlwdG9yLnZhbHVlIGFzICguLi5hcmdzOiB1bmtub3duW10pID0+IHVua25vd247XG4gICAgZGVzY3JpcHRvci52YWx1ZSA9IGZ1bmN0aW9uICguLi5hcmdzOiB1bmtub3duW10pOiB1bmtub3duIHtcbiAgICAgICAgbGV0IGNvbmZpZyA9IGFyZ3NbMF0gYXMgeyBhY2NvdW50Pzogc3RyaW5nOyBidWNrZXROYW1lPzogc3RyaW5nOyBwYXRoPzogc3RyaW5nIH0gfCB1bmRlZmluZWQ7XG4gICAgICAgIGlmIChjb25maWcgJiYgdHlwZW9mIGNvbmZpZyA9PT0gXCJvYmplY3RcIikge1xuICAgICAgICAgICAgaWYgKFwiYWNjb3VudFwiIGluIGNvbmZpZykgYXNzZXJ0VmFsaWROYW1lKGNvbmZpZy5hY2NvdW50IGFzIHN0cmluZywgXCJhY2NvdW50XCIpO1xuICAgICAgICAgICAgaWYgKFwiYnVja2V0TmFtZVwiIGluIGNvbmZpZykgYXNzZXJ0VmFsaWROYW1lKGNvbmZpZy5idWNrZXROYW1lIGFzIHN0cmluZywgXCJidWNrZXQgbmFtZVwiKTtcbiAgICAgICAgICAgIGlmIChcInBhdGhcIiBpbiBjb25maWcpIGFzc2VydFZhbGlkUGF0aChjb25maWcucGF0aCBhcyBzdHJpbmcpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBvcmlnaW5hbC5hcHBseSh0aGlzLCBhcmdzKTtcbiAgICB9O1xufVxuIl19
|
|
50
|
-
/* _JS_SOURCE_HASH = "
|
|
50
|
+
/* _JS_SOURCE_HASH = "52f73d0ef7e2f65848acc68373c50e34bd39c017c3ff908220fc66a6f3d0296f"; */
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/// <reference types="node" />
|
|
3
3
|
import { LogFileInfo } from "../StreamingLogs";
|
|
4
4
|
export declare const LOGS_FOLDER_NAME = "logs";
|
|
5
|
-
/** One mutation the server performed: set/del/move/undelete/setLarge/routingConfig, plus the per-file synchronization writes ("sync get"/"sync set"). Sizes and times, never the data. internal marks writes pushed by a peer's synchronization rather than a client. */
|
|
5
|
+
/** One mutation the server performed: set/del/move/undelete/setLarge/routingConfig, plus the per-file synchronization writes ("sync get"/"sync set"). Sizes and times, never the data. internal marks writes pushed by a peer's synchronization rather than a client. Also printed to the console. */
|
|
6
6
|
export declare function logMutation(entry: {
|
|
7
7
|
op: string;
|
|
8
8
|
account: string;
|
|
@@ -15,13 +15,14 @@ export declare function logMutation(entry: {
|
|
|
15
15
|
callerId?: string;
|
|
16
16
|
internal?: boolean;
|
|
17
17
|
}): void;
|
|
18
|
-
/** A synchronization key point: scans and full syncs starting/finishing, reconciles, boundary scans - what an operator greps for to see whether the fleet is converging. */
|
|
18
|
+
/** A synchronization key point: scans and full syncs starting/finishing, reconciles, boundary scans - what an operator greps for to see whether the fleet is converging. Also printed to the console. */
|
|
19
19
|
export declare function logSyncEvent(entry: {
|
|
20
20
|
event: string;
|
|
21
21
|
store: string;
|
|
22
22
|
source?: string;
|
|
23
23
|
[key: string]: unknown;
|
|
24
24
|
}): void;
|
|
25
|
+
/** One console.error is all an error takes (console.error and console.warn are HOOKED to feed the stream) - this just guarantees the hook is installed first, for very-early callers. */
|
|
25
26
|
export declare function logStorageError(message: string): void;
|
|
26
27
|
/** The log files this server holds - see StreamingLogs.listFiles. Empty on processes with no storage folder. */
|
|
27
28
|
export declare function listStorageLogFiles(): Promise<LogFileInfo[]>;
|
|
@@ -16,14 +16,8 @@ const getLogs = lazy((): StreamingLogs | undefined => {
|
|
|
16
16
|
} catch {
|
|
17
17
|
return undefined;
|
|
18
18
|
}
|
|
19
|
-
let threadId: string | undefined;
|
|
20
|
-
try {
|
|
21
|
-
threadId = getOwnThreadId(getStorageServerConfigOptional()?.rootDomain || "");
|
|
22
|
-
} catch {
|
|
23
|
-
threadId = undefined;
|
|
24
|
-
}
|
|
25
19
|
hookErrorLogging();
|
|
26
|
-
return new StreamingLogs({ folder, threadId });
|
|
20
|
+
return new StreamingLogs({ folder, threadId: getThreadIdSafe() });
|
|
27
21
|
});
|
|
28
22
|
|
|
29
23
|
function firstExternalIPv4(): string | undefined {
|
|
@@ -35,48 +29,65 @@ function firstExternalIPv4(): string | undefined {
|
|
|
35
29
|
return undefined;
|
|
36
30
|
}
|
|
37
31
|
|
|
32
|
+
function getThreadIdSafe(): string | undefined {
|
|
33
|
+
try {
|
|
34
|
+
return getOwnThreadId(getStorageServerConfigOptional()?.rootDomain || "");
|
|
35
|
+
} catch {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
38
40
|
// Computed once: none of it changes over a process's lifetime
|
|
39
41
|
const baseFields = lazy(() => ({
|
|
40
42
|
pid: process.pid,
|
|
43
|
+
threadId: getThreadIdSafe(),
|
|
41
44
|
entryPoint: process.argv[1],
|
|
42
45
|
ip: firstExternalIPv4(),
|
|
43
46
|
domain: getStorageServerConfigOptional()?.domain,
|
|
44
47
|
}));
|
|
45
48
|
|
|
46
|
-
|
|
49
|
+
// Everything logged here goes BOTH to the storage log stream and the console - one call, never two. The console line carries only the entry (the base fields are constant per process, so they would just be noise there); the stream gets everything.
|
|
50
|
+
function write(kind: string, entry: { [key: string]: unknown }, alsoConsole: boolean): void {
|
|
51
|
+
if (alsoConsole) {
|
|
52
|
+
console.log(`[${kind}] ${JSON.stringify(entry)}`);
|
|
53
|
+
}
|
|
47
54
|
let logs = getLogs();
|
|
48
55
|
if (!logs) return;
|
|
49
56
|
logs.log({ kind, time: Date.now(), ...baseFields(), ...entry });
|
|
50
57
|
}
|
|
51
58
|
|
|
52
|
-
/** One mutation the server performed: set/del/move/undelete/setLarge/routingConfig, plus the per-file synchronization writes ("sync get"/"sync set"). Sizes and times, never the data. internal marks writes pushed by a peer's synchronization rather than a client. */
|
|
59
|
+
/** One mutation the server performed: set/del/move/undelete/setLarge/routingConfig, plus the per-file synchronization writes ("sync get"/"sync set"). Sizes and times, never the data. internal marks writes pushed by a peer's synchronization rather than a client. Also printed to the console. */
|
|
53
60
|
export function logMutation(entry: { op: string; account: string; bucketName: string; store?: string; path: string; toPath?: string; size?: number; writeTime?: number; callerId?: string; internal?: boolean }): void {
|
|
54
|
-
write("mutation", entry);
|
|
61
|
+
write("mutation", entry, true);
|
|
55
62
|
}
|
|
56
63
|
|
|
57
|
-
/** A synchronization key point: scans and full syncs starting/finishing, reconciles, boundary scans - what an operator greps for to see whether the fleet is converging. */
|
|
64
|
+
/** A synchronization key point: scans and full syncs starting/finishing, reconciles, boundary scans - what an operator greps for to see whether the fleet is converging. Also printed to the console. */
|
|
58
65
|
export function logSyncEvent(entry: { event: string; store: string; source?: string; [key: string]: unknown }): void {
|
|
59
|
-
write("sync", entry);
|
|
66
|
+
write("sync", entry, true);
|
|
60
67
|
}
|
|
61
68
|
|
|
69
|
+
/** One console.error is all an error takes (console.error and console.warn are HOOKED to feed the stream) - this just guarantees the hook is installed first, for very-early callers. */
|
|
62
70
|
export function logStorageError(message: string): void {
|
|
63
|
-
|
|
71
|
+
getLogs();
|
|
72
|
+
console.error(message);
|
|
64
73
|
}
|
|
65
74
|
|
|
66
|
-
// Everything console.errored also lands in the log stream (with a reentrancy guard: a failure INSIDE logging must not log itself forever)
|
|
67
|
-
let
|
|
75
|
+
// Everything console.errored or console.warned also lands in the log stream (with a reentrancy guard: a failure INSIDE logging must not log itself forever)
|
|
76
|
+
let writingHooked = false;
|
|
68
77
|
const hookErrorLogging = lazy(() => {
|
|
69
|
-
let
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
for (let level of ["error", "warn"] as const) {
|
|
79
|
+
let original = console[level].bind(console);
|
|
80
|
+
console[level] = (...args: unknown[]) => {
|
|
81
|
+
original(...args);
|
|
82
|
+
if (writingHooked) return;
|
|
83
|
+
writingHooked = true;
|
|
84
|
+
try {
|
|
85
|
+
write(level, { message: args.map(x => typeof x === "string" && x || ((x as Error)?.stack ?? JSON.stringify(x))).join(" ").slice(0, 5000) }, false);
|
|
86
|
+
} catch { } finally {
|
|
87
|
+
writingHooked = false;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
80
91
|
});
|
|
81
92
|
|
|
82
93
|
/** The log files this server holds - see StreamingLogs.listFiles. Empty on processes with no storage folder. */
|
|
@@ -75,10 +75,6 @@ type ScanTally = Record<ScanOutcome, number>;
|
|
|
75
75
|
function newScanTally(): ScanTally {
|
|
76
76
|
return { filtered: 0, new: 0, updated: 0, tombstone: 0, unchanged: 0 };
|
|
77
77
|
}
|
|
78
|
-
function formatScanTally(tally: ScanTally, total: number): string {
|
|
79
|
-
let pct = (n: number) => `${Math.round(n / Math.max(total, 1) * 1000) / 10}%`;
|
|
80
|
-
return `${tally.new} new paths (${pct(tally.new)}), ${tally.updated} newer writes (${pct(tally.updated)}), ${tally.tombstone} deletions (${pct(tally.tombstone)}), ${tally.unchanged} unchanged (${pct(tally.unchanged)}), ${tally.filtered} outside route (${pct(tally.filtered)})`;
|
|
81
|
-
}
|
|
82
78
|
|
|
83
79
|
export class StoreSync {
|
|
84
80
|
// Per source slot, in step with the store's sources array. Slots are never spliced out - a removed one is marked dead - so a slot number held by a running loop always means the same source.
|
|
@@ -226,7 +222,7 @@ export class StoreSync {
|
|
|
226
222
|
/** A boundary scan of the node that owned (part of) our route in the valid window before ours, when that node is different storage (a disk rescan can't see its writes): just its changes since the boundary neighborhood, with matching values pulled onto our own disk. */
|
|
227
223
|
public async boundaryScanRemote(source: IArchives, config: { since: number; route?: [number, number] }): Promise<void> {
|
|
228
224
|
let scanStart = Date.now();
|
|
229
|
-
|
|
225
|
+
logSyncEvent({ event: "boundaryScanStart", store: this.store.folder, source: source.getDebugName(), since: config.since, route: config.route || FULL_ROUTE });
|
|
230
226
|
let changes = await source.getChangesAfter2({ time: config.since, routes: config.route && [config.route] || undefined });
|
|
231
227
|
let tally = newScanTally();
|
|
232
228
|
for (let file of changes) {
|
|
@@ -246,7 +242,10 @@ export class StoreSync {
|
|
|
246
242
|
continue;
|
|
247
243
|
}
|
|
248
244
|
let copied = await copyArchiveFile({ from: source, to: this.store.sources[0].source, path: file.path, size: file.size, writeTime: file.createTime, forceSetImmutable: true, noChecks: true, internal: true });
|
|
249
|
-
if (!copied)
|
|
245
|
+
if (!copied) {
|
|
246
|
+
console.warn(`Boundary scan could not copy ${file.path} from ${source.getDebugName()} (store ${this.store.folder}): its change feed listed it (${file.size} bytes, writeTime ${new Date(file.createTime).toISOString()}) but the read found nothing`);
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
250
249
|
this.store.noteSyncTransfer("sync get", file.path, copied.size);
|
|
251
250
|
if (copied.size === 0) {
|
|
252
251
|
this.store.setIndexDeleted(file.path, copied.writeTime);
|
|
@@ -260,8 +259,7 @@ export class StoreSync {
|
|
|
260
259
|
tally.new++;
|
|
261
260
|
}
|
|
262
261
|
}
|
|
263
|
-
|
|
264
|
-
logSyncEvent({ event: "boundaryScanFinish", store: this.store.folder, source: source.getDebugName(), durationMs: Date.now() - scanStart, since: config.since, changes: changes.length, newPaths: tally.new, updated: tally.updated, tombstones: tally.tombstone });
|
|
262
|
+
logSyncEvent({ event: "boundaryScanFinish", store: this.store.folder, source: source.getDebugName(), durationMs: Date.now() - scanStart, since: config.since, changes: changes.length, newPaths: tally.new, updated: tally.updated, tombstones: tally.tombstone, unchanged: tally.unchanged });
|
|
265
263
|
}
|
|
266
264
|
|
|
267
265
|
// ── per-source loops ──
|
|
@@ -270,6 +268,8 @@ export class StoreSync {
|
|
|
270
268
|
await this.store.registerSlot(sourceIndex);
|
|
271
269
|
let { source } = this.store.sources[sourceIndex];
|
|
272
270
|
let state = this.states[sourceIndex];
|
|
271
|
+
// If a slot ever gets TWO of these, its loops are doubled - every poll and full sync runs twice
|
|
272
|
+
logSyncEvent({ event: "sourceSyncStart", store: this.store.folder, source: source.getDebugName(), sourceIndex, url: this.store.sources[sourceIndex].url, intermediate: this.store.sources[sourceIndex].intermediate });
|
|
273
273
|
// Read live for every pass, not captured - the store's source list can change while loops run
|
|
274
274
|
let noFullSync = () => this.store.sources[sourceIndex].noFullSync;
|
|
275
275
|
let listing: Map<string, number> | undefined;
|
|
@@ -329,7 +329,6 @@ export class StoreSync {
|
|
|
329
329
|
let scanStart = Date.now();
|
|
330
330
|
let activity: SyncActivity = { type: "metadataScan", sourceDebugName: source.getDebugName(), startTime: scanStart };
|
|
331
331
|
this.activities.add(activity);
|
|
332
|
-
console.log(`Metadata scan of ${source.getDebugName()} starting (store ${this.store.folder})`);
|
|
333
332
|
logSyncEvent({ event: "scanStart", store: this.store.folder, source: source.getDebugName() });
|
|
334
333
|
let progressTimer = setInterval(() => {
|
|
335
334
|
console.log(`Metadata scan of ${source.getDebugName()} still running (${Math.round((Date.now() - scanStart) / 1000)}s, store ${this.store.folder})`);
|
|
@@ -392,11 +391,7 @@ export class StoreSync {
|
|
|
392
391
|
if (!routeContains(route, getRoute(key))) continue;
|
|
393
392
|
missingOnSource++;
|
|
394
393
|
}
|
|
395
|
-
|
|
396
|
-
let union = indexSizeBefore + newPaths;
|
|
397
|
-
let pct = (n: number) => `${Math.round(n / Math.max(union, 1) * 1000) / 10}%`;
|
|
398
|
-
console.log(`Metadata scan of ${source.getDebugName()} finished in ${Math.round((Date.now() - scanStart) / 1000)}s (store ${this.store.folder}): ${files.length} listed vs ${indexSizeBefore} indexed (union ${union}): ${formatScanTally(tally, union)}, ${missingOnSource} in index but missing on source (${pct(missingOnSource)}), ${removedFromIndex} removed from index (${pct(removedFromIndex)})`);
|
|
399
|
-
logSyncEvent({ event: "scanFinish", store: this.store.folder, source: source.getDebugName(), durationMs: Date.now() - scanStart, listed: files.length, indexedBefore: indexSizeBefore, newPaths: tally.new, updated: tally.updated, tombstones: tally.tombstone, missingOnSource, removedFromIndex });
|
|
394
|
+
logSyncEvent({ event: "scanFinish", store: this.store.folder, source: source.getDebugName(), durationMs: Date.now() - scanStart, listed: files.length, indexedBefore: indexSizeBefore, newPaths: tally.new, updated: tally.updated, tombstones: tally.tombstone, unchanged: tally.unchanged, outsideRoute: tally.filtered, missingOnSource, removedFromIndex });
|
|
400
395
|
state.changesAfterTime = Math.max(state.changesAfterTime, scanStart - CHANGES_POLL_OVERLAP);
|
|
401
396
|
return seen;
|
|
402
397
|
}
|
|
@@ -462,8 +457,6 @@ export class StoreSync {
|
|
|
462
457
|
}
|
|
463
458
|
if (failed) {
|
|
464
459
|
console.error(`Reconciling sync source ${source.getDebugName()} (store ${this.store.folder}): pushed ${pushed} files, ${failed} failed${aborted && ` before aborting the pass (${consecutiveFailures} consecutive failures - the source looks down; the next scan cycle retries)` || ""}. First errors: ${errors.join(" | ")}`);
|
|
465
|
-
} else if (pushed) {
|
|
466
|
-
console.log(`Reconciled sync source ${source.getDebugName()} (store ${this.store.folder}): pushed ${pushed} files it was missing or held older copies of`);
|
|
467
460
|
}
|
|
468
461
|
if (pushed || failed) {
|
|
469
462
|
logSyncEvent({ event: "reconcileFinish", store: this.store.folder, source: source.getDebugName(), pushed, failed, aborted });
|
|
@@ -503,9 +496,9 @@ export class StoreSync {
|
|
|
503
496
|
for (let file of changes) {
|
|
504
497
|
tally[this.updateScanIndex(sourceIndex, file)]++;
|
|
505
498
|
}
|
|
506
|
-
// Polls run constantly, so only the ones that actually changed the index get
|
|
499
|
+
// Polls run constantly, so only the ones that actually changed the index get an entry
|
|
507
500
|
if (tally.new || tally.updated || tally.tombstone) {
|
|
508
|
-
|
|
501
|
+
logSyncEvent({ event: "changesPoll", store: this.store.folder, source: source.getDebugName(), changes: changes.length, newPaths: tally.new, updated: tally.updated, tombstones: tally.tombstone, unchanged: tally.unchanged });
|
|
509
502
|
}
|
|
510
503
|
state.scannedCount += changes.length;
|
|
511
504
|
state.changesAfterTime = pollStart - CHANGES_POLL_OVERLAP;
|
|
@@ -535,7 +528,7 @@ export class StoreSync {
|
|
|
535
528
|
totalBytes,
|
|
536
529
|
};
|
|
537
530
|
this.activities.add(activity);
|
|
538
|
-
logSyncEvent({ event: "fullSyncStart", store: this.store.folder, source: source.getDebugName(), files: pending.length, bytes: totalBytes });
|
|
531
|
+
logSyncEvent({ event: "fullSyncStart", store: this.store.folder, source: source.getDebugName(), diffBased: state.supportsChangesAfter, files: pending.length, bytes: totalBytes });
|
|
539
532
|
let progressLogged = false;
|
|
540
533
|
let logProgress = () => {
|
|
541
534
|
progressLogged = true;
|
|
@@ -557,12 +550,15 @@ export class StoreSync {
|
|
|
557
550
|
console.warn(`Full sync from ${source.getDebugName()} (store ${this.store.folder}) has been running for ${formatTime(elapsed)}: ${doneFiles}/${pending.length} files (${(doneFiles / pending.length * 100).toFixed(1)}%), ${formatNumber(doneBytes)}B/${formatNumber(totalBytes)}B (${(totalBytes && doneBytes / totalBytes * 100 || 100).toFixed(1)}%), ${formatNumber(bytesPerSecond)}B/s. Estimated ${etaText}.`);
|
|
558
551
|
}, FULL_SYNC_SLOW_ERROR_INTERVAL);
|
|
559
552
|
(slowErrorTimer as { unref?: () => void }).unref?.();
|
|
553
|
+
let failed = 0;
|
|
554
|
+
let copiedFiles = 0;
|
|
555
|
+
let copiedBytes = 0;
|
|
556
|
+
let missingOnSource = 0;
|
|
557
|
+
let aborted = false;
|
|
560
558
|
try {
|
|
561
559
|
let nextIndex = 0;
|
|
562
|
-
let failed = 0;
|
|
563
560
|
let consecutiveFailures = 0;
|
|
564
561
|
let errors: string[] = [];
|
|
565
|
-
let aborted = false;
|
|
566
562
|
let copyWorker = async () => {
|
|
567
563
|
while (!aborted && !this.store.stopped.stop && !state.stopped.stop) {
|
|
568
564
|
let index = nextIndex++;
|
|
@@ -571,11 +567,16 @@ export class StoreSync {
|
|
|
571
567
|
try {
|
|
572
568
|
let copied = await copyArchiveFile({ from: source, to: this.store.sources[0].source, path: key, size: entry.size, writeTime: entry.writeTime, forceSetImmutable: true, noChecks: true, internal: true });
|
|
573
569
|
if (copied) {
|
|
570
|
+
copiedFiles++;
|
|
571
|
+
copiedBytes += copied.size;
|
|
574
572
|
this.store.noteSyncTransfer("sync get", key, copied.size);
|
|
575
|
-
//
|
|
576
|
-
if (this.
|
|
577
|
-
|
|
573
|
+
// The copy carries the source's write time, and the index commits it under the normal ordering rule (>= the current time wins) - setIndexEntry refusing means a NEWER write landed while we copied, which must be said, not swallowed
|
|
574
|
+
if (!this.store.setIndexEntry(key, { writeTime: copied.writeTime, size: copied.size, sourcesListIndex: this.store.sourcesListIndexOfSlot(0) })) {
|
|
575
|
+
console.warn(`Full sync copied ${key} from ${source.getDebugName()} (store ${this.store.folder}) with writeTime ${copied.writeTime} (${new Date(copied.writeTime).toISOString()}), but the index now has ${this.store.currentWriteTime(key)} (${new Date(this.store.currentWriteTime(key)).toISOString()}) - something wrote it while we were syncing; keeping the newer entry`);
|
|
578
576
|
}
|
|
577
|
+
} else {
|
|
578
|
+
missingOnSource++;
|
|
579
|
+
console.warn(`Full sync could not copy ${key} from ${source.getDebugName()} (store ${this.store.folder}): the source answered but no longer has it (expected ${entry.size} bytes, writeTime ${new Date(entry.writeTime).toISOString()})`);
|
|
579
580
|
}
|
|
580
581
|
consecutiveFailures = 0;
|
|
581
582
|
} catch (e) {
|
|
@@ -612,7 +613,7 @@ export class StoreSync {
|
|
|
612
613
|
if (progressLogged) {
|
|
613
614
|
logProgress();
|
|
614
615
|
}
|
|
615
|
-
logSyncEvent({ event: "fullSyncFinish", store: this.store.folder, source: source.getDebugName(), durationMs: Date.now() - activity.startTime,
|
|
616
|
+
logSyncEvent({ event: "fullSyncFinish", store: this.store.folder, source: source.getDebugName(), diffBased: state.supportsChangesAfter, durationMs: Date.now() - activity.startTime, totalFiles: pending.length, totalBytes, processedFiles: activity.doneFiles, copiedFiles, copiedBytes, missingOnSource, failed, aborted });
|
|
616
617
|
}
|
|
617
618
|
}
|
|
618
619
|
|