log both to idb storage and console, include open items in export

refactor logging api so a logger has multiple reporters, IDBLogPersister
and/or ConsoleReporter.

By default, we add the idb persister for production and both for dev
You can also inject your own logger when creating the platform now.
This commit is contained in:
Bruno Windels 2022-05-06 15:54:45 +02:00
parent 1a08616df1
commit 0fdc6b1c3a
9 changed files with 182 additions and 103 deletions

View file

@ -136,7 +136,8 @@ export class SettingsViewModel extends ViewModel {
} }
async exportLogs() { async exportLogs() {
const logExport = await this.logger.export(); const persister = this.logger.reporters.find(r => typeof r.export === "function");
const logExport = await persister.export();
this.platform.saveFileAs(logExport.asBlob(), `hydrogen-logs-${this.platform.clock.now()}.json`); this.platform.saveFileAs(logExport.asBlob(), `hydrogen-logs-${this.platform.clock.now()}.json`);
} }

View file

@ -14,6 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
export {Logger} from "./logging/Logger";
export {IDBLogPersister} from "./logging/IDBLogPersister";
export {ConsoleReporter} from "./logging/ConsoleReporter";
export {Platform} from "./platform/web/Platform.js"; export {Platform} from "./platform/web/Platform.js";
export {Client, LoadStatus} from "./matrix/Client.js"; export {Client, LoadStatus} from "./matrix/Client.js";
export {RoomStatus} from "./matrix/room/common"; export {RoomStatus} from "./matrix/room/common";

View file

@ -13,22 +13,27 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {BaseLogger} from "./BaseLogger";
import {LogItem} from "./LogItem";
import type {ILogItem, LogItemValues, ILogExport} from "./types";
export class ConsoleLogger extends BaseLogger { import type {ILogger, ILogItem, LogItemValues, ILogReporter} from "./types";
_persistItem(item: LogItem): void { import type {LogItem} from "./LogItem";
printToConsole(item);
export class ConsoleReporter implements ILogReporter {
private logger?: ILogger;
reportItem(item: ILogItem): void {
printToConsole(item as LogItem);
} }
async export(): Promise<ILogExport | undefined> { setLogger(logger: ILogger) {
return undefined; this.logger = logger;
} }
printOpenItems(): void { printOpenItems(): void {
for (const item of this._openItems) { if (!this.logger) {
this._persistItem(item); return;
}
for (const item of this.logger.getOpenRootItems()) {
this.reportItem(item);
} }
} }
} }

View file

