Skip to content

Commit feff8b2

Browse files
committed
feat(Logger): Use IndexedDB to store logs in order to store more
Signed-off-by: Marcel Klehr <[email protected]>
1 parent 3889862 commit feff8b2

File tree

7 files changed

+100
-27
lines changed

7 files changed

+100
-27
lines changed

package-lock.json

Lines changed: 9 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@
9797
"buffer": "^6.0.3",
9898
"cheerio": "^1.0.0-rc.12",
9999
"core-js": "3.x",
100+
"dexie": "^4.0.11",
100101
"fast-xml-parser": "^4.2.7",
101102
"humanize-duration": "^3.25.1",
102103
"intl-messageformat": "^9.9.1",

src/lib/Account.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,6 @@ export default class Account {
198198
Logger.log(
199199
'Resource is locked, trying again soon'
200200
)
201-
await Logger.persist()
202201
return
203202
}
204203
} else {
@@ -348,7 +347,6 @@ export default class Account {
348347
await this.init()
349348
}
350349
}
351-
await Logger.persist()
352350
}
353351

354352
static async stringifyError(er:any):Promise<string> {

src/lib/IndexedDB.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import Dexie, { type EntityTable } from 'dexie'
2+
3+
interface LogMessage {
4+
id: number;
5+
dateTime: number;
6+
message: string;
7+
}
8+
9+
const db = new Dexie('floccus') as Dexie & {
10+
logs: EntityTable<
11+
LogMessage,
12+
'id' // primary key "id" (for the typings only)
13+
>;
14+
}
15+
16+
db.version(1).stores({
17+
logs: '++id, dateTime, message'
18+
})
19+
20+
export { db }
21+
export { LogMessage }
22+
23+
export async function freeStorageIfNecessary() {
24+
if (navigator.storage && navigator.storage.estimate) {
25+
let {usage, quota} = await navigator.storage.estimate()
26+
if (usage / quota > 0.9) {
27+
const oneWeekAgo = Date.now() - 60 * 60 * 1000 * 24 * 7
28+
29+
await db.logs
30+
.where('dateTime').below(oneWeekAgo)
31+
.delete()
32+
}
33+
34+
({usage, quota} = await navigator.storage.estimate())
35+
if (usage / quota > 0.6) {
36+
const oneDayAgo = Date.now() - 60 * 60 * 1000 * 24
37+
38+
await db.logs
39+
.where('dateTime').below(oneDayAgo)
40+
.delete()
41+
}
42+
}
43+
}

src/lib/Logger.js

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,38 +6,31 @@ import Crypto from './Crypto'
66
import { Share } from '@capacitor/share'
77
import { Filesystem, Directory, Encoding } from '@capacitor/filesystem'
88
import { Capacitor } from '@capacitor/core'
9+
import { db } from './IndexedDB'
910

1011
export default class Logger {
1112
static log() {
12-
const logMsg = [new Date().toISOString(), ...arguments]
13+
const dateTime = Date.now()
14+
const logMsg = [...arguments]
15+
const message = util.format.apply(util, logMsg)
1316

1417
// log to console
1518
DEBUG && console.log(util.format.apply(util, logMsg))
16-
this.messages.push(util.format.apply(util, logMsg)) // TODO: Use a linked list here to get O(n)
17-
}
18-
19-
static async persist() {
20-
const Storage = (Capacitor.getPlatform() === 'web') ? await import('./browser/BrowserAccountStorage') : await import('./native/NativeAccountStorage')
21-
await Storage.default.changeEntry(
22-
'logs',
23-
log => {
24-
const messages = this.messages
25-
this.messages = []
26-
return messages // only save the last sync run
27-
},
28-
[]
29-
)
19+
db.logs.add({dateTime, message})
20+
.catch(e => {
21+
console.error('Failed to log to IndexedDB: ', e)
22+
console.error(e)
23+
})
3024
}
3125

3226
static async getLogs() {
33-
const Storage = (Capacitor.getPlatform() === 'web') ? await import('./browser/BrowserAccountStorage') : await import('./native/NativeAccountStorage')
34-
return Storage.default.getEntry('logs', [])
27+
return db.logs.orderBy('dateTime').toArray()
3528
}
3629

3730
static async anonymizeLogs(logs) {
3831
const regex = /\[(.*?)\]\((.*?)\)|\[(.*?)\]/g
39-
const newLogs = await Parallel.map(logs, async(entry) => {
40-
return Logger.replaceAsync(entry, regex, async(match, p1, p2, p3) => {
32+
await Parallel.map(logs, async(logMessage) => {
33+
logMessage.message = await Logger.replaceAsync(logMessage.message, regex, async(match, p1, p2, p3) => {
4134
if (p1 && p2) {
4235
const hash1 = await Crypto.sha256(p1)
4336
const hash2 = await Crypto.sha256(p2)
@@ -50,8 +43,11 @@ export default class Logger {
5043
}, 1)
5144
const regex2 = /url=https?%3A%2F%2F.*$|url=https?%3A%2F%2F[^ ]*/
5245
const regex3 = /https?:\/\/[^ /]*\//
53-
return newLogs
54-
.map(line => line.replace(regex2, '###url###').replace(regex3, '###server###'))
46+
logs
47+
.forEach(logMessage => {
48+
logMessage.message = logMessage.message.replace(regex2, '###url###').replace(regex3, '###server###')
49+
})
50+
return logs
5551
}
5652

5753
static async replaceAsync(str, regex, asyncFn) {
@@ -75,7 +71,12 @@ export default class Logger {
7571
if (anonymous) {
7672
logs = await Logger.anonymizeLogs(logs)
7773
}
78-
let blob = new Blob([logs.join('\n')], {
74+
logs = logs
75+
.map(logMessage => {
76+
return new Date(logMessage.dateTime).toISOString() + ' ' + logMessage.message
77+
})
78+
.join('\n')
79+
let blob = new Blob([logs], {
7980
type: 'text/plain',
8081
endings: 'native'
8182
})
@@ -121,4 +122,3 @@ export default class Logger {
121122
}
122123
}
123124
}
124-
Logger.messages = []

src/lib/browser/BrowserController.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import uniqBy from 'lodash/uniqBy'
99
import Account from '../Account'
1010
import { STATUS_ALLGOOD, STATUS_DISABLED, STATUS_ERROR, STATUS_SYNCING } from '../interfaces/Controller'
1111
import * as Sentry from '@sentry/browser'
12+
import { freeStorageIfNecessary } from '../IndexedDB'
1213

1314
const INACTIVITY_TIMEOUT = 7 * 1000 // 7 seconds
1415
const MAX_BACKOFF_INTERVAL = 1000 * 60 * 60 // 1 hour
@@ -121,6 +122,17 @@ export default class BrowserController {
121122
}
122123
})
123124

125+
// Remove old logs
126+
127+
BrowserAccountStorage.changeEntry(
128+
'logs',
129+
log => {
130+
return []
131+
},
132+
[]
133+
)
134+
freeStorageIfNecessary()
135+
124136
// do some cleaning if this is a new version
125137

126138
browser.storage.local.get(['currentVersion', 'lastInterventionAt']).then(async d => {

src/lib/native/NativeController.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Cryptography from '../Crypto'
44
import NativeAccountStorage from './NativeAccountStorage'
55
import Account from '../Account'
66
import { STATUS_ALLGOOD, STATUS_DISABLED, STATUS_ERROR, STATUS_SYNCING } from '../interfaces/Controller'
7+
import { freeStorageIfNecessary } from '../IndexedDB'
78

89
const INACTIVITY_TIMEOUT = 1000 * 7
910
const MAX_BACKOFF_INTERVAL = 1000 * 60 * 60 // 1 hour
@@ -83,6 +84,17 @@ export default class NativeController {
8384

8485
this.alarms = new AlarmManager(this)
8586

87+
// Remove old logs
88+
89+
NativeAccountStorage.changeEntry(
90+
'logs',
91+
log => {
92+
return []
93+
},
94+
[]
95+
)
96+
freeStorageIfNecessary()
97+
8698
// lock accounts when locking is enabled
8799

88100
Storage.get({key: 'accountsLocked' }).then(async({value: accountsLocked}) => {

0 commit comments

Comments
 (0)