forked from mystiq/hydrogen-web
Merge pull request #583 from vector-im/ts-conversion-logging
Convert src/logging to typescript
This commit is contained in:
commit
efccc1e19e
29 changed files with 464 additions and 316 deletions
|
@ -184,7 +184,7 @@ import {Clock as MockClock} from "../../../../mocks/Clock.js";
|
|||
import {createMockStorage} from "../../../../mocks/Storage";
|
||||
import {ListObserver} from "../../../../mocks/ListObserver.js";
|
||||
import {createEvent, withTextBody, withContent} from "../../../../mocks/event.js";
|
||||
import {NullLogItem, NullLogger} from "../../../../logging/NullLogger.js";
|
||||
import {NullLogItem, NullLogger} from "../../../../logging/NullLogger";
|
||||
import {HomeServer as MockHomeServer} from "../../../../mocks/HomeServer.js";
|
||||
// other imports
|
||||
import {BaseMessageTile} from "./tiles/BaseMessageTile.js";
|
||||
|
|
|
@ -15,23 +15,27 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {LogItem} from "./LogItem.js";
|
||||
import {LogLevel, LogFilter} from "./LogFilter.js";
|
||||
import {LogItem} from "./LogItem";
|
||||
import {LogLevel, LogFilter} from "./LogFilter";
|
||||
import type {ILogger, ILogExport, FilterCreator, LabelOrValues, LogCallback, ILogItem} from "./types";
|
||||
import type {Platform} from "../platform/web/Platform.js";
|
||||
|
||||
export abstract class BaseLogger implements ILogger {
|
||||
protected _openItems: Set<LogItem> = new Set();
|
||||
protected _platform: Platform;
|
||||
|
||||
export class BaseLogger {
|
||||
constructor({platform}) {
|
||||
this._openItems = new Set();
|
||||
this._platform = platform;
|
||||
}
|
||||
|
||||
log(labelOrValues, logLevel = LogLevel.Info) {
|
||||
const item = new LogItem(labelOrValues, logLevel, null, this);
|
||||
item._end = item._start;
|
||||
this._persistItem(item, null, false);
|
||||
log(labelOrValues: LabelOrValues, logLevel: LogLevel = LogLevel.Info): void {
|
||||
const item = new LogItem(labelOrValues, logLevel, this);
|
||||
item.end = item.start;
|
||||
this._persistItem(item, undefined, false);
|
||||
}
|
||||
|
||||
/** if item is a log item, wrap the callback in a child of it, otherwise start a new root log item. */
|
||||
wrapOrRun(item, labelOrValues, callback, logLevel = null, filterCreator = null) {
|
||||
wrapOrRun<T>(item: ILogItem | undefined, labelOrValues: LabelOrValues, callback: LogCallback<T>, logLevel?: LogLevel, filterCreator?: FilterCreator): T {
|
||||
if (item) {
|
||||
return item.wrap(labelOrValues, callback, logLevel, filterCreator);
|
||||
} else {
|
||||
|
@ -43,28 +47,31 @@ export class BaseLogger {
|
|||
where the (async) result or errors are not propagated but still logged.
|
||||
Useful to pair with LogItem.refDetached.
|
||||
|
||||
@return {LogItem} the log item added, useful to pass to LogItem.refDetached */
|
||||
runDetached(labelOrValues, callback, logLevel = null, filterCreator = null) {
|
||||
if (logLevel === null) {
|
||||
@return {ILogItem} the log item added, useful to pass to LogItem.refDetached */
|
||||
runDetached<T>(labelOrValues: LabelOrValues, callback: LogCallback<T>, logLevel?: LogLevel, filterCreator?: FilterCreator): ILogItem {
|
||||
if (!logLevel) {
|
||||
logLevel = LogLevel.Info;
|
||||
}
|
||||
const item = new LogItem(labelOrValues, logLevel, null, this);
|
||||
this._run(item, callback, logLevel, filterCreator, false /* don't throw, nobody is awaiting */);
|
||||
const item = new LogItem(labelOrValues, logLevel, this);
|
||||
this._run(item, callback, logLevel, false /* don't throw, nobody is awaiting */, filterCreator);
|
||||
return item;
|
||||
}
|
||||
|
||||
/** run a callback wrapped in a log operation.
|
||||
Errors and duration are transparently logged, also for async operations.
|
||||
Whatever the callback returns is returned here. */
|
||||
run(labelOrValues, callback, logLevel = null, filterCreator = null) {
|
||||
if (logLevel === null) {
|
||||
run<T>(labelOrValues: LabelOrValues, callback: LogCallback<T>, logLevel?: LogLevel, filterCreator?: FilterCreator): T {
|
||||
if (logLevel === undefined) {
|
||||
logLevel = LogLevel.Info;
|
||||
}
|
||||
const item = new LogItem(labelOrValues, logLevel, null, this);
|
||||
return this._run(item, callback, logLevel, filterCreator, true);
|
||||
const item = new LogItem(labelOrValues, logLevel, this);
|
||||
return this._run(item, callback, logLevel, true, filterCreator);
|
||||
}
|
||||
|
||||
_run(item, callback, logLevel, filterCreator, shouldThrow) {
|
||||
_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.
|
||||
_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 {
|
||||
this._openItems.add(item);
|
||||
|
||||
const finishItem = () => {
|
||||
|
@ -88,24 +95,29 @@ export class BaseLogger {
|
|||
};
|
||||
|
||||
try {
|
||||
const result = item.run(callback);
|
||||
let result = item.run(callback);
|
||||
if (result instanceof Promise) {
|
||||
return result.then(promiseResult => {
|
||||
result = result.then(promiseResult => {
|
||||
finishItem();
|
||||
return promiseResult;
|
||||
}, err => {
|
||||
finishItem();
|
||||
if (shouldThrow) {
|
||||
if (wantResult) {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
}) as unknown as T;
|
||||
if (wantResult) {
|
||||
return result;
|
||||
}
|
||||
} else {
|
||||
finishItem();
|
||||
if(wantResult) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
finishItem();
|
||||
if (shouldThrow) {
|
||||
if (wantResult) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
@ -127,24 +139,20 @@ export class BaseLogger {
|
|||
this._openItems.clear();
|
||||
}
|
||||
|
||||
_persistItem() {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
abstract _persistItem(item: LogItem, filter?: LogFilter, forced?: boolean): void;
|
||||
|
||||
async export() {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
abstract export(): Promise<ILogExport | undefined>;
|
||||
|
||||
// expose log level without needing
|
||||
get level() {
|
||||
get level(): typeof LogLevel {
|
||||
return LogLevel;
|
||||
}
|
||||
|
||||
_now() {
|
||||
_now(): number {
|
||||
return this._platform.clock.now();
|
||||
}
|
||||
|
||||
_createRefId() {
|
||||
_createRefId(): number {
|
||||
return Math.round(this._platform.random() * Number.MAX_SAFE_INTEGER);
|
||||
}
|
||||
}
|
|
@ -13,32 +13,35 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
import {BaseLogger} from "./BaseLogger.js";
|
||||
import {BaseLogger} from "./BaseLogger";
|
||||
import {LogItem} from "./LogItem";
|
||||
import type {ILogItem, LogItemValues, ILogExport} from "./types";
|
||||
|
||||
export class ConsoleLogger extends BaseLogger {
|
||||
_persistItem(item) {
|
||||
_persistItem(item: LogItem): void {
|
||||
printToConsole(item);
|
||||
}
|
||||
|
||||
async export(): Promise<ILogExport | undefined> {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const excludedKeysFromTable = ["l", "id"];
|
||||
function filterValues(values) {
|
||||
if (!values) {
|
||||
return null;
|
||||
}
|
||||
function filterValues(values: LogItemValues): LogItemValues | null {
|
||||
return Object.entries(values)
|
||||
.filter(([key]) => !excludedKeysFromTable.includes(key))
|
||||
.reduce((obj, [key, value]) => {
|
||||
.reduce((obj: LogItemValues, [key, value]) => {
|
||||
obj = obj || {};
|
||||
obj[key] = value;
|
||||
return obj;
|
||||
}, null);
|
||||
}
|
||||
|
||||
function printToConsole(item) {
|
||||
function printToConsole(item: LogItem): void {
|
||||
const label = `${itemCaption(item)} (${item.duration}ms)`;
|
||||
const filteredValues = filterValues(item._values);
|
||||
const shouldGroup = item._children || filteredValues;
|
||||
const filteredValues = filterValues(item.values);
|
||||
const shouldGroup = item.children || filteredValues;
|
||||
if (shouldGroup) {
|
||||
if (item.error) {
|
||||
console.group(label);
|
||||
|
@ -58,8 +61,8 @@ function printToConsole(item) {
|
|||
if (filteredValues) {
|
||||
console.table(filteredValues);
|
||||
}
|
||||
if (item._children) {
|
||||
for(const c of item._children) {
|
||||
if (item.children) {
|
||||
for(const c of item.children) {
|
||||
printToConsole(c);
|
||||
}
|
||||
}
|
||||
|
@ -68,18 +71,18 @@ function printToConsole(item) {
|
|||
}
|
||||
}
|
||||
|
||||
function itemCaption(item) {
|
||||
if (item._values.t === "network") {
|
||||
return `${item._values.method} ${item._values.url}`;
|
||||
} else if (item._values.l && typeof item._values.id !== "undefined") {
|
||||
return `${item._values.l} ${item._values.id}`;
|
||||
} else if (item._values.l && typeof item._values.status !== "undefined") {
|
||||
return `${item._values.l} (${item._values.status})`;
|
||||
} else if (item._values.l && item.error) {
|
||||
return `${item._values.l} failed`;
|
||||
} else if (typeof item._values.ref !== "undefined") {
|
||||
return `ref ${item._values.ref}`;
|
||||
function itemCaption(item: ILogItem): string {
|
||||
if (item.values.t === "network") {
|
||||
return `${item.values.method} ${item.values.url}`;
|
||||
} else if (item.values.l && typeof item.values.id !== "undefined") {
|
||||
return `${item.values.l} ${item.values.id}`;
|
||||
} else if (item.values.l && typeof item.values.status !== "undefined") {
|
||||
return `${item.values.l} (${item.values.status})`;
|
||||
} else if (item.values.l && item.error) {
|
||||
return `${item.values.l} failed`;
|
||||
} else if (typeof item.values.ref !== "undefined") {
|
||||
return `ref ${item.values.ref}`;
|
||||
} else {
|
||||
return item._values.l || item._values.type;
|
||||
return item.values.l || item.values.type;
|
||||
}
|
||||
}
|
|
@ -22,10 +22,25 @@ import {
|
|||
iterateCursor,
|
||||
fetchResults,
|
||||
} from "../matrix/storage/idb/utils";
|
||||
import {BaseLogger} from "./BaseLogger.js";
|
||||
import {BaseLogger} from "./BaseLogger";
|
||||
import type {Interval} from "../platform/web/dom/Clock";
|
||||
import type {Platform} from "../platform/web/Platform.js";
|
||||
import type {BlobHandle} from "../platform/web/dom/BlobHandle.js";
|
||||
import type {ILogItem, ILogExport} from "./types";
|
||||
import type {LogFilter} from "./LogFilter";
|
||||
|
||||
type QueuedItem = {
|
||||
json: string;
|
||||
id?: number;
|
||||
}
|
||||
|
||||
export class IDBLogger extends BaseLogger {
|
||||
constructor(options) {
|
||||
private readonly _name: string;
|
||||
private readonly _limit: number;
|
||||
private readonly _flushInterval: Interval;
|
||||
private _queuedItems: QueuedItem[];
|
||||
|
||||
constructor(options: {name: string, flushInterval?: number, limit?: number, platform: Platform}) {
|
||||
super(options);
|
||||
const {name, flushInterval = 60 * 1000, limit = 3000} = options;
|
||||
this._name = name;
|
||||
|
@ -36,18 +51,19 @@ export class IDBLogger extends BaseLogger {
|
|||
this._flushInterval = this._platform.clock.createInterval(() => this._tryFlush(), flushInterval);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
// TODO: move dispose to ILogger, listen to pagehide elsewhere and call dispose from there, which calls _finishAllAndFlush
|
||||
dispose(): void {
|
||||
window.removeEventListener("pagehide", this, false);
|
||||
this._flushInterval.dispose();
|
||||
}
|
||||
|
||||
handleEvent(evt) {
|
||||
handleEvent(evt: Event): void {
|
||||
if (evt.type === "pagehide") {
|
||||
this._finishAllAndFlush();
|
||||
}
|
||||
}
|
||||
|
||||
async _tryFlush() {
|
||||
async _tryFlush(): Promise<void> {
|
||||
const db = await this._openDB();
|
||||
try {
|
||||
const txn = db.transaction(["logs"], "readwrite");
|
||||
|
@ -77,13 +93,13 @@ export class IDBLogger extends BaseLogger {
|
|||
}
|
||||
}
|
||||
|
||||
_finishAllAndFlush() {
|
||||
_finishAllAndFlush(): void {
|
||||
this._finishOpenItems();
|
||||
this.log({l: "pagehide, closing logs", t: "navigation"});
|
||||
this._persistQueuedItems(this._queuedItems);
|
||||
}
|
||||
|
||||
_loadQueuedItems() {
|
||||
_loadQueuedItems(): QueuedItem[] {
|
||||
const key = `${this._name}_queuedItems`;
|
||||
try {
|
||||
const json = window.localStorage.getItem(key);
|
||||
|
@ -97,18 +113,18 @@ export class IDBLogger extends BaseLogger {
|
|||
return [];
|
||||
}
|
||||
|
||||
_openDB() {
|
||||
_openDB(): Promise<IDBDatabase> {
|
||||
return openDatabase(this._name, db => db.createObjectStore("logs", {keyPath: "id", autoIncrement: true}), 1);
|
||||
}
|
||||
|
||||
_persistItem(logItem, filter, forced) {
|
||||
const serializedItem = logItem.serialize(filter, forced);
|
||||
_persistItem(logItem: ILogItem, filter: LogFilter, forced: boolean): void {
|
||||
const serializedItem = logItem.serialize(filter, undefined, forced);
|
||||
this._queuedItems.push({
|
||||
json: JSON.stringify(serializedItem)
|
||||
});
|
||||
}
|
||||
|
||||
_persistQueuedItems(items) {
|
||||
_persistQueuedItems(items: QueuedItem[]): void {
|
||||
try {
|
||||
window.localStorage.setItem(`${this._name}_queuedItems`, JSON.stringify(items));
|
||||
} catch (e) {
|
||||
|
@ -116,12 +132,12 @@ export class IDBLogger extends BaseLogger {
|
|||
}
|
||||
}
|
||||
|
||||
async export() {
|
||||
async export(): Promise<ILogExport> {
|
||||
const db = await this._openDB();
|
||||
try {
|
||||
const txn = db.transaction(["logs"], "readonly");
|
||||
const logs = txn.objectStore("logs");
|
||||
const storedItems = await fetchResults(logs.openCursor(), () => false);
|
||||
const storedItems: QueuedItem[] = await fetchResults(logs.openCursor(), () => false);
|
||||
const allItems = storedItems.concat(this._queuedItems);
|
||||
return new IDBLogExport(allItems, this, this._platform);
|
||||
} finally {
|
||||
|
@ -131,19 +147,22 @@ export class IDBLogger extends BaseLogger {
|
|||
}
|
||||
}
|
||||
|
||||
async _removeItems(items) {
|
||||
async _removeItems(items: QueuedItem[]): Promise<void> {
|
||||
const db = await this._openDB();
|
||||
try {
|
||||
const txn = db.transaction(["logs"], "readwrite");
|
||||
const logs = txn.objectStore("logs");
|
||||
for (const item of items) {
|
||||
const queuedIdx = this._queuedItems.findIndex(i => i.id === item.id);
|
||||
if (queuedIdx === -1) {
|
||||
if (typeof item.id === "number") {
|
||||
logs.delete(item.id);
|
||||
} else {
|
||||
// assume the (non-persisted) object in each array will be the same
|
||||
const queuedIdx = this._queuedItems.indexOf(item);
|
||||
if (queuedIdx === -1) {
|
||||
this._queuedItems.splice(queuedIdx, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
await txnAsPromise(txn);
|
||||
} finally {
|
||||
try {
|
||||
|
@ -153,33 +172,37 @@ export class IDBLogger extends BaseLogger {
|
|||
}
|
||||
}
|
||||
|
||||
class IDBLogExport {
|
||||
constructor(items, logger, platform) {
|
||||
class IDBLogExport implements ILogExport {
|
||||
private readonly _items: QueuedItem[];
|
||||
private readonly _logger: IDBLogger;
|
||||
private readonly _platform: Platform;
|
||||
|
||||
constructor(items: QueuedItem[], logger: IDBLogger, platform: Platform) {
|
||||
this._items = items;
|
||||
this._logger = logger;
|
||||
this._platform = platform;
|
||||
}
|
||||
|
||||
get count() {
|
||||
get count(): number {
|
||||
return this._items.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {Promise}
|
||||
*/
|
||||
removeFromStore() {
|
||||
removeFromStore(): Promise<void> {
|
||||
return this._logger._removeItems(this._items);
|
||||
}
|
||||
|
||||
asBlob() {
|
||||
asBlob(): BlobHandle {
|
||||
const log = {
|
||||
formatVersion: 1,
|
||||
appVersion: this._platform.updateService?.version,
|
||||
items: this._items.map(i => JSON.parse(i.json))
|
||||
};
|
||||
const json = JSON.stringify(log);
|
||||
const buffer = this._platform.encoding.utf8.encode(json);
|
||||
const blob = this._platform.createBlob(buffer, "application/json");
|
||||
const buffer: Uint8Array = this._platform.encoding.utf8.encode(json);
|
||||
const blob: BlobHandle = this._platform.createBlob(buffer, "application/json");
|
||||
return blob;
|
||||
}
|
||||
}
|
|
@ -14,31 +14,35 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
export const LogLevel = {
|
||||
All: 1,
|
||||
Debug: 2,
|
||||
Detail: 3,
|
||||
Info: 4,
|
||||
Warn: 5,
|
||||
Error: 6,
|
||||
Fatal: 7,
|
||||
Off: 8,
|
||||
import type {ILogItem, ISerializedItem} from "./types";
|
||||
|
||||
export enum LogLevel {
|
||||
All = 1,
|
||||
Debug,
|
||||
Detail,
|
||||
Info,
|
||||
Warn,
|
||||
Error,
|
||||
Fatal,
|
||||
Off
|
||||
}
|
||||
|
||||
export class LogFilter {
|
||||
constructor(parentFilter) {
|
||||
private _min?: LogLevel;
|
||||
private _parentFilter?: LogFilter;
|
||||
|
||||
constructor(parentFilter?: LogFilter) {
|
||||
this._parentFilter = parentFilter;
|
||||
this._min = null;
|
||||
}
|
||||
|
||||
filter(item, children) {
|
||||
filter(item: ILogItem, children: ISerializedItem[] | null): boolean {
|
||||
if (this._parentFilter) {
|
||||
if (!this._parentFilter.filter(item, children)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// neither our children or us have a loglevel high enough, filter out.
|
||||
if (this._min !== null && !Array.isArray(children) && item.logLevel < this._min) {
|
||||
if (this._min !== undefined && !Array.isArray(children) && item.logLevel < this._min) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
|
@ -46,7 +50,7 @@ export class LogFilter {
|
|||
}
|
||||
|
||||
/* methods to build the filter */
|
||||
minLevel(logLevel) {
|
||||
minLevel(logLevel: LogLevel): LogFilter {
|
||||
this._min = logLevel;
|
||||
return this;
|
||||
}
|
|
@ -15,39 +15,47 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {LogLevel, LogFilter} from "./LogFilter.js";
|
||||
import {LogLevel, LogFilter} from "./LogFilter";
|
||||
import type {BaseLogger} from "./BaseLogger";
|
||||
import type {ISerializedItem, ILogItem, LogItemValues, LabelOrValues, FilterCreator, LogCallback} from "./types";
|
||||
|
||||
export class LogItem {
|
||||
constructor(labelOrValues, logLevel, filterCreator, logger) {
|
||||
export class LogItem implements ILogItem {
|
||||
public readonly start: number;
|
||||
public logLevel: LogLevel;
|
||||
public error?: Error;
|
||||
public end?: number;
|
||||
private _values: LogItemValues;
|
||||
private _logger: BaseLogger;
|
||||
private _filterCreator?: FilterCreator;
|
||||
private _children?: Array<LogItem>;
|
||||
|
||||
constructor(labelOrValues: LabelOrValues, logLevel: LogLevel, logger: BaseLogger, filterCreator?: FilterCreator) {
|
||||
this._logger = logger;
|
||||
this._start = logger._now();
|
||||
this._end = null;
|
||||
this.start = logger._now();
|
||||
// (l)abel
|
||||
this._values = typeof labelOrValues === "string" ? {l: labelOrValues} : labelOrValues;
|
||||
this.error = null;
|
||||
this.logLevel = logLevel;
|
||||
this._children = null;
|
||||
this._filterCreator = filterCreator;
|
||||
}
|
||||
|
||||
/** start a new root log item and run it detached mode, see BaseLogger.runDetached */
|
||||
runDetached(labelOrValues, callback, logLevel, filterCreator) {
|
||||
runDetached(labelOrValues: LabelOrValues, callback: LogCallback<unknown>, logLevel?: LogLevel, filterCreator?: FilterCreator): ILogItem {
|
||||
return this._logger.runDetached(labelOrValues, callback, logLevel, filterCreator);
|
||||
}
|
||||
|
||||
/** start a new detached root log item and log a reference to it from this item */
|
||||
wrapDetached(labelOrValues, callback, logLevel, filterCreator) {
|
||||
wrapDetached(labelOrValues: LabelOrValues, callback: LogCallback<unknown>, logLevel?: LogLevel, filterCreator?: FilterCreator): void {
|
||||
this.refDetached(this.runDetached(labelOrValues, callback, logLevel, filterCreator));
|
||||
}
|
||||
|
||||
/** logs a reference to a different log item, usually obtained from runDetached.
|
||||
This is useful if the referenced operation can't be awaited. */
|
||||
refDetached(logItem, logLevel = null) {
|
||||
refDetached(logItem: ILogItem, logLevel?: LogLevel): void {
|
||||
logItem.ensureRefId();
|
||||
return this.log({ref: logItem._values.refId}, logLevel);
|
||||
this.log({ref: logItem.values.refId}, logLevel);
|
||||
}
|
||||
|
||||
ensureRefId() {
|
||||
ensureRefId(): void {
|
||||
if (!this._values.refId) {
|
||||
this.set("refId", this._logger._createRefId());
|
||||
}
|
||||
|
@ -56,29 +64,33 @@ export class LogItem {
|
|||
/**
|
||||
* Creates a new child item and runs it in `callback`.
|
||||
*/
|
||||
wrap(labelOrValues, callback, logLevel = null, filterCreator = null) {
|
||||
wrap<T>(labelOrValues: LabelOrValues, callback: LogCallback<T>, logLevel?: LogLevel, filterCreator?: FilterCreator): T {
|
||||
const item = this.child(labelOrValues, logLevel, filterCreator);
|
||||
return item.run(callback);
|
||||
}
|
||||
|
||||
get duration() {
|
||||
if (this._end) {
|
||||
return this._end - this._start;
|
||||
get duration(): number | undefined {
|
||||
if (this.end) {
|
||||
return this.end - this.start;
|
||||
} else {
|
||||
return null;
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
durationWithoutType(type) {
|
||||
return this.duration - this.durationOfType(type);
|
||||
durationWithoutType(type: string): number | undefined {
|
||||
const durationOfType = this.durationOfType(type);
|
||||
if (this.duration && durationOfType) {
|
||||
return this.duration - durationOfType;
|
||||
}
|
||||
}
|
||||
|
||||
durationOfType(type) {
|
||||
durationOfType(type: string): number | undefined {
|
||||
if (this._values.t === type) {
|
||||
return this.duration;
|
||||
} else if (this._children) {
|
||||
return this._children.reduce((sum, c) => {
|
||||
return sum + c.durationOfType(type);
|
||||
const duration = c.durationOfType(type);
|
||||
return sum + (duration ?? 0);
|
||||
}, 0);
|
||||
} else {
|
||||
return 0;
|
||||
|
@ -91,12 +103,12 @@ export class LogItem {
|
|||
*
|
||||
* Hence, the child item is not returned.
|
||||
*/
|
||||
log(labelOrValues, logLevel = null) {
|
||||
const item = this.child(labelOrValues, logLevel, null);
|
||||
item._end = item._start;
|
||||
log(labelOrValues: LabelOrValues, logLevel?: LogLevel): void {
|
||||
const item = this.child(labelOrValues, logLevel);
|
||||
item.end = item.start;
|
||||
}
|
||||
|
||||
set(key, value) {
|
||||
set(key: string | object, value?: unknown): void {
|
||||
if(typeof key === "object") {
|
||||
const values = key;
|
||||
Object.assign(this._values, values);
|
||||
|
@ -105,7 +117,7 @@ export class LogItem {
|
|||
}
|
||||
}
|
||||
|
||||
serialize(filter, parentStartTime = null, forced) {
|
||||
serialize(filter: LogFilter, parentStartTime: number | undefined, forced: boolean): ISerializedItem | undefined {
|
||||
if (this._filterCreator) {
|
||||
try {
|
||||
filter = this._filterCreator(new LogFilter(filter), this);
|
||||
|
@ -113,10 +125,10 @@ export class LogItem {
|
|||
console.error("Error creating log filter", err);
|
||||
}
|
||||
}
|
||||
let children;
|
||||
if (this._children !== null) {
|
||||
children = this._children.reduce((array, c) => {
|
||||
const s = c.serialize(filter, this._start, false);
|
||||
let children: Array<ISerializedItem> | null = null;
|
||||
if (this._children) {
|
||||
children = this._children.reduce((array: Array<ISerializedItem>, c) => {
|
||||
const s = c.serialize(filter, this.start, false);
|
||||
if (s) {
|
||||
if (array === null) {
|
||||
array = [];
|
||||
|
@ -127,12 +139,12 @@ export class LogItem {
|
|||
}, null);
|
||||
}
|
||||
if (filter && !filter.filter(this, children)) {
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
// in (v)alues, (l)abel and (t)ype are also reserved.
|
||||
const item = {
|
||||
const item: ISerializedItem = {
|
||||
// (s)tart
|
||||
s: parentStartTime === null ? this._start : this._start - parentStartTime,
|
||||
s: typeof parentStartTime === "number" ? this.start - parentStartTime : this.start,
|
||||
// (d)uration
|
||||
d: this.duration,
|
||||
// (v)alues
|
||||
|
@ -171,20 +183,19 @@ export class LogItem {
|
|||
* @param {Function} callback [description]
|
||||
* @return {[type]} [description]
|
||||
*/
|
||||
run(callback) {
|
||||
if (this._end !== null) {
|
||||
run<T>(callback: LogCallback<T>): T {
|
||||
if (this.end !== undefined) {
|
||||
console.trace("log item is finished, additional logs will likely not be recorded");
|
||||
}
|
||||
let result;
|
||||
try {
|
||||
result = callback(this);
|
||||
const result = callback(this);
|
||||
if (result instanceof Promise) {
|
||||
return result.then(promiseResult => {
|
||||
this.finish();
|
||||
return promiseResult;
|
||||
}, err => {
|
||||
throw this.catch(err);
|
||||
});
|
||||
}) as unknown as T;
|
||||
} else {
|
||||
this.finish();
|
||||
return result;
|
||||
|
@ -198,45 +209,53 @@ export class LogItem {
|
|||
* finished the item, recording the end time. After finishing, an item can't be modified anymore as it will be persisted.
|
||||
* @internal shouldn't typically be called by hand. allows to force finish if a promise is still running when closing the app
|
||||
*/
|
||||
finish() {
|
||||
if (this._end === null) {
|
||||
if (this._children !== null) {
|
||||
finish(): void {
|
||||
if (this.end === undefined) {
|
||||
if (this._children) {
|
||||
for(const c of this._children) {
|
||||
c.finish();
|
||||
}
|
||||
}
|
||||
this._end = this._logger._now();
|
||||
this.end = this._logger._now();
|
||||
}
|
||||
}
|
||||
|
||||
// expose log level without needing import everywhere
|
||||
get level() {
|
||||
get level(): typeof LogLevel {
|
||||
return LogLevel;
|
||||
}
|
||||
|
||||
catch(err) {
|
||||
catch(err: Error): Error {
|
||||
this.error = err;
|
||||
this.logLevel = LogLevel.Error;
|
||||
this.finish();
|
||||
return err;
|
||||
}
|
||||
|
||||
child(labelOrValues, logLevel, filterCreator) {
|
||||
if (this._end !== null) {
|
||||
child(labelOrValues: LabelOrValues, logLevel?: LogLevel, filterCreator?: FilterCreator): LogItem {
|
||||
if (this.end) {
|
||||
console.trace("log item is finished, additional logs will likely not be recorded");
|
||||
}
|
||||
if (!logLevel) {
|
||||
logLevel = this.logLevel || LogLevel.Info;
|
||||
}
|
||||
const item = new LogItem(labelOrValues, logLevel, filterCreator, this._logger);
|
||||
if (this._children === null) {
|
||||
const item = new LogItem(labelOrValues, logLevel, this._logger, filterCreator);
|
||||
if (!this._children) {
|
||||
this._children = [];
|
||||
}
|
||||
this._children.push(item);
|
||||
return item;
|
||||
}
|
||||
|
||||
get logger() {
|
||||
get logger(): BaseLogger {
|
||||
return this._logger;
|
||||
}
|
||||
|
||||
get values(): LogItemValues {
|
||||
return this._values;
|
||||
}
|
||||
|
||||
get children(): Array<LogItem> | undefined {
|
||||
return this._children;
|
||||
}
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
/*
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
import {LogLevel} from "./LogFilter.js";
|
||||
|
||||
function noop () {}
|
||||
|
||||
|
||||
export class NullLogger {
|
||||
constructor() {
|
||||
this.item = new NullLogItem(this);
|
||||
}
|
||||
|
||||
log() {}
|
||||
|
||||
run(_, callback) {
|
||||
return callback(this.item);
|
||||
}
|
||||
|
||||
wrapOrRun(item, _, callback) {
|
||||
if (item) {
|
||||
return item.wrap(null, callback);
|
||||
} else {
|
||||
return this.run(null, callback);
|
||||
}
|
||||
}
|
||||
|
||||
runDetached(_, callback) {
|
||||
new Promise(r => r(callback(this.item))).then(noop, noop);
|
||||
}
|
||||
|
||||
async export() {
|
||||
return null;
|
||||
}
|
||||
|
||||
get level() {
|
||||
return LogLevel;
|
||||
}
|
||||
}
|
||||
|
||||
export class NullLogItem {
|
||||
constructor(logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
wrap(_, callback) {
|
||||
return callback(this);
|
||||
}
|
||||
log() {}
|
||||
set() {}
|
||||
|
||||
runDetached(_, callback) {
|
||||
new Promise(r => r(callback(this))).then(noop, noop);
|
||||
}
|
||||
|
||||
wrapDetached(_, callback) {
|
||||
return this.refDetached(null, callback);
|
||||
}
|
||||
|
||||
run(callback) {
|
||||
return callback(this);
|
||||
}
|
||||
|
||||
refDetached() {}
|
||||
|
||||
ensureRefId() {}
|
||||
|
||||
get level() {
|
||||
return LogLevel;
|
||||
}
|
||||
|
||||
get duration() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
catch(err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
child() {
|
||||
return this;
|
||||
}
|
||||
|
||||
finish() {}
|
||||
}
|
||||
|
||||
export const Instance = new NullLogger();
|
106
src/logging/NullLogger.ts
Normal file
106
src/logging/NullLogger.ts
Normal file
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
import {LogLevel} from "./LogFilter";
|
||||
import type {ILogger, ILogExport, ILogItem, LabelOrValues, LogCallback, LogItemValues} from "./types";
|
||||
|
||||
function noop (): void {}
|
||||
|
||||
export class NullLogger implements ILogger {
|
||||
public readonly item: ILogItem = new NullLogItem(this);
|
||||
|
||||
log(): void {}
|
||||
|
||||
run<T>(_, callback: LogCallback<T>): T {
|
||||
return callback(this.item);
|
||||
}
|
||||
|
||||
wrapOrRun<T>(item: ILogItem | undefined, _, callback: LogCallback<T>): T {
|
||||
if (item) {
|
||||
return item.wrap(_, callback);
|
||||
} else {
|
||||
return this.run(_, callback);
|
||||
}
|
||||
}
|
||||
|
||||
runDetached(_, callback): ILogItem {
|
||||
new Promise(r => r(callback(this.item))).then(noop, noop);
|
||||
return this.item;
|
||||
}
|
||||
|
||||
async export(): Promise<ILogExport | undefined> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
get level(): typeof LogLevel {
|
||||
return LogLevel;
|
||||
}
|
||||
}
|
||||
|
||||
export class NullLogItem implements ILogItem {
|
||||
public readonly logger: NullLogger;
|
||||
public readonly logLevel: LogLevel;
|
||||
public children?: Array<ILogItem>;
|
||||
public values: LogItemValues;
|
||||
public error?: Error;
|
||||
|
||||
constructor(logger: NullLogger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
wrap<T>(_: LabelOrValues, callback: LogCallback<T>): T {
|
||||
return callback(this);
|
||||
}
|
||||
|
||||
log(): void {}
|
||||
set(): void {}
|
||||
|
||||
runDetached(_: LabelOrValues, callback: LogCallback<unknown>): ILogItem {
|
||||
new Promise(r => r(callback(this))).then(noop, noop);
|
||||
return this;
|
||||
}
|
||||
|
||||
wrapDetached(_: LabelOrValues, _callback: LogCallback<unknown>): void {
|
||||
return this.refDetached();
|
||||
}
|
||||
|
||||
refDetached(): void {}
|
||||
|
||||
ensureRefId(): void {}
|
||||
|
||||
get level(): typeof LogLevel {
|
||||
return LogLevel;
|
||||
}
|
||||
|
||||
get duration(): 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
catch(err: Error): Error {
|
||||
return err;
|
||||
}
|
||||
|
||||
child(): ILogItem {
|
||||
return this;
|
||||
}
|
||||
|
||||
finish(): void {}
|
||||
|
||||
serialize(): undefined {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export const Instance = new NullLogger();
|
82
src/logging/types.ts
Normal file
82
src/logging/types.ts
Normal file
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {LogLevel, LogFilter} from "./LogFilter";
|
||||
import type {BaseLogger} from "./BaseLogger";
|
||||
import type {BlobHandle} from "../platform/web/dom/BlobHandle.js";
|
||||
|
||||
export interface ISerializedItem {
|
||||
s: number;
|
||||
d?: number;
|
||||
v: LogItemValues;
|
||||
l: LogLevel;
|
||||
e?: {
|
||||
stack?: string;
|
||||
name: string;
|
||||
message: string;
|
||||
};
|
||||
f?: boolean;
|
||||
c?: Array<ISerializedItem>;
|
||||
};
|
||||
|
||||
export interface ILogItem {
|
||||
logLevel: LogLevel;
|
||||
error?: Error;
|
||||
readonly logger: ILogger;
|
||||
readonly level: typeof LogLevel;
|
||||
readonly end?: number;
|
||||
readonly start?: number;
|
||||
readonly values: LogItemValues;
|
||||
wrap<T>(labelOrValues: LabelOrValues, callback: LogCallback<T>, logLevel?: LogLevel, filterCreator?: FilterCreator): T;
|
||||
log(labelOrValues: LabelOrValues, logLevel?: LogLevel): void;
|
||||
set(key: string | object, value: unknown): void;
|
||||
runDetached(labelOrValues: LabelOrValues, callback: LogCallback<unknown>, logLevel?: LogLevel, filterCreator?: FilterCreator): ILogItem;
|
||||
wrapDetached(labelOrValues: LabelOrValues, callback: LogCallback<unknown>, logLevel?: LogLevel, filterCreator?: FilterCreator): void;
|
||||
refDetached(logItem: ILogItem, logLevel?: LogLevel): void;
|
||||
ensureRefId(): void;
|
||||
catch(err: Error): Error;
|
||||
serialize(filter: LogFilter, parentStartTime: number | undefined, forced: boolean): ISerializedItem | undefined;
|
||||
}
|
||||
|
||||
export interface ILogger {
|
||||
log(labelOrValues: LabelOrValues, logLevel?: LogLevel): void;
|
||||
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;
|
||||
run<T>(labelOrValues: LabelOrValues, callback: LogCallback<T>, logLevel?: LogLevel, filterCreator?: FilterCreator): T;
|
||||
export(): Promise<ILogExport | undefined>;
|
||||
get level(): typeof LogLevel;
|
||||
}
|
||||
|
||||
export interface ILogExport {
|
||||
get count(): number;
|
||||
removeFromStore(): Promise<void>;
|
||||
asBlob(): BlobHandle;
|
||||
}
|
||||
|
||||
export type LogItemValues = {
|
||||
l?: string;
|
||||
t?: string;
|
||||
id?: unknown;
|
||||
status?: string | number;
|
||||
refId?: number;
|
||||
ref?: number;
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export type LabelOrValues = string | LogItemValues;
|
||||
export type FilterCreator = ((filter: LogFilter, item: ILogItem) => LogFilter);
|
||||
export type LogCallback<T> = (item: ILogItem) => T;
|
|
@ -1,16 +0,0 @@
|
|||
// 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)
|
||||
// if you know you always have a log item, better to use the methods on the log item than these utility functions.
|
||||
|
||||
import {Instance as NullLoggerInstance} from "./NullLogger.js";
|
||||
|
||||
export function wrapOrRunNullLogger(logItem, labelOrValues, callback, logLevel = null, filterCreator = null) {
|
||||
if (logItem) {
|
||||
return logItem.wrap(logItem, labelOrValues, callback, logLevel, filterCreator);
|
||||
} else {
|
||||
return NullLoggerInstance.run(null, callback);
|
||||
}
|
||||
}
|
||||
|
||||
export function ensureLogItem(logItem) {
|
||||
return logItem || NullLoggerInstance.item;
|
||||
}
|
18
src/logging/utils.ts
Normal file
18
src/logging/utils.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
// 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)
|
||||
// if you know you always have a log item, better to use the methods on the log item than these utility functions.
|
||||
|
||||
import {Instance as NullLoggerInstance} from "./NullLogger";
|
||||
import type {FilterCreator, ILogItem, LabelOrValues, LogCallback} from "./types";
|
||||
import {LogLevel} from "./LogFilter";
|
||||
|
||||
export function wrapOrRunNullLogger<T>(logItem: ILogItem | undefined, labelOrValues: LabelOrValues, callback: LogCallback<T>, logLevel?: LogLevel, filterCreator?: FilterCreator): T | Promise<T> {
|
||||
if (logItem) {
|
||||
return logItem.wrap(labelOrValues, callback, logLevel, filterCreator);
|
||||
} else {
|
||||
return NullLoggerInstance.run(null, callback);
|
||||
}
|
||||
}
|
||||
|
||||
export function ensureLogItem(logItem: ILogItem): ILogItem {
|
||||
return logItem || NullLoggerInstance.item;
|
||||
}
|
|
@ -26,7 +26,7 @@ import type {OlmWorker} from "../OlmWorker";
|
|||
import type {Transaction} from "../../storage/idb/Transaction";
|
||||
import type {TimelineEvent} from "../../storage/types";
|
||||
import type {DecryptionResult} from "../DecryptionResult";
|
||||
import type {LogItem} from "../../../logging/LogItem";
|
||||
import type {ILogItem} from "../../../logging/types";
|
||||
|
||||
export class Decryption {
|
||||
private keyLoader: KeyLoader;
|
||||
|
@ -136,7 +136,7 @@ export class Decryption {
|
|||
* Extracts room keys from decrypted device messages.
|
||||
* The key won't be persisted yet, you need to call RoomKey.write for that.
|
||||
*/
|
||||
roomKeysFromDeviceMessages(decryptionResults: DecryptionResult[], log: LogItem): IncomingRoomKey[] {
|
||||
roomKeysFromDeviceMessages(decryptionResults: DecryptionResult[], log: ILogItem): IncomingRoomKey[] {
|
||||
const keys: IncomingRoomKey[] = [];
|
||||
for (const dr of decryptionResults) {
|
||||
if (dr.event?.type !== "m.room_key" || dr.event.content?.algorithm !== MEGOLM_ALGORITHM) {
|
||||
|
|
|
@ -27,7 +27,7 @@ import {Heroes} from "./members/Heroes.js";
|
|||
import {EventEntry} from "./timeline/entries/EventEntry.js";
|
||||
import {ObservedEventMap} from "./ObservedEventMap.js";
|
||||
import {DecryptionSource} from "../e2ee/common.js";
|
||||
import {ensureLogItem} from "../../logging/utils.js";
|
||||
import {ensureLogItem} from "../../logging/utils";
|
||||
import {PowerLevels} from "./PowerLevels.js";
|
||||
import {RetainedObservableValue} from "../../observable/ObservableValue";
|
||||
|
||||
|
|
|
@ -244,7 +244,7 @@ export class Invite extends EventEmitter {
|
|||
}
|
||||
}
|
||||
|
||||
import {NullLogItem} from "../../logging/NullLogger.js";
|
||||
import {NullLogItem} from "../../logging/NullLogger";
|
||||
import {Clock as MockClock} from "../../mocks/Clock.js";
|
||||
import {default as roomInviteFixture} from "../../fixtures/matrix/invites/room.js";
|
||||
import {default as dmInviteFixture} from "../../fixtures/matrix/invites/dm.js";
|
||||
|
|
|
@ -353,7 +353,7 @@ export class SendQueue {
|
|||
import {HomeServer as MockHomeServer} from "../../../mocks/HomeServer.js";
|
||||
import {createMockStorage} from "../../../mocks/Storage";
|
||||
import {ListObserver} from "../../../mocks/ListObserver.js";
|
||||
import {NullLogger, NullLogItem} from "../../../logging/NullLogger.js";
|
||||
import {NullLogger, NullLogItem} from "../../../logging/NullLogger";
|
||||
import {createEvent, withTextBody, withTxnId} from "../../../mocks/event.js";
|
||||
import {poll} from "../../../mocks/poll.js";
|
||||
import {createAnnotation} from "../timeline/relations.js";
|
||||
|
|
|
@ -346,7 +346,7 @@ import {Clock as MockClock} from "../../../mocks/Clock.js";
|
|||
import {createMockStorage} from "../../../mocks/Storage";
|
||||
import {ListObserver} from "../../../mocks/ListObserver.js";
|
||||
import {createEvent, withTextBody, withContent, withSender} from "../../../mocks/event.js";
|
||||
import {NullLogItem} from "../../../logging/NullLogger.js";
|
||||
import {NullLogItem} from "../../../logging/NullLogger";
|
||||
import {EventEntry} from "./entries/EventEntry.js";
|
||||
import {User} from "../../User.js";
|
||||
import {PendingEvent} from "../sending/PendingEvent.js";
|
||||
|
|
|
@ -205,7 +205,7 @@ import {FragmentIdComparer} from "../FragmentIdComparer.js";
|
|||
import {RelationWriter} from "./RelationWriter.js";
|
||||
import {createMockStorage} from "../../../../mocks/Storage";
|
||||
import {FragmentBoundaryEntry} from "../entries/FragmentBoundaryEntry.js";
|
||||
import {NullLogItem} from "../../../../logging/NullLogger.js";
|
||||
import {NullLogItem} from "../../../../logging/NullLogger";
|
||||
import {TimelineMock, eventIds, eventId} from "../../../../mocks/TimelineMock.ts";
|
||||
import {SyncWriter} from "./SyncWriter.js";
|
||||
import {MemberWriter} from "./MemberWriter.js";
|
||||
|
|
|
@ -257,7 +257,7 @@ import {createMockStorage} from "../../../../mocks/Storage";
|
|||
import {createEvent, withTextBody, withRedacts, withContent} from "../../../../mocks/event.js";
|
||||
import {createAnnotation} from "../relations.js";
|
||||
import {FragmentIdComparer} from "../FragmentIdComparer.js";
|
||||
import {NullLogItem} from "../../../../logging/NullLogger.js";
|
||||
import {NullLogItem} from "../../../../logging/NullLogger";
|
||||
|
||||
export function tests() {
|
||||
const fragmentIdComparer = new FragmentIdComparer([]);
|
||||
|
|
|
@ -258,7 +258,7 @@ export class SyncWriter {
|
|||
|
||||
import {createMockStorage} from "../../../../mocks/Storage";
|
||||
import {createEvent, withTextBody} from "../../../../mocks/event.js";
|
||||
import {Instance as nullLogger} from "../../../../logging/NullLogger.js";
|
||||
import {Instance as nullLogger} from "../../../../logging/NullLogger";
|
||||
export function tests() {
|
||||
const roomId = "!abc:hs.tld";
|
||||
return {
|
||||
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
|||
|
||||
import {iterateCursor, DONE, NOT_DONE, reqAsPromise} from "./utils";
|
||||
import {StorageError} from "../common";
|
||||
import {LogItem} from "../../../logging/LogItem.js";
|
||||
import {ILogItem} from "../../../logging/types";
|
||||
import {IDBKey} from "./Transaction";
|
||||
|
||||
// this is the part of the Transaction class API that is used here and in the Store subclass,
|
||||
|
@ -25,7 +25,7 @@ export interface ITransaction {
|
|||
idbFactory: IDBFactory;
|
||||
IDBKeyRange: typeof IDBKeyRange;
|
||||
databaseName: string;
|
||||
addWriteError(error: StorageError, refItem: LogItem | undefined, operationName: string, keys: IDBKey[] | undefined);
|
||||
addWriteError(error: StorageError, refItem: ILogItem | undefined, operationName: string, keys: IDBKey[] | undefined);
|
||||
}
|
||||
|
||||
type Reducer<A,B> = (acc: B, val: A) => B
|
||||
|
@ -277,7 +277,7 @@ export function tests() {
|
|||
|
||||
class MockTransaction extends MockIDBImpl {
|
||||
get databaseName(): string { return "mockdb"; }
|
||||
addWriteError(error: StorageError, refItem: LogItem | undefined, operationName: string, keys: IDBKey[] | undefined) {}
|
||||
addWriteError(error: StorageError, refItem: ILogItem | undefined, operationName: string, keys: IDBKey[] | undefined) {}
|
||||
}
|
||||
|
||||
interface TestEntry {
|
||||
|
|
|
@ -18,7 +18,7 @@ import {IDOMStorage} from "./types";
|
|||
import {Transaction} from "./Transaction";
|
||||
import { STORE_NAMES, StoreNames, StorageError } from "../common";
|
||||
import { reqAsPromise } from "./utils";
|
||||
import { BaseLogger } from "../../../logging/BaseLogger.js";
|
||||
import { ILogger } from "../../../logging/types";
|
||||
|
||||
const WEBKITEARLYCLOSETXNBUG_BOGUS_KEY = "782rh281re38-boguskey";
|
||||
|
||||
|
@ -26,13 +26,13 @@ export class Storage {
|
|||
private _db: IDBDatabase;
|
||||
private _hasWebkitEarlyCloseTxnBug: boolean;
|
||||
|
||||
readonly logger: BaseLogger;
|
||||
readonly logger: ILogger;
|
||||
readonly idbFactory: IDBFactory
|
||||
readonly IDBKeyRange: typeof IDBKeyRange;
|
||||
readonly storeNames: typeof StoreNames;
|
||||
readonly localStorage: IDOMStorage;
|
||||
|
||||
constructor(idbDatabase: IDBDatabase, idbFactory: IDBFactory, _IDBKeyRange: typeof IDBKeyRange, hasWebkitEarlyCloseTxnBug: boolean, localStorage: IDOMStorage, logger: BaseLogger) {
|
||||
constructor(idbDatabase: IDBDatabase, idbFactory: IDBFactory, _IDBKeyRange: typeof IDBKeyRange, hasWebkitEarlyCloseTxnBug: boolean, localStorage: IDOMStorage, logger: ILogger) {
|
||||
this._db = idbDatabase;
|
||||
this.idbFactory = idbFactory;
|
||||
this.IDBKeyRange = _IDBKeyRange;
|
||||
|
|
|
@ -20,11 +20,10 @@ import { openDatabase, reqAsPromise } from "./utils";
|
|||
import { exportSession, importSession, Export } from "./export";
|
||||
import { schema } from "./schema";
|
||||
import { detectWebkitEarlyCloseTxnBug } from "./quirks";
|
||||
import { BaseLogger } from "../../../logging/BaseLogger.js";
|
||||
import { LogItem } from "../../../logging/LogItem.js";
|
||||
import { ILogItem } from "../../../logging/types";
|
||||
|
||||
const sessionName = (sessionId: string) => `hydrogen_session_${sessionId}`;
|
||||
const openDatabaseWithSessionId = function(sessionId: string, idbFactory: IDBFactory, localStorage: IDOMStorage, log: LogItem) {
|
||||
const openDatabaseWithSessionId = function(sessionId: string, idbFactory: IDBFactory, localStorage: IDOMStorage, log: ILogItem) {
|
||||
const create = (db, txn, oldVersion, version) => createStores(db, txn, oldVersion, version, localStorage, log);
|
||||
return openDatabase(sessionName(sessionId), create, schema.length, idbFactory);
|
||||
}
|
||||
|
@ -63,7 +62,7 @@ export class StorageFactory {
|
|||
this._localStorage = localStorage;
|
||||
}
|
||||
|
||||
async create(sessionId: string, log: LogItem): Promise<Storage> {
|
||||
async create(sessionId: string, log: ILogItem): Promise<Storage> {
|
||||
await this._serviceWorkerHandler?.preventConcurrentSessionAccess(sessionId);
|
||||
requestPersistedStorage().then(persisted => {
|
||||
// Firefox lies here though, and returns true even if the user denied the request
|
||||
|
@ -83,23 +82,25 @@ export class StorageFactory {
|
|||
return reqAsPromise(req);
|
||||
}
|
||||
|
||||
async export(sessionId: string, log: LogItem): Promise<Export> {
|
||||
async export(sessionId: string, log: ILogItem): Promise<Export> {
|
||||
const db = await openDatabaseWithSessionId(sessionId, this._idbFactory, this._localStorage, log);
|
||||
return await exportSession(db);
|
||||
}
|
||||
|
||||
async import(sessionId: string, data: Export, log: LogItem): Promise<void> {
|
||||
async import(sessionId: string, data: Export, log: ILogItem): Promise<void> {
|
||||
const db = await openDatabaseWithSessionId(sessionId, this._idbFactory, this._localStorage, log);
|
||||
return await importSession(db, data);
|
||||
}
|
||||
}
|
||||
|
||||
async function createStores(db: IDBDatabase, txn: IDBTransaction, oldVersion: number | null, version: number, localStorage: IDOMStorage, log: LogItem): Promise<void> {
|
||||
async function createStores(db: IDBDatabase, txn: IDBTransaction, oldVersion: number | null, version: number, localStorage: IDOMStorage, log: ILogItem): Promise<void> {
|
||||
const startIdx = oldVersion || 0;
|
||||
return log.wrap({l: "storage migration", oldVersion, version}, async log => {
|
||||
for(let i = startIdx; i < version; ++i) {
|
||||
return log.wrap(
|
||||
{ l: "storage migration", oldVersion, version },
|
||||
async (log) => {
|
||||
for (let i = startIdx; i < version; ++i) {
|
||||
const migrationFunc = schema[i];
|
||||
await log.wrap(`v${i + 1}`, log => migrationFunc(db, txn, localStorage, log));
|
||||
await log.wrap(`v${i + 1}`, (log) => migrationFunc(db, txn, localStorage, log));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ import {QueryTarget, IDBQuery, ITransaction} from "./QueryTarget";
|
|||
import {IDBRequestError, IDBRequestAttemptError} from "./error";
|
||||
import {reqAsPromise} from "./utils";
|
||||
import {Transaction, IDBKey} from "./Transaction";
|
||||
import {LogItem} from "../../../logging/LogItem.js";
|
||||
import {ILogItem} from "../../../logging/types";
|
||||
|
||||
const LOG_REQUESTS = false;
|
||||
|
||||
|
@ -145,7 +145,7 @@ export class Store<T> extends QueryTarget<T> {
|
|||
return new QueryTarget<T>(new QueryTargetWrapper<T>(this._idbStore.index(indexName)), this._transaction);
|
||||
}
|
||||
|
||||
put(value: T, log?: LogItem): void {
|
||||
put(value: T, log?: ILogItem): void {
|
||||
// If this request fails, the error will bubble up to the transaction and abort it,
|
||||
// which is the behaviour we want. Therefore, it is ok to not create a promise for this
|
||||
// request and await it.
|
||||
|
@ -160,13 +160,13 @@ export class Store<T> extends QueryTarget<T> {
|
|||
this._prepareErrorLog(request, log, "put", undefined, value);
|
||||
}
|
||||
|
||||
add(value: T, log?: LogItem): void {
|
||||
add(value: T, log?: ILogItem): void {
|
||||
// ok to not monitor result of request, see comment in `put`.
|
||||
const request = this._idbStore.add(value);
|
||||
this._prepareErrorLog(request, log, "add", undefined, value);
|
||||
}
|
||||
|
||||
async tryAdd(value: T, log: LogItem): Promise<boolean> {
|
||||
async tryAdd(value: T, log: ILogItem): Promise<boolean> {
|
||||
try {
|
||||
await reqAsPromise(this._idbStore.add(value));
|
||||
return true;
|
||||
|
@ -181,13 +181,13 @@ export class Store<T> extends QueryTarget<T> {
|
|||
}
|
||||
}
|
||||
|
||||
delete(keyOrKeyRange: IDBValidKey | IDBKeyRange, log?: LogItem): void {
|
||||
delete(keyOrKeyRange: IDBValidKey | IDBKeyRange, log?: ILogItem): void {
|
||||
// ok to not monitor result of request, see comment in `put`.
|
||||
const request = this._idbStore.delete(keyOrKeyRange);
|
||||
this._prepareErrorLog(request, log, "delete", keyOrKeyRange, undefined);
|
||||
}
|
||||
|
||||
private _prepareErrorLog(request: IDBRequest, log: LogItem | undefined, operationName: string, key: IDBKey | undefined, value: T | undefined) {
|
||||
private _prepareErrorLog(request: IDBRequest, log: ILogItem | undefined, operationName: string, key: IDBKey | undefined, value: T | undefined) {
|
||||
if (log) {
|
||||
log.ensureRefId();
|
||||
}
|
||||
|
|
|
@ -36,15 +36,14 @@ import {OutboundGroupSessionStore} from "./stores/OutboundGroupSessionStore";
|
|||
import {GroupSessionDecryptionStore} from "./stores/GroupSessionDecryptionStore";
|
||||
import {OperationStore} from "./stores/OperationStore";
|
||||
import {AccountDataStore} from "./stores/AccountDataStore";
|
||||
import {LogItem} from "../../../logging/LogItem.js";
|
||||
import {BaseLogger} from "../../../logging/BaseLogger.js";
|
||||
import type {ILogger, ILogItem} from "../../../logging/types";
|
||||
|
||||
export type IDBKey = IDBValidKey | IDBKeyRange;
|
||||
|
||||
class WriteErrorInfo {
|
||||
constructor(
|
||||
public readonly error: StorageError,
|
||||
public readonly refItem: LogItem | undefined,
|
||||
public readonly refItem: ILogItem | undefined,
|
||||
public readonly operationName: string,
|
||||
public readonly keys: IDBKey[] | undefined,
|
||||
) {}
|
||||
|
@ -77,7 +76,7 @@ export class Transaction {
|
|||
return this._storage.databaseName;
|
||||
}
|
||||
|
||||
get logger(): BaseLogger {
|
||||
get logger(): ILogger {
|
||||
return this._storage.logger;
|
||||
}
|
||||
|
||||
|
@ -169,7 +168,7 @@ export class Transaction {
|
|||
return this._store(StoreNames.accountData, idbStore => new AccountDataStore(idbStore));
|
||||
}
|
||||
|
||||
async complete(log?: LogItem): Promise<void> {
|
||||
async complete(log?: ILogItem): Promise<void> {
|
||||
try {
|
||||
await txnAsPromise(this._txn);
|
||||
} catch (err) {
|
||||
|
@ -190,7 +189,7 @@ export class Transaction {
|
|||
return error;
|
||||
}
|
||||
|
||||
abort(log?: LogItem): void {
|
||||
abort(log?: ILogItem): void {
|
||||
// TODO: should we wrap the exception in a StorageError?
|
||||
try {
|
||||
this._txn.abort();
|
||||
|
@ -202,14 +201,14 @@ export class Transaction {
|
|||
}
|
||||
}
|
||||
|
||||
addWriteError(error: StorageError, refItem: LogItem | undefined, operationName: string, keys: IDBKey[] | undefined) {
|
||||
addWriteError(error: StorageError, refItem: ILogItem | undefined, operationName: string, keys: IDBKey[] | undefined) {
|
||||
// don't log subsequent `AbortError`s
|
||||
if (error.errcode !== "AbortError" || this._writeErrors.length === 0) {
|
||||
this._writeErrors.push(new WriteErrorInfo(error, refItem, operationName, keys));
|
||||
}
|
||||
}
|
||||
|
||||
private _logWriteErrors(parentItem: LogItem | undefined) {
|
||||
private _logWriteErrors(parentItem: ILogItem | undefined) {
|
||||
const callback = errorGroupItem => {
|
||||
// we don't have context when there is no parentItem, so at least log stores
|
||||
if (!parentItem) {
|
||||
|
|
|
@ -11,10 +11,10 @@ import {SessionStore} from "./stores/SessionStore";
|
|||
import {Store} from "./Store";
|
||||
import {encodeScopeTypeKey} from "./stores/OperationStore";
|
||||
import {MAX_UNICODE} from "./stores/common";
|
||||
import {LogItem} from "../../../logging/LogItem.js";
|
||||
import {ILogItem} from "../../../logging/types";
|
||||
|
||||
|
||||
export type MigrationFunc = (db: IDBDatabase, txn: IDBTransaction, localStorage: IDOMStorage, log: LogItem) => Promise<void> | void;
|
||||
export type MigrationFunc = (db: IDBDatabase, txn: IDBTransaction, localStorage: IDOMStorage, log: ILogItem) => Promise<void> | void;
|
||||
// FUNCTIONS SHOULD ONLY BE APPENDED!!
|
||||
// the index in the array is the database version
|
||||
export const schema: MigrationFunc[] = [
|
||||
|
@ -166,7 +166,7 @@ function createTimelineRelationsStore(db: IDBDatabase) : void {
|
|||
}
|
||||
|
||||
//v11 doesn't change the schema, but ensures all userIdentities have all the roomIds they should (see #470)
|
||||
async function fixMissingRoomsInUserIdentities(db: IDBDatabase, txn: IDBTransaction, localStorage: IDOMStorage, log: LogItem) {
|
||||
async function fixMissingRoomsInUserIdentities(db: IDBDatabase, txn: IDBTransaction, localStorage: IDOMStorage, log: ILogItem) {
|
||||
const roomSummaryStore = txn.objectStore("roomSummary");
|
||||
const trackedRoomIds: string[] = [];
|
||||
await iterateCursor<SummaryData>(roomSummaryStore.openCursor(), roomSummary => {
|
||||
|
@ -220,7 +220,7 @@ async function changeSSSSKeyPrefix(db: IDBDatabase, txn: IDBTransaction) {
|
|||
}
|
||||
}
|
||||
// v13
|
||||
async function backupAndRestoreE2EEAccountToLocalStorage(db: IDBDatabase, txn: IDBTransaction, localStorage: IDOMStorage, log: LogItem) {
|
||||
async function backupAndRestoreE2EEAccountToLocalStorage(db: IDBDatabase, txn: IDBTransaction, localStorage: IDOMStorage, log: ILogItem) {
|
||||
const session = txn.objectStore("session");
|
||||
// the Store object gets passed in several things through the Transaction class (a wrapper around IDBTransaction),
|
||||
// the only thing we should need here is the databaseName though, so we mock it out.
|
||||
|
|
|
@ -16,8 +16,8 @@ limitations under the License.
|
|||
import {Store} from "../Store";
|
||||
import {IDOMStorage} from "../types";
|
||||
import {SESSION_E2EE_KEY_PREFIX} from "../../../e2ee/common.js";
|
||||
import {LogItem} from "../../../../logging/LogItem.js";
|
||||
import {parse, stringify} from "../../../../utils/typedJSON";
|
||||
import type {ILogItem} from "../../../../logging/types";
|
||||
|
||||
export interface SessionEntry {
|
||||
key: string;
|
||||
|
@ -64,7 +64,7 @@ export class SessionStore {
|
|||
});
|
||||
}
|
||||
|
||||
async tryRestoreE2EEIdentityFromLocalStorage(log: LogItem): Promise<boolean> {
|
||||
async tryRestoreE2EEIdentityFromLocalStorage(log: ILogItem): Promise<boolean> {
|
||||
let success = false;
|
||||
const lsPrefix = this._localStorageKeyPrefix;
|
||||
const prefix = lsPrefix + SESSION_E2EE_KEY_PREFIX;
|
||||
|
|
|
@ -20,7 +20,7 @@ import { encodeUint32, decodeUint32 } from "../utils";
|
|||
import {KeyLimits} from "../../common";
|
||||
import {Store} from "../Store";
|
||||
import {TimelineEvent, StateEvent} from "../../types";
|
||||
import {LogItem} from "../../../../logging/LogItem.js";
|
||||
import {ILogItem} from "../../../../logging/types";
|
||||
|
||||
interface Annotation {
|
||||
count: number;
|
||||
|
@ -286,7 +286,7 @@ export class TimelineEventStore {
|
|||
*
|
||||
* Returns if the event was not yet known and the entry was written.
|
||||
*/
|
||||
tryInsert(entry: TimelineEventEntry, log: LogItem): Promise<boolean> {
|
||||
tryInsert(entry: TimelineEventEntry, log: ILogItem): Promise<boolean> {
|
||||
(entry as TimelineEventStorageEntry).key = encodeKey(entry.roomId, entry.fragmentId, entry.eventIndex);
|
||||
(entry as TimelineEventStorageEntry).eventIdKey = encodeEventIdKey(entry.roomId, entry.event.event_id);
|
||||
return this._timelineStore.tryAdd(entry as TimelineEventStorageEntry, log);
|
||||
|
@ -320,7 +320,7 @@ export class TimelineEventStore {
|
|||
import {createMockStorage} from "../../../../mocks/Storage";
|
||||
import {createEvent, withTextBody} from "../../../../mocks/event.js";
|
||||
import {createEventEntry} from "../../../room/timeline/persistence/common.js";
|
||||
import {Instance as logItem} from "../../../../logging/NullLogger.js";
|
||||
import {Instance as nullLogger} from "../../../../logging/NullLogger";
|
||||
|
||||
export function tests() {
|
||||
|
||||
|
@ -368,7 +368,7 @@ export function tests() {
|
|||
let eventKey = EventKey.defaultFragmentKey(109);
|
||||
for (const insertedId of insertedIds) {
|
||||
const entry = createEventEntry(eventKey.nextKey(), roomId, createEventWithId(insertedId));
|
||||
assert(await txn.timelineEvents.tryInsert(entry, logItem));
|
||||
assert(await txn.timelineEvents.tryInsert(entry, nullLogger.item));
|
||||
eventKey = eventKey.nextKey();
|
||||
}
|
||||
const eventKeyMap = await txn.timelineEvents.getEventKeysForIds(roomId, checkedIds);
|
||||
|
|
|
@ -18,7 +18,7 @@ import {FDBFactory, FDBKeyRange} from "../../lib/fake-indexeddb/index.js";
|
|||
import {StorageFactory} from "../matrix/storage/idb/StorageFactory";
|
||||
import {IDOMStorage} from "../matrix/storage/idb/types";
|
||||
import {Storage} from "../matrix/storage/idb/Storage";
|
||||
import {Instance as nullLogger} from "../logging/NullLogger.js";
|
||||
import {Instance as nullLogger} from "../logging/NullLogger";
|
||||
import {openDatabase, CreateObjectStore} from "../matrix/storage/idb/utils";
|
||||
|
||||
export function createMockStorage(): Promise<Storage> {
|
||||
|
|
|
@ -21,8 +21,8 @@ import {SessionInfoStorage} from "../../matrix/sessioninfo/localstorage/SessionI
|
|||
import {SettingsStorage} from "./dom/SettingsStorage.js";
|
||||
import {Encoding} from "./utils/Encoding.js";
|
||||
import {OlmWorker} from "../../matrix/e2ee/OlmWorker.js";
|
||||
import {IDBLogger} from "../../logging/IDBLogger.js";
|
||||
import {ConsoleLogger} from "../../logging/ConsoleLogger.js";
|
||||
import {IDBLogger} from "../../logging/IDBLogger";
|
||||
import {ConsoleLogger} from "../../logging/ConsoleLogger";
|
||||
import {RootView} from "./ui/RootView.js";
|
||||
import {Clock} from "./dom/Clock.js";
|
||||
import {ServiceWorkerHandler} from "./dom/ServiceWorkerHandler.js";
|
||||
|
|
Loading…
Reference in a new issue