فهرست منبع

Expose the `replacer` function instead of serialising in MemoryLogger

This makes it easier to provide context around a list of log records.

Slightly changes the LogRecord type
Lennart Grahl 6 سال پیش
والد
کامیت
77cb183188
6فایلهای تغییر یافته به همراه85 افزوده شده و 79 حذف شده
  1. 7 4
      src/controllers/troubleshooting.ts
  2. 58 57
      src/helpers/logger.ts
  3. 1 1
      src/threema.d.ts
  4. 3 1
      tests/bootstrap.ts
  5. 4 4
      tests/service/log.js
  6. 12 12
      tests/ts/logger_helpers.ts

+ 7 - 4
src/controllers/troubleshooting.ts

@@ -20,6 +20,7 @@ import {Logger} from 'ts-log';
 import {arrayToBuffer, hasFeature, sleep} from '../helpers';
 import * as clipboard from '../helpers/clipboard';
 
+import {MemoryLogger} from '../helpers/logger';
 import {BrowserService} from '../services/browser';
 import {LogService} from '../services/log';
 import {WebClientService} from '../services/webclient';
@@ -92,8 +93,8 @@ export class TroubleshootingController extends DialogController {
         this.isSending = true;
         this.sendingFailed = false;
 
-        // Serialise the log
-        const log = new TextEncoder().encode(this.logService.memory.serialize());
+        // Get the log
+        const log = new TextEncoder().encode(this.getLog());
 
         // Error handler
         const fail = () => {
@@ -192,7 +193,9 @@ export class TroubleshootingController extends DialogController {
      * Serialise the memory log.
      */
     private getLog(): string {
-        // TODO: Add metadata
-        return this.logService.memory.serialize();
+        const records = this.logService.memory.getRecords();
+
+        // TODO: Add metadata to report
+        return JSON.stringify(records, MemoryLogger.replacer);
     }
 }

+ 58 - 57
src/helpers/logger.ts

@@ -209,28 +209,18 @@ export class MemoryLogger implements Logger {
         }
 
         // Add newest record
-        this.records.push([new Date(), type, message, ...args]);
+        this.records.push([Date.now(), type, message, ...args]);
     }
 
     /**
-     * Serialise all log records to JSON.
+     * Get a copy of all currently logged records. Strips any style formatting
+     * of the log tags.
      *
-     * While serialising, a recursive filter will be applied:
-     *
-     * - the types `null`, `string`, `number` and `boolean` will be returned
-     *   unmodified,
-     * - an object implementing the `Confidential` interface will be returned
-     *   sanitised,
-     * - an `Error` instance will be left as is,
-     * - the binary types `Uint8Array` and `Blob` will only return meta
-     *   information about the content, and
-     * - everything else will return the value's type instead of the value
-     *   itself.
-     *
-     * @param space Amount of white spaces used for nested block indentation.
+     * Important: Objects implementing the `Confidential` interface will be
+     *            returned as is.
      */
-    public serialize(space: number = 2): string {
-        const records = this.records.map(([date, type, message, ...args]: LogRecord) => {
+    public getRecords(): LogRecord[] {
+        return this.records.map(([date, type, message, ...args]: LogRecord) => {
             // Strip message formatting
             if (message !== null && message !== undefined && message.constructor === String) {
                 let stripped = false;
@@ -249,54 +239,65 @@ export class MemoryLogger implements Logger {
                     args.shift();
                 }
             }
-
-            // Convert date to a timestamp with millisecond accuracy
-            const timestampMs = date.getTime();
-            return [timestampMs, type, message, ...args];
+            return [date, type, message, ...args];
         });
+    }
 
-        // Serialise to JSON
-        return JSON.stringify(records, (_, value) => {
-            // Handle `null` and `undefined` early
-            if (value === null || value === undefined) {
-                return value;
-            }
+    /**
+     * Replacer function for serialising log records to JSON.
+     *
+     * A recursive filter will be applied:
+     *
+     * - the types `null`, `string`, `number` and `boolean` will be returned
+     *   unmodified,
+     * - an object implementing the `Confidential` interface will be returned
+     *   sanitised,
+     * - an `Error` instance will be left as is,
+     * - the binary types `Uint8Array` and `Blob` will only return meta
+     *   information about the content, and
+     * - everything else will return the value's type instead of the value
+     *   itself.
+     */
+    public static replacer(key: string, value: any): any {
+        // Handle `null` and `undefined` early
+        if (value === null || value === undefined) {
+            return value;
+        }
 
-            // Apply filter to confidential data
-            if (value instanceof BaseConfidential) {
-                return value.censored();
-            }
+        // Apply filter to confidential data
+        if (value instanceof BaseConfidential) {
+            return value.censored();
+        }
 
-            // Allowed (standard) types
-            for (const allowedType of ALLOWED_TYPES) {
-                if (value.constructor === allowedType) {
-                    return value;
-                }
+        // Allowed (standard) types
+        for (const allowedType of ALLOWED_TYPES) {
+            if (value.constructor === allowedType) {
+                return value;
             }
+        }
 
-            // Allow exceptions
-            if (value instanceof Error) {
-                return value.toString();
-            }
+        // Allow exceptions
+        if (value instanceof Error) {
+            return value.toString();
+        }
 
-            // Filter binary data
-            if (value instanceof ArrayBuffer) {
-                return `[ArrayBuffer: length=${value.byteLength}]`;
-            }
-            if (value instanceof Uint8Array) {
-                return `[Uint8Array: length=${value.byteLength}, offset=${value.byteOffset}]`;
-            }
-            if (value instanceof Blob) {
-                return `[Blob: length=${value.size}, type=${value.type}]`;
-            }
+        // Filter binary data
+        if (value instanceof ArrayBuffer) {
+            return `[ArrayBuffer: length=${value.byteLength}]`;
+        }
+        if (value instanceof Uint8Array) {
+            return `[Uint8Array: length=${value.byteLength}, offset=${value.byteOffset}]`;
+        }
+        if (value instanceof Blob) {
+            return `[Blob: length=${value.size}, type=${value.type}]`;
+        }
 
-            // Plain object
-            if (value.constructor === Object) {
-                return value;
-            }
+        // Plain object
+        if (value.constructor === Object) {
+            return value;
+        }
 
-            // Not listed
-            return `[${value.constructor.name}]`;
-        }, space);
+        // Not listed
+        return `[${value.constructor.name}]`;
     }
 }

+ 1 - 1
src/threema.d.ts

@@ -20,7 +20,7 @@ declare const angular: ng.IAngularStatic;
 declare namespace threema {
     type LogType = 'debug' | 'trace' | 'info' | 'warn' | 'error';
     type LogLevel = 'none' | 'debug' | 'info' | 'warn' | 'error';
-    type LogRecord = [Date, LogType, any?, ...any[]];
+    type LogRecord = [number, LogType, any?, ...any[]];
 
     /**
      * An object can be marked as confidential in which case it needs to

+ 3 - 1
tests/bootstrap.ts

@@ -17,14 +17,16 @@
  * along with Threema Web. If not, see <http://www.gnu.org/licenses/>.
  */
 
-// tslint:disable:no-console
 import config from '../src/config';
+import {MemoryLogger} from '../src/helpers/logger';
 
 // A dependency graph that contains any wasm must all be imported asynchronously.
 import('../src/app')
     .then(() => {
         // @ts-ignore
         window.config = config;
+        // @ts-ignore
+        window.MemoryLogger = MemoryLogger;
         console.info('Bundle loaded')
     })
     .catch((e) => console.error('Could not load bundle', e));

+ 4 - 4
tests/service/log.js

@@ -94,7 +94,7 @@ describe('LogService', function() {
             // Expect the memory logger to have been called for 'debug' and above
             // (i.e. all log levels).
             expect(JSON
-                .parse($service.memory.serialize())
+                .parse(JSON.stringify($service.memory.getRecords(), MemoryLogger.replacer))
                 .map((record) => record.slice(1))
             ).toEqual([
                 ['debug', '[test]', 'debug'],
@@ -116,7 +116,7 @@ describe('LogService', function() {
 
             // Expect the memory logger tag to be unpadded
             expect(JSON
-                .parse($service.memory.serialize())
+                .parse(JSON.stringify($service.memory.getRecords(), MemoryLogger.replacer))
                 .map((record) => record.slice(1))
             ).toEqual([
                 ['info', '[test]', 'test']
@@ -135,7 +135,7 @@ describe('LogService', function() {
 
             // Expect the memory logger tag to be unpadded and unstyled
             expect(JSON
-                .parse($service.memory.serialize())
+                .parse(JSON.stringify($service.memory.getRecords(), MemoryLogger.replacer))
                 .map((record) => record.slice(1))
             ).toEqual([
                 ['info', '[test]', 'test']
@@ -155,7 +155,7 @@ describe('LogService', function() {
 
             // Expect the memory logger to only contain the 'info' log
             expect(JSON
-                .parse($service.memory.serialize())
+                .parse(JSON.stringify($service.memory.getRecords(), MemoryLogger.replacer))
                 .map((record) => record.slice(1))
             ).toEqual([
                 ['info', '[test]', 'info']

+ 12 - 12
tests/ts/logger_helpers.ts

@@ -298,7 +298,7 @@ describe('Logger Helpers', () => {
             }
             const end = Date.now();
             const timestamps = JSON
-                .parse(logger.serialize())
+                .parse(JSON.stringify(logger.getRecords(), MemoryLogger.replacer))
                 .map((entry) => entry[0]);
             expect(timestamps.length).toBe(10);
             for (const timestamp of timestamps) {
@@ -323,7 +323,7 @@ describe('Logger Helpers', () => {
             ];
             logger.debug(...record.slice(1));
             const records = JSON
-                .parse(logger.serialize())
+                .parse(JSON.stringify(logger.getRecords(), MemoryLogger.replacer))
                 .map((entry) => entry.slice(1));
             expect(records.length).toBe(1);
             expect(records[0]).toEqual(record);
@@ -344,7 +344,7 @@ describe('Logger Helpers', () => {
             ];
             logger.debug('  te%cst  ', 'color: #fff', ...args);
             const records = JSON
-                .parse(logger.serialize())
+                .parse(JSON.stringify(logger.getRecords(), MemoryLogger.replacer))
                 .map((entry) => entry.slice(1));
             expect(records.length).toBe(1);
             expect(records[0]).toEqual((['debug', 'test'] as any[]).concat(args));
@@ -371,7 +371,7 @@ describe('Logger Helpers', () => {
             ];
             logger.debug(...record.slice(1));
             const records = JSON
-                .parse(logger.serialize())
+                .parse(JSON.stringify(logger.getRecords(), MemoryLogger.replacer))
                 .map((entry) => entry.slice(1));
             expect(records.length).toBe(1);
             expect(records[0]).toEqual(record);
@@ -391,7 +391,7 @@ describe('Logger Helpers', () => {
             ];
             logger.debug(...record.slice(1));
             const records = JSON
-                .parse(logger.serialize())
+                .parse(JSON.stringify(logger.getRecords(), MemoryLogger.replacer))
                 .map((entry) => entry.slice(1));
             expect(records.length).toBe(1);
             expect(records[0]).toEqual(record);
@@ -404,7 +404,7 @@ describe('Logger Helpers', () => {
             const confidential = new TestConfidential();
             logger.debug(confidential, confidential, confidential);
             const records = JSON
-                .parse(logger.serialize())
+                .parse(JSON.stringify(logger.getRecords(), MemoryLogger.replacer))
                 .map((entry) => entry.slice(1));
             expect(records.length).toBe(1);
             expect(records[0]).toEqual(['debug', 'censored', 'censored', 'censored']);
@@ -417,7 +417,7 @@ describe('Logger Helpers', () => {
             const error = new Error('WTF!');
             logger.error(error);
             const records = JSON
-                .parse(logger.serialize())
+                .parse(JSON.stringify(logger.getRecords(), MemoryLogger.replacer))
                 .map((entry) => entry.slice(1));
             expect(records.length).toBe(1);
             expect(records[0]).toEqual(['error', error.toString()]);
@@ -433,7 +433,7 @@ describe('Logger Helpers', () => {
             const blob = new Blob([JSON.stringify({ a: 10 })], { type: 'application/json'} );
             logger.debug(buffer, array, blob);
             const records = JSON
-                .parse(logger.serialize())
+                .parse(JSON.stringify(logger.getRecords(), MemoryLogger.replacer))
                 .map((entry) => entry.slice(1));
             expect(records.length).toBe(1);
             expect(records[0]).toEqual([
@@ -450,7 +450,7 @@ describe('Logger Helpers', () => {
             // Ensure instances are being represented with their name.
             logger.debug(logger);
             const records = JSON
-                .parse(logger.serialize())
+                .parse(JSON.stringify(logger.getRecords(), MemoryLogger.replacer))
                 .map((entry) => entry.slice(1));
             expect(records.length).toBe(1);
             expect(records[0]).toEqual(['debug', '[MemoryLogger]']);
@@ -469,7 +469,7 @@ describe('Logger Helpers', () => {
             };
             logger.debug(object);
             const records = JSON
-                .parse(logger.serialize())
+                .parse(JSON.stringify(logger.getRecords(), MemoryLogger.replacer))
                 .map((entry) => entry.slice(1));
             expect(records.length).toBe(1);
             expect(records[0]).toEqual(['debug', object]);
@@ -486,7 +486,7 @@ describe('Logger Helpers', () => {
             ];
             logger.debug(array);
             const records = JSON
-                .parse(logger.serialize())
+                .parse(JSON.stringify(logger.getRecords(), MemoryLogger.replacer))
                 .map((entry) => entry.slice(1));
             expect(records.length).toBe(1);
             expect(records[0]).toEqual(['debug', array]);
@@ -500,7 +500,7 @@ describe('Logger Helpers', () => {
                 logger.debug(i);
             }
             const records = JSON
-                .parse(logger.serialize())
+                .parse(JSON.stringify(logger.getRecords(), MemoryLogger.replacer))
                 .map((entry) => entry.slice(1));
             expect(records).toEqual([
                 ['debug', 8],