@ -22,36 +22,69 @@ import {
iterateCursor, iterateCursor,
fetchResults, fetchResults,
} from "../matrix/storage/idb/utils"; } from "../matrix/storage/idb/utils";
import {BaseLogger} from "./BaseLogger";
import type {Interval} from "../platform/web/dom/Clock"; import type {Interval} from "../platform/web/dom/Clock";
import type {Platform} from "../platform/web/Platform.js"; import type {Platform} from "../platform/web/Platform.js";
import type {BlobHandle} from "../platform/web/dom/BlobHandle.js"; import type {BlobHandle} from "../platform/web/dom/BlobHandle.js";
import type {ILogItem, ILogExport, ISerializedItem} from "./types"; import type {ILogItem, ILogger, ILogReporter, ISerializedItem} from "./types";
import type {LogFilter} from "./LogFilter"; import {LogFilter} from "./LogFilter";
type QueuedItem = { type QueuedItem = {
json: string; json: string;
id?: number; id?: number;
} }
export class IDBLogger extends BaseLogger { type Options = {
private readonly _name: string; name: string,
private readonly _limit: number; flushInterval?: number,
limit?: number,
platform: Platform,
serializedTransformer?: (item: ISerializedItem) => ISerializedItem
}
export class IDBLogPersister implements ILogReporter {
private readonly _flushInterval: Interval; private readonly _flushInterval: Interval;
private _queuedItems: QueuedItem[]; private _queuedItems: QueuedItem[];
private readonly options: Options;
private logger?: ILogger;
constructor(options: {name: string, flushInterval?: number, limit?: number, platform: Platform, serializedTransformer?: (item: ISerializedItem) => ISerializedItem}) { constructor(options: Options) {
super(options); this.options = options;
const {name, flushInterval = 60 * 1000, limit = 3000} = options;
this._name = name;
this._limit = limit;
this._queuedItems = this._loadQueuedItems(); this._queuedItems = this._loadQueuedItems();
// TODO: also listen for unload just in case sync keeps on running after pagehide is fired? // TODO: also listen for unload just in case sync keeps on running after pagehide is fired?
window.addEventListener("pagehide", this, false); window.addEventListener("pagehide", this, false);
this._flushInterval = this._platform.clock.createInterval(() => this._tryFlush(), flushInterval); this._flushInterval = this.options.platform.clock.createInterval(
() => this._tryFlush(),
this.options.flushInterval ?? 60 * 1000
);
}
setLogger(logger: ILogger): void {
this.logger = logger;
}
reportItem(logItem: ILogItem, filter: LogFilter, forced: boolean): void {
const queuedItem = this.prepareItemForQueue(logItem, filter, forced);
if (queuedItem) {
this._queuedItems.push(queuedItem);
}
}
async export(): Promise<IDBLogExport> {
const db = await this._openDB();
try {
const txn = db.transaction(["logs"], "readonly");
const logs = txn.objectStore("logs");
const storedItems: QueuedItem[] = await fetchResults(logs.openCursor(), () => false);
const openItems = this.getSerializedOpenItems();
const allItems = storedItems.concat(this._queuedItems).concat(openItems);
return new IDBLogExport(allItems, this, this.options.platform);
} finally {
try {
db.close();
} catch (e) {}
}
} }
// TODO: move dispose to ILogger, listen to pagehide elsewhere and call dispose from there, which calls _finishAllAndFlush
dispose(): void { dispose(): void {
window.removeEventListener("pagehide", this, false); window.removeEventListener("pagehide", this, false);
this._flushInterval.dispose(); this._flushInterval.dispose();
@ -63,7 +96,7 @@ export class IDBLogger extends BaseLogger {
} }
} }
async _tryFlush(): Promise<void> { private async _tryFlush(): Promise<void> {
const db = await this._openDB(); const db = await this._openDB();
try { try {
const txn = db.transaction(["logs"], "readwrite"); const txn = db.transaction(["logs"], "readwrite");
@ -73,9 +106,10 @@ export class IDBLogger extends BaseLogger {
logs.add(i); logs.add(i);
} }
const itemCount = await reqAsPromise(logs.count()); const itemCount = await reqAsPromise(logs.count());
if (itemCount > this._limit) { const limit = this.options.limit ?? 3000;
if (itemCount > limit) {
// delete an extra 10% so we don't need to delete every time we flush // delete an extra 10% so we don't need to delete every time we flush
let deleteAmount = (itemCount - this._limit) + Math.round(0.1 * this._limit); let deleteAmount = (itemCount - limit) + Math.round(0.1 * limit);
await iterateCursor(logs.openCursor(), (_, __, cursor) => { await iterateCursor(logs.openCursor(), (_, __, cursor) => {
cursor.delete(); cursor.delete();
deleteAmount -= 1; deleteAmount -= 1;
@ -93,14 +127,16 @@ export class IDBLogger extends BaseLogger {
} }
} }
_finishAllAndFlush(): void { private _finishAllAndFlush(): void {
this._finishOpenItems(); if (this.logger) {
this.log({l: "pagehide, closing logs", t: "navigation"}); this.logger.log({l: "pagehide, closing logs", t: "navigation"});
this.logger.forceFinish();
}
this._persistQueuedItems(this._queuedItems); this._persistQueuedItems(this._queuedItems);
} }
_loadQueuedItems(): QueuedItem[] { private _loadQueuedItems(): QueuedItem[] {
const key = `${this._name}_queuedItems`; const key = `${this.options.name}_queuedItems`;
try { try {
const json = window.localStorage.getItem(key); const json = window.localStorage.getItem(key);
if (json) { if (json) {
@ -113,44 +149,32 @@ export class IDBLogger extends BaseLogger {
return []; return [];
} }
_openDB(): Promise<IDBDatabase> { private _openDB(): Promise<IDBDatabase> {
return openDatabase(this._name, db => db.createObjectStore("logs", {keyPath: "id", autoIncrement: true}), 1); return openDatabase(this.options.name, db => db.createObjectStore("logs", {keyPath: "id", autoIncrement: true}), 1);
} }
_persistItem(logItem: ILogItem, filter: LogFilter, forced: boolean): void { private prepareItemForQueue(logItem: ILogItem, filter: LogFilter, forced: boolean): QueuedItem | undefined {
const serializedItem = logItem.serialize(filter, undefined, forced); let serializedItem = logItem.serialize(filter, undefined, forced);
if (serializedItem) { if (serializedItem) {
const transformedSerializedItem = this._serializedTransformer(serializedItem); if (this.options.serializedTransformer) {
this._queuedItems.push({ serializedItem = this.options.serializedTransformer(serializedItem);
json: JSON.stringify(transformedSerializedItem) }
}); return {
json: JSON.stringify(serializedItem)
};
} }
} }
_persistQueuedItems(items: QueuedItem[]): void { private _persistQueuedItems(items: QueuedItem[]): void {
try { try {
window.localStorage.setItem(`${this._name}_queuedItems`, JSON.stringify(items)); window.localStorage.setItem(`${this.options.name}_queuedItems`, JSON.stringify(items));
} catch (e) { } catch (e) {
console.error("Could not persist queued log items in localStorage, they will likely be lost", e); console.error("Could not persist queued log items in localStorage, they will likely be lost", e);
} }
} }
async export(): Promise<ILogExport> { /** @internal called by ILogExport.removeFromStore */
const db = await this._openDB(); async removeItems(items: QueuedItem[]): Promise<void> {
try {
const txn = db.transaction(["logs"], "readonly");
const logs = txn.objectStore("logs");
const storedItems: QueuedItem[] = await fetchResults(logs.openCursor(), () => false);
const allItems = storedItems.concat(this._queuedItems);
return new IDBLogExport(allItems, this, this._platform);
} finally {
try {
db.close();
} catch (e) {}
}
}
async _removeItems(items: QueuedItem[]): Promise<void> {
const db = await this._openDB(); const db = await this._openDB();
try { try {
const txn = db.transaction(["logs"], "readwrite"); const txn = db.transaction(["logs"], "readwrite");
@ -173,14 +197,29 @@ export class IDBLogger extends BaseLogger {
} catch (e) {} } catch (e) {}
} }
} }
private getSerializedOpenItems(): QueuedItem[] {
const openItems: QueuedItem[] = [];
if (!this.logger) {
return openItems;
}
const filter = new LogFilter();
for(const item of this.logger!.getOpenRootItems()) {
const openItem = this.prepareItemForQueue(item, filter, false);
if (openItem) {
openItems.push(openItem);
}
}
return openItems;
}
} }
class IDBLogExport implements ILogExport { export class IDBLogExport {
private readonly _items: QueuedItem[]; private readonly _items: QueuedItem[];
private readonly _logger: IDBLogger; private readonly _logger: IDBLogPersister;
private readonly _platform: Platform; private readonly _platform: Platform;
constructor(items: QueuedItem[], logger: IDBLogger, platform: Platform) { constructor(items: QueuedItem[], logger: IDBLogPersister, platform: Platform) {
this._items = items; this._items = items;
this._logger = logger; this._logger = logger;
this._platform = platform; this._platform = platform;
@ -194,18 +233,23 @@ class IDBLogExport implements ILogExport {
* @return {Promise} * @return {Promise}
*/ */
removeFromStore(): Promise<void> { removeFromStore(): Promise<void> {
return this._logger._removeItems(this._items); return this._logger.removeItems(this._items);
} }
asBlob(): BlobHandle { asBlob(): BlobHandle {
const json = this.asJSON();
const buffer: Uint8Array = this._platform.encoding.utf8.encode(json);
const blob: BlobHandle = this._platform.createBlob(buffer, "application/json");
return blob;
}
asJSON(): string {
const log = { const log = {
formatVersion: 1, formatVersion: 1,
appVersion: this._platform.updateService?.version, appVersion: this._platform.updateService?.version,
items: this._items.map(i => JSON.parse(i.json)) items: this._items.map(i => JSON.parse(i.json))
}; };
const json = JSON.stringify(log); const json = JSON.stringify(log);
const buffer: Uint8Array = this._platform.encoding.utf8.encode(json); return json;
const blob: BlobHandle = this._platform.createBlob(buffer, "application/json");
return blob;
} }
} }

View file

@ -16,7 +16,7 @@ limitations under the License.
*/ */
import {LogLevel, LogFilter} from "./LogFilter"; import {LogLevel, LogFilter} from "./LogFilter";
import type {BaseLogger} from "./BaseLogger"; import type {Logger} from "./Logger";
import type {ISerializedItem, ILogItem, LogItemValues, LabelOrValues, FilterCreator, LogCallback} from "./types"; import type {ISerializedItem, ILogItem, LogItemValues, LabelOrValues, FilterCreator, LogCallback} from "./types";
export class LogItem implements ILogItem { export class LogItem implements ILogItem {
@ -25,11 +25,11 @@ export class LogItem implements ILogItem {
public error?: Error; public error?: Error;
public end?: number; public end?: number;
private _values: LogItemValues; private _values: LogItemValues;
protected _logger: BaseLogger; protected _logger: Logger;
private _filterCreator?: FilterCreator; private _filterCreator?: FilterCreator;
private _children?: Array<LogItem>; private _children?: Array<LogItem>;
constructor(labelOrValues: LabelOrValues, logLevel: LogLevel, logger: BaseLogger, filterCreator?: FilterCreator) { constructor(labelOrValues: LabelOrValues, logLevel: LogLevel, logger: Logger, filterCreator?: FilterCreator) {
this._logger = logger; this._logger = logger;
this.start = logger._now(); this.start = logger._now();
// (l)abel // (l)abel
@ -38,7 +38,7 @@ export class LogItem implements ILogItem {
this._filterCreator = filterCreator; this._filterCreator = filterCreator;
} }
/** start a new root log item and run it detached mode, see BaseLogger.runDetached */ /** start a new root log item and run it detached mode, see Logger.runDetached */
runDetached(labelOrValues: LabelOrValues, callback: LogCallback<unknown>, logLevel?: LogLevel, filterCreator?: FilterCreator): ILogItem { runDetached(labelOrValues: LabelOrValues, callback: LogCallback<unknown>, logLevel?: LogLevel, filterCreator?: FilterCreator): ILogItem {
return this._logger.runDetached(labelOrValues, callback, logLevel, filterCreator); return this._logger.runDetached(labelOrValues, callback, logLevel, filterCreator);
} }
@ -253,7 +253,7 @@ export class LogItem implements ILogItem {
return item; return item;
} }
get logger(): BaseLogger { get logger(): Logger {
return this._logger; return this._logger;
} }

View file

@ -17,17 +17,17 @@ limitations under the License.
import {LogItem} from "./LogItem"; import {LogItem} from "./LogItem";
import {LogLevel, LogFilter} from "./LogFilter"; import {LogLevel, LogFilter} from "./LogFilter";
import type {ILogger, ILogExport, FilterCreator, LabelOrValues, LogCallback, ILogItem, ISerializedItem} from "./types"; import type {ILogger, ILogReporter, FilterCreator, LabelOrValues, LogCallback, ILogItem, ISerializedItem} from "./types";
import type {Platform} from "../platform/web/Platform.js"; import type {Platform} from "../platform/web/Platform.js";
export abstract class BaseLogger implements ILogger { export class Logger implements ILogger {
protected _openItems: Set<LogItem> = new Set(); protected _openItems: Set<LogItem> = new Set();
protected _platform: Platform; protected _platform: Platform;
protected _serializedTransformer: (item: ISerializedItem) => ISerializedItem; protected _serializedTransformer: (item: ISerializedItem) => ISerializedItem;
public readonly reporters: ILogReporter[] = [];
constructor({platform, serializedTransformer = (item: ISerializedItem) => item}) { constructor({platform}) {
this._platform = platform; this._platform = platform;
this._serializedTransformer = serializedTransformer;
} }
log(labelOrValues: LabelOrValues, logLevel: LogLevel = LogLevel.Info): void { log(labelOrValues: LabelOrValues, logLevel: LogLevel = LogLevel.Info): void {
@ -79,10 +79,10 @@ export abstract class BaseLogger implements ILogger {
return this._run(item, callback, logLevel, true, filterCreator); return this._run(item, callback, logLevel, true, filterCreator);
} }
_run<T>(item: LogItem, callback: LogCallback<T>, logLevel: LogLevel, wantResult: true, filterCreator?: FilterCreator): T; private _run<T>(item: LogItem, callback: LogCallback<T>, logLevel: LogLevel, wantResult: true, filterCreator?: FilterCreator): T;
// we don't return if we don't throw, as we don't have anything to return when an error is caught but swallowed for the fire-and-forget case. // we don't return if we don't throw, as we don't have anything to return when an error is caught but swallowed for the fire-and-forget case.
_run<T>(item: LogItem, callback: LogCallback<T>, logLevel: LogLevel, wantResult: false, filterCreator?: FilterCreator): void; private _run<T>(item: LogItem, callback: LogCallback<T>, logLevel: LogLevel, wantResult: false, filterCreator?: FilterCreator): void;
_run<T>(item: LogItem, callback: LogCallback<T>, logLevel: LogLevel, wantResult: boolean, filterCreator?: FilterCreator): T | void { private _run<T>(item: LogItem, callback: LogCallback<T>, logLevel: LogLevel, wantResult: boolean, filterCreator?: FilterCreator): T | void {
this._openItems.add(item); this._openItems.add(item);
const finishItem = () => { const finishItem = () => {
@ -134,7 +134,16 @@ export abstract class BaseLogger implements ILogger {
} }
} }
_finishOpenItems() { addReporter(reporter: ILogReporter): void {
reporter.setLogger(this);
this.reporters.push(reporter);
}
getOpenRootItems(): Iterable<ILogItem> {
return this._openItems;
}
forceFinish() {
for (const openItem of this._openItems) { for (const openItem of this._openItems) {
openItem.forceFinish(); openItem.forceFinish();
try { try {
@ -150,19 +159,24 @@ export abstract class BaseLogger implements ILogger {
this._openItems.clear(); this._openItems.clear();
} }
abstract _persistItem(item: LogItem, filter?: LogFilter, forced?: boolean): void; /** @internal */
_persistItem(item: LogItem, filter?: LogFilter, forced?: boolean): void {
abstract export(): Promise<ILogExport | undefined>; for (var i = 0; i < this.reporters.length; i += 1) {
this.reporters[i].reportItem(item, filter, forced);
}
}
// expose log level without needing // expose log level without needing
get level(): typeof LogLevel { get level(): typeof LogLevel {
return LogLevel; return LogLevel;
} }
/** @internal */
_now(): number { _now(): number {
return this._platform.clock.now(); return this._platform.clock.now();
} }
/** @internal */
_createRefId(): number { _createRefId(): number {
return Math.round(this._platform.random() * Number.MAX_SAFE_INTEGER); return Math.round(this._platform.random() * Number.MAX_SAFE_INTEGER);
} }
@ -171,7 +185,7 @@ export abstract class BaseLogger implements ILogger {
class DeferredPersistRootLogItem extends LogItem { class DeferredPersistRootLogItem extends LogItem {
finish() { finish() {
super.finish(); super.finish();
(this._logger as BaseLogger)._persistItem(this, undefined, false); (this._logger as Logger)._persistItem(this, undefined, false);
} }
forceFinish() { forceFinish() {

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {LogLevel} from "./LogFilter"; import {LogLevel} from "./LogFilter";
import type {ILogger, ILogExport, ILogItem, LabelOrValues, LogCallback, LogItemValues} from "./types"; import type {ILogger, ILogItem, LabelOrValues, LogCallback, LogItemValues} from "./types";
function noop (): void {} function noop (): void {}
@ -23,6 +23,14 @@ export class NullLogger implements ILogger {
log(): void {} log(): void {}
addReporter() {}
getOpenRootItems(): Iterable<ILogItem> {
return [];
}
forceFinish(): void {}
child(): ILogItem { child(): ILogItem {
return this.item; return this.item;
} }
@ -44,10 +52,6 @@ export class NullLogger implements ILogger {
return this.item; return this.item;
} }
async export(): Promise<ILogExport | undefined> {
return undefined;
}
get level(): typeof LogLevel { get level(): typeof LogLevel {
return LogLevel; return LogLevel;
} }

View file

@ -16,7 +16,6 @@ limitations under the License.
*/ */
import {LogLevel, LogFilter} from "./LogFilter"; import {LogLevel, LogFilter} from "./LogFilter";
import type {BaseLogger} from "./BaseLogger";
import type {BlobHandle} from "../platform/web/dom/BlobHandle.js"; import type {BlobHandle} from "../platform/web/dom/BlobHandle.js";
export interface ISerializedItem { export interface ISerializedItem {
@ -40,7 +39,7 @@ export interface ILogItem {
readonly level: typeof LogLevel; readonly level: typeof LogLevel;
readonly end?: number; readonly end?: number;
readonly start?: number; readonly start?: number;
readonly values: LogItemValues; readonly values: Readonly<LogItemValues>;
wrap<T>(labelOrValues: LabelOrValues, callback: LogCallback<T>, logLevel?: LogLevel, filterCreator?: FilterCreator): T; wrap<T>(labelOrValues: LabelOrValues, callback: LogCallback<T>, logLevel?: LogLevel, filterCreator?: FilterCreator): T;
/*** This is sort of low-level, you probably want to use wrap. If you do use it, it should only be called once. */ /*** This is sort of low-level, you probably want to use wrap. If you do use it, it should only be called once. */
run<T>(callback: LogCallback<T>): T; run<T>(callback: LogCallback<T>): T;
@ -74,14 +73,20 @@ export interface ILogger {
wrapOrRun<T>(item: ILogItem | undefined, labelOrValues: LabelOrValues, callback: LogCallback<T>, logLevel?: LogLevel, filterCreator?: FilterCreator): T; wrapOrRun<T>(item: ILogItem | undefined, labelOrValues: LabelOrValues, callback: LogCallback<T>, logLevel?: LogLevel, filterCreator?: FilterCreator): T;
runDetached<T>(labelOrValues: LabelOrValues, callback: LogCallback<T>, logLevel?: LogLevel, filterCreator?: FilterCreator): ILogItem; runDetached<T>(labelOrValues: LabelOrValues, callback: LogCallback<T>, logLevel?: LogLevel, filterCreator?: FilterCreator): ILogItem;
run<T>(labelOrValues: LabelOrValues, callback: LogCallback<T>, logLevel?: LogLevel, filterCreator?: FilterCreator): T; run<T>(labelOrValues: LabelOrValues, callback: LogCallback<T>, logLevel?: LogLevel, filterCreator?: FilterCreator): T;
export(): Promise<ILogExport | undefined>;
get level(): typeof LogLevel; get level(): typeof LogLevel;
getOpenRootItems(): Iterable<ILogItem>;
addReporter(reporter: ILogReporter): void;
get reporters(): ReadonlyArray<ILogReporter>;
/**
* force-finishes any open items and passes them to the reporter, with the forced flag set.
* Good think to do when the page is being closed to not lose any logs.
**/
forceFinish(): void;
} }
export interface ILogExport { export interface ILogReporter {
get count(): number; setLogger(logger: ILogger): void;
removeFromStore(): Promise<void>; reportItem(item: ILogItem, filter?: LogFilter, forced?: boolean): void;
asBlob(): BlobHandle;
} }
export type LogItemValues = { export type LogItemValues = {

View file

@ -21,8 +21,9 @@ import {SessionInfoStorage} from "../../matrix/sessioninfo/localstorage/SessionI
import {SettingsStorage} from "./dom/SettingsStorage.js"; import {SettingsStorage} from "./dom/SettingsStorage.js";
import {Encoding} from "./utils/Encoding.js"; import {Encoding} from "./utils/Encoding.js";
import {OlmWorker} from "../../matrix/e2ee/OlmWorker.js"; import {OlmWorker} from "../../matrix/e2ee/OlmWorker.js";
import {IDBLogger} from "../../logging/IDBLogger"; import {IDBLogPersister} from "../../logging/IDBLogPersister";
import {ConsoleLogger} from "../../logging/ConsoleLogger"; import {ConsoleReporter} from "../../logging/ConsoleReporter";
import {Logger} from "../../logging/Logger";
import {RootView} from "./ui/RootView.js"; import {RootView} from "./ui/RootView.js";
import {Clock} from "./dom/Clock.js"; import {Clock} from "./dom/Clock.js";
import {ServiceWorkerHandler} from "./dom/ServiceWorkerHandler.js"; import {ServiceWorkerHandler} from "./dom/ServiceWorkerHandler.js";
@ -128,7 +129,7 @@ function adaptUIOnVisualViewportResize(container) {
} }
export class Platform { export class Platform {
constructor({ container, assetPaths, config, configURL, options = null, cryptoExtras = null }) { constructor({ container, assetPaths, config, configURL, logger, options = null, cryptoExtras = null }) {
this._container = container; this._container = container;
this._assetPaths = assetPaths; this._assetPaths = assetPaths;
this._config = config; this._config = config;
@ -137,7 +138,7 @@ export class Platform {
this.clock = new Clock(); this.clock = new Clock();
this.encoding = new Encoding(); this.encoding = new Encoding();
this.random = Math.random; this.random = Math.random;
this._createLogger(options?.development); this.logger = logger ?? this._createLogger(options?.development);
this.history = new History(); this.history = new History();
this.onlineStatus = new OnlineStatus(); this.onlineStatus = new OnlineStatus();
this._serviceWorkerHandler = null; this._serviceWorkerHandler = null;
@ -185,6 +186,7 @@ export class Platform {
} }
_createLogger(isDevelopment) { _createLogger(isDevelopment) {
const logger = new Logger({platform: this});
// Make sure that loginToken does not end up in the logs // Make sure that loginToken does not end up in the logs
const transformer = (item) => { const transformer = (item) => {
if (item.e?.stack) { if (item.e?.stack) {
@ -192,11 +194,12 @@ export class Platform {
} }
return item; return item;
}; };
const logPersister = new IDBLogPersister({name: "hydrogen_logs", platform: this, serializedTransformer: transformer});
logger.addReporter(logPersister);
if (isDevelopment) { if (isDevelopment) {
this.logger = new ConsoleLogger({platform: this}); logger.addReporter(new ConsoleReporter());
} else {
this.logger = new IDBLogger({name: "hydrogen_logs", platform: this, serializedTransformer: transformer});
} }
return logger;
} }
get updateService() { get updateService() {