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:
parent
1a08616df1
commit
0fdc6b1c3a
9 changed files with 182 additions and 103 deletions
|
@ -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`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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() {
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
Reference in a new issue