Skip to content

Commit 1b5b97b

Browse files
authored
Merge pull request #1903 from floccusaddon/feat/indexeddb-logs
feat(Logger): Use IndexedDB to store logs in order to store more
2 parents e788459 + 44724dc commit 1b5b97b

File tree

7 files changed

+107
-35
lines changed

7 files changed

+107
-35
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: 15 additions & 5 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
@@ -23,19 +24,18 @@ class AlarmManager {
2324

2425
async checkSync() {
2526
const accounts = await BrowserAccountStorage.getAllAccounts()
26-
const promises = []
2727
for (let accountId of accounts) {
2828
const account = await Account.get(accountId)
2929
const data = account.getData()
3030
const lastSync = data.lastSync || 0
3131
const interval = data.syncInterval || DEFAULT_SYNC_INTERVAL
3232
if (data.scheduled) {
33-
promises.push(this.ctl.scheduleSync(accountId))
33+
await this.ctl.scheduleSync(accountId)
3434
continue
3535
}
3636
if (data.error && data.errorCount > 1) {
3737
if (Date.now() > this.getBackoffInterval(interval, data.errorCount, lastSync) + lastSync) {
38-
promises.push(this.ctl.scheduleSync(accountId))
38+
await this.ctl.scheduleSync(accountId)
3939
continue
4040
}
4141
continue
@@ -44,10 +44,9 @@ class AlarmManager {
4444
Date.now() >
4545
interval * 1000 * 60 + lastSync
4646
) {
47-
promises.push(this.ctl.scheduleSync(accountId))
47+
await this.ctl.scheduleSync(accountId)
4848
}
4949
}
50-
await Promise.all(promises)
5150
}
5251

5352
/**
@@ -121,6 +120,17 @@ export default class BrowserController {
121120
}
122121
})
123122

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

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

src/lib/native/NativeController.js

Lines changed: 16 additions & 3 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
@@ -35,12 +36,12 @@ class AlarmManager {
3536
const lastSync = data.lastSync || 0
3637
const interval = data.syncInterval || DEFAULT_SYNC_INTERVAL
3738
if (data.scheduled) {
38-
this.ctl.scheduleSync(accountId)
39+
await this.ctl.scheduleSync(accountId)
3940
continue
4041
}
4142
if (data.error && data.errorCount > 1) {
4243
if (Date.now() > this.getBackoffInterval(interval, data.errorCount, lastSync) + lastSync) {
43-
this.ctl.scheduleSync(accountId)
44+
await this.ctl.scheduleSync(accountId)
4445
continue
4546
}
4647
continue
@@ -49,7 +50,7 @@ class AlarmManager {
4950
Date.now() >
5051
interval * 1000 * 60 + data.lastSync
5152
) {
52-
this.ctl.scheduleSync(accountId)
53+
await this.ctl.scheduleSync(accountId)
5354
}
5455
}
5556
}
@@ -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}) => {
@@ -133,6 +145,7 @@ export default class NativeController {
133145
if (this.schedule[accountId]) {
134146
clearTimeout(this.schedule[accountId])
135147
}
148+
console.log('scheduleSync: setting a timeout in ms :', INACTIVITY_TIMEOUT)
136149
this.schedule[accountId] = setTimeout(
137150
() => this.scheduleSync(accountId),
138151
INACTIVITY_TIMEOUT

0 commit comments

Comments
 (0)