Skip to content

Commit ab732bc

Browse files
authored
Merge pull request #273 from vector-im/bwindels/keybackuplogging
Add logging to key backup operations
2 parents 7ba979e + 1348611 commit ab732bc

File tree

10 files changed

+125
-60
lines changed

10 files changed

+125
-60
lines changed

src/logging/LogItem.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,8 @@ export class LogItem {
140140
// (e)rror
141141
item.e = {
142142
stack: this.error.stack,
143-
name: this.error.name
143+
name: this.error.name,
144+
message: this.error.message.split("\n")[0]
144145
};
145146
}
146147
if (forced) {

src/logging/NullLogger.js

+37-4
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,30 @@ limitations under the License.
1515
*/
1616
import {LogLevel} from "./LogFilter.js";
1717

18-
// TODO: add missing methods
18+
function noop () {}
19+
20+
1921
export class NullLogger {
2022
constructor() {
21-
this._item = new NullLogItem();
23+
this.item = new NullLogItem();
2224
}
2325

2426
log() {}
2527

2628
run(_, callback) {
27-
return callback(this._item);
29+
return callback(this.item);
30+
}
31+
32+
wrapOrRun(item, _, callback) {
33+
if (item) {
34+
item.wrap(null, callback);
35+
} else {
36+
this.run(null, callback);
37+
}
38+
}
39+
40+
runDetached(_, callback) {
41+
new Promise(r => r(callback(this.item))).then(noop, noop);
2842
}
2943

3044
async export() {
@@ -42,12 +56,29 @@ class NullLogItem {
4256
}
4357
log() {}
4458
set() {}
45-
anonymize() {}
59+
60+
runDetached(_, callback) {
61+
new Promise(r => r(callback(this))).then(noop, noop);
62+
}
63+
64+
wrapDetached(_, callback) {
65+
return this.refDetached(null, callback);
66+
}
67+
68+
run(callback) {
69+
return callback(this);
70+
}
71+
72+
refDetached() {}
4673

4774
get level() {
4875
return LogLevel;
4976
}
5077

78+
get duration() {
79+
return 0;
80+
}
81+
5182
catch(err) {
5283
return err;
5384
}
@@ -58,3 +89,5 @@ class NullLogItem {
5889

5990
finish() {}
6091
}
92+
93+
export const Instance = new NullLogger();

src/logging/utils.js

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// these are helper functions if you can't assume you always have a log item (e.g. some code paths call with one set, others don't)
2+
// if you know you always have a log item, better to use the methods on the log item than these utility functions.
3+
4+
import {Instance as NullLoggerInstance} from "./NullLogger.js";
5+
6+
export function wrapOrRunNullLogger(logItem, labelOrValues, callback, logLevel = null, filterCreator = null) {
7+
if (logItem) {
8+
return logItem.wrap(logItem, labelOrValues, callback, logLevel, filterCreator);
9+
} else {
10+
return NullLoggerInstance.run(null, callback);
11+
}
12+
}
13+
14+
export function ensureLogItem(logItem) {
15+
return logItem || NullLoggerInstance.item;
16+
}

src/matrix/SessionContainer.js

+1
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ export class SessionContainer {
146146
}
147147

148148
async _loadSessionInfo(sessionInfo, isNewLogin, log) {
149+
log.set("appVersion", this._platform.version);
149150
const clock = this._platform.clock;
150151
this._sessionStartedByReconnector = false;
151152
this._status.set(LoadStatus.Loading);

src/matrix/e2ee/RoomEncryption.js

+34-27
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export class RoomEncryption {
5656
this._sessionBackup = sessionBackup;
5757
}
5858

59-
async restoreMissingSessionsFromBackup(entries) {
59+
async restoreMissingSessionsFromBackup(entries, log) {
6060
const events = entries.filter(e => e.isEncrypted && !e.isDecrypted && e.event).map(e => e.event);
6161
const eventsBySession = groupEventsBySession(events);
6262
const groups = Array.from(eventsBySession.values());
@@ -69,7 +69,7 @@ export class RoomEncryption {
6969
// start with last sessions which should be for the last items in the timeline
7070
for (var i = missingSessions.length - 1; i >= 0; i--) {
7171
const session = missingSessions[i];
72-
await this._requestMissingSessionFromBackup(session.senderKey, session.sessionId);
72+
await log.wrap("session", log => this._requestMissingSessionFromBackup(session.senderKey, session.sessionId, log));
7373
}
7474
}
7575
}
@@ -98,9 +98,6 @@ export class RoomEncryption {
9898
return shouldFlush;
9999
}
100100

101-
// this happens before entries exists, as they are created by the syncwriter
102-
// but we want to be able to map it back to something in the timeline easily
103-
// when retrying decryption.
104101
async prepareDecryptAll(events, newKeys, source, txn) {
105102
const errors = new Map();
106103
const validEvents = [];
@@ -137,47 +134,50 @@ export class RoomEncryption {
137134
return new DecryptionPreparation(preparation, errors, source, this, events);
138135
}
139136

140-
async _processDecryptionResults(events, results, errors, source, txn) {
137+
async _processDecryptionResults(events, results, errors, source, txn, log) {
141138
const missingSessionEvents = events.filter(event => {
142139
const error = errors.get(event.event_id);
143140
return error?.code === "MEGOLM_NO_SESSION";
144141
});
145142
if (!missingSessionEvents.length) {
146143
return;
147144
}
148-
const eventsBySession = groupEventsBySession(events);
145+
// store missing event ids if received from sync
146+
const missingEventsBySession = groupEventsBySession(missingSessionEvents);
149147
if (source === DecryptionSource.Sync) {
150-
await Promise.all(Array.from(eventsBySession.values()).map(async group => {
148+
await Promise.all(Array.from(missingEventsBySession.values()).map(async group => {
151149
const eventIds = group.events.map(e => e.event_id);
152150
return this._megolmDecryption.addMissingKeyEventIds(
153151
this._room.id, group.senderKey, group.sessionId, eventIds, txn);
154152
}));
155153
}
154+
155+
if (!this._sessionBackup) {
156+
return;
157+
}
156158

157-
// TODO: do proper logging here
158-
// run detached
159-
Promise.resolve().then(async () => {
159+
log.wrapDetached("check key backup", async log => {
160160
// if the message came from sync, wait 10s to see if the room key arrives late,
161161
// and only after that proceed to request from backup
162+
log.set("source", source);
163+
log.set("events", missingSessionEvents.length);
164+
log.set("sessions", missingEventsBySession.size);
162165
if (source === DecryptionSource.Sync) {
163166
await this._clock.createTimeout(10000).elapsed();
164167
if (this._disposed) {
165168
return;
166169
}
167170
// now check which sessions have been received already
168171
const txn = await this._storage.readTxn([this._storage.storeNames.inboundGroupSessions]);
169-
await Promise.all(Array.from(eventsBySession).map(async ([key, group]) => {
172+
await Promise.all(Array.from(missingEventsBySession).map(async ([key, group]) => {
170173
if (await this._megolmDecryption.hasSession(this._room.id, group.senderKey, group.sessionId, txn)) {
171-
eventsBySession.delete(key);
174+
missingEventsBySession.delete(key);
172175
}
173176
}));
174177
}
175-
await Promise.all(Array.from(eventsBySession.values()).map(group => {
176-
return this._requestMissingSessionFromBackup(group.senderKey, group.sessionId);
178+
await Promise.all(Array.from(missingEventsBySession.values()).map(group => {
179+
return log.wrap("session", log => this._requestMissingSessionFromBackup(group.senderKey, group.sessionId, log));
177180
}));
178-
}).catch(err => {
179-
console.log("failed to fetch missing session from key backup");
180-
console.error(err);
181181
});
182182
}
183183

@@ -194,18 +194,21 @@ export class RoomEncryption {
194194
}
195195
}
196196

197-
async _requestMissingSessionFromBackup(senderKey, sessionId) {
197+
async _requestMissingSessionFromBackup(senderKey, sessionId, log) {
198198
// show prompt to enable secret storage
199199
if (!this._sessionBackup) {
200+
log.set("enabled", false);
200201
this._notifyMissingMegolmSession();
201202
return;
202203
}
203-
204+
log.set("id", sessionId);
205+
log.set("senderKey", senderKey);
204206
try {
205-
const session = await this._sessionBackup.getSession(this._room.id, sessionId);
207+
const session = await this._sessionBackup.getSession(this._room.id, sessionId, log);
206208
if (session?.algorithm === MEGOLM_ALGORITHM) {
207209
if (session["sender_key"] !== senderKey) {
208-
console.warn("Got session key back from backup with different sender key, ignoring", {session, senderKey});
210+
log.set("wrong_sender_key", session["sender_key"]);
211+
log.logLevel = log.level.Warn;
209212
return;
210213
}
211214
let roomKey = this._megolmDecryption.roomKeyFromBackup(this._room.id, sessionId, session);
@@ -216,6 +219,7 @@ export class RoomEncryption {
216219
const txn = await this._storage.readWriteTxn([this._storage.storeNames.inboundGroupSessions]);
217220
try {
218221
keyIsBestOne = await this._megolmDecryption.writeRoomKey(roomKey, txn);
222+
log.set("isBetter", keyIsBestOne);
219223
if (keyIsBestOne) {
220224
retryEventIds = roomKey.eventIds;
221225
}
@@ -230,15 +234,18 @@ export class RoomEncryption {
230234
roomKey.dispose();
231235
}
232236
if (keyIsBestOne) {
233-
await this._room.notifyRoomKey(roomKey, retryEventIds || []);
237+
await log.wrap("retryDecryption", log => this._room.notifyRoomKey(roomKey, retryEventIds || [], log));
234238
}
235239
}
236240
} else if (session?.algorithm) {
237-
console.info(`Backed-up session of unknown algorithm: ${session.algorithm}`);
241+
log.set("unknown algorithm", session.algorithm);
238242
}
239243
} catch (err) {
240244
if (!(err.name === "HomeServerError" && err.errcode === "M_NOT_FOUND")) {
241-
console.error(`Could not get session ${sessionId} from backup`, err);
245+
log.set("not_found", true);
246+
} else {
247+
log.error = err;
248+
log.logLevel = log.level.Error;
242249
}
243250
}
244251
}
@@ -485,10 +492,10 @@ class DecryptionChanges {
485492
this._events = events;
486493
}
487494

488-
async write(txn) {
495+
async write(txn, log) {
489496
const {results, errors} = await this._megolmDecryptionChanges.write(txn);
490497
mergeMap(this._extraErrors, errors);
491-
await this._roomEncryption._processDecryptionResults(this._events, results, errors, this._source, txn);
498+
await this._roomEncryption._processDecryptionResults(this._events, results, errors, this._source, txn, log);
492499
return new BatchDecryptionResult(results, errors, this._roomEncryption);
493500
}
494501
}

src/matrix/e2ee/megolm/SessionBackup.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ export class SessionBackup {
2121
this._hsApi = hsApi;
2222
}
2323

24-
async getSession(roomId, sessionId) {
25-
const sessionResponse = await this._hsApi.roomKeyForRoomAndSession(this._backupInfo.version, roomId, sessionId).response();
24+
async getSession(roomId, sessionId, log) {
25+
const sessionResponse = await this._hsApi.roomKeyForRoomAndSession(this._backupInfo.version, roomId, sessionId, {log}).response();
2626
const sessionInfo = this._decryption.decrypt(
2727
sessionResponse.session_data.ephemeral,
2828
sessionResponse.session_data.mac,

src/matrix/room/Room.js

+15-12
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {EventEntry} from "./timeline/entries/EventEntry.js";
2929
import {ObservedEventMap} from "./ObservedEventMap.js";
3030
import {AttachmentUpload} from "./AttachmentUpload.js";
3131
import {DecryptionSource} from "../e2ee/common.js";
32+
import {ensureLogItem} from "../../logging/utils.js";
3233

3334
const EVENT_ENCRYPTED_TYPE = "m.room.encrypted";
3435

@@ -81,7 +82,7 @@ export class Room extends EventEmitter {
8182
* @param {Array<string>} eventIds any event ids that should be retried. There might be more in the timeline though for this key.
8283
* @return {Promise}
8384
*/
84-
async notifyRoomKey(roomKey, eventIds) {
85+
async notifyRoomKey(roomKey, eventIds, log) {
8586
if (!this._roomEncryption) {
8687
return;
8788
}
@@ -95,7 +96,7 @@ export class Room extends EventEmitter {
9596
retryEntries = retryEntries.concat(retryTimelineEntries);
9697
}
9798
if (retryEntries.length) {
98-
const decryptRequest = this._decryptEntries(DecryptionSource.Retry, retryEntries, txn);
99+
const decryptRequest = this._decryptEntries(DecryptionSource.Retry, retryEntries, txn, log);
99100
// this will close txn while awaiting decryption
100101
await decryptRequest.complete();
101102

@@ -125,8 +126,8 @@ export class Room extends EventEmitter {
125126
* Used for decrypting when loading/filling the timeline, and retrying decryption,
126127
* not during sync, where it is split up during the multiple phases.
127128
*/
128-
_decryptEntries(source, entries, inboundSessionTxn = null) {
129-
const request = new DecryptionRequest(async r => {
129+
_decryptEntries(source, entries, inboundSessionTxn, log = null) {
130+
const request = new DecryptionRequest(async (r, log) => {
130131
if (!inboundSessionTxn) {
131132
inboundSessionTxn = await this._storage.readTxn([this._storage.storeNames.inboundGroupSessions]);
132133
}
@@ -148,7 +149,7 @@ export class Room extends EventEmitter {
148149
const writeTxn = await this._storage.readWriteTxn(stores);
149150
let decryption;
150151
try {
151-
decryption = await changes.write(writeTxn);
152+
decryption = await changes.write(writeTxn, log);
152153
if (isTimelineOpen) {
153154
await decryption.verifySenders(writeTxn);
154155
}
@@ -162,7 +163,7 @@ export class Room extends EventEmitter {
162163
if (this._observedEvents) {
163164
this._observedEvents.updateEvents(entries);
164165
}
165-
});
166+
}, ensureLogItem(log));
166167
return request;
167168
}
168169

@@ -248,7 +249,7 @@ export class Room extends EventEmitter {
248249
await log.wrap("syncWriter", log => this._syncWriter.writeSync(roomResponse, txn, log), log.level.Detail);
249250
let allEntries = newEntries;
250251
if (decryptChanges) {
251-
const decryption = await decryptChanges.write(txn);
252+
const decryption = await log.wrap("decryptChanges", log => decryptChanges.write(txn, log));
252253
log.set("decryptionResults", decryption.results.size);
253254
log.set("decryptionErrors", decryption.errors.size);
254255
if (this._isTimelineOpen) {
@@ -510,7 +511,7 @@ export class Room extends EventEmitter {
510511
}
511512
await txn.complete();
512513
if (this._roomEncryption) {
513-
const decryptRequest = this._decryptEntries(DecryptionSource.Timeline, gapResult.entries);
514+
const decryptRequest = this._decryptEntries(DecryptionSource.Timeline, gapResult.entries, null, log);
514515
await decryptRequest.complete();
515516
}
516517
// once txn is committed, update in-memory state & emit events
@@ -588,7 +589,9 @@ export class Room extends EventEmitter {
588589
this._roomEncryption?.enableSessionBackup(sessionBackup);
589590
// TODO: do we really want to do this every time you open the app?
590591
if (this._timeline) {
591-
this._roomEncryption.restoreMissingSessionsFromBackup(this._timeline.remoteEntries);
592+
this._platform.logger.run("enableSessionBackup", log => {
593+
return this._roomEncryption.restoreMissingSessionsFromBackup(this._timeline.remoteEntries, log);
594+
});
592595
}
593596
}
594597

@@ -675,7 +678,7 @@ export class Room extends EventEmitter {
675678
if (this._roomEncryption) {
676679
this._timeline.enableEncryption(this._decryptEntries.bind(this, DecryptionSource.Timeline));
677680
}
678-
await this._timeline.load(this._user);
681+
await this._timeline.load(this._user, log);
679682
return this._timeline;
680683
});
681684
}
@@ -745,10 +748,10 @@ export class Room extends EventEmitter {
745748
}
746749

747750
class DecryptionRequest {
748-
constructor(decryptFn) {
751+
constructor(decryptFn, log) {
749752
this._cancelled = false;
750753
this.preparation = null;
751-
this._promise = decryptFn(this);
754+
this._promise = log.wrap("decryptEntries", log => decryptFn(this, log));
752755
}
753756

754757
complete() {

0 commit comments

Comments
 (0)