Ver Fonte

Replace Angular logger with ts-log and our custom LogService

Lennart Grahl há 6 anos atrás
pai
commit
7017af415c
41 ficheiros alterados com 660 adições e 685 exclusões
  1. 13 7
      src/config.ts
  2. 6 8
      src/controller_model/avatar.ts
  3. 14 13
      src/controller_model/contact.ts
  4. 16 16
      src/controller_model/distributionList.ts
  5. 24 24
      src/controller_model/group.ts
  6. 11 9
      src/controller_model/me.ts
  7. 23 19
      src/controllers/status.ts
  8. 7 10
      src/directives/avatar.ts
  9. 6 6
      src/directives/avatar_area.ts
  10. 10 10
      src/directives/avatar_editor.ts
  11. 10 11
      src/directives/compose_area.ts
  12. 7 7
      src/directives/drag_file.ts
  13. 6 9
      src/directives/mediabox.ts
  14. 5 3
      src/directives/member_list_editor.ts
  15. 9 10
      src/directives/message.ts
  16. 18 25
      src/directives/message_media.ts
  17. 5 3
      src/directives/verification_level.ts
  18. 2 25
      src/filters.ts
  19. 9 21
      src/helpers.ts
  20. 69 74
      src/partials/messenger.ts
  21. 31 29
      src/partials/welcome.ts
  22. 12 2
      src/services/battery.ts
  23. 8 10
      src/services/browser.ts
  24. 0 7
      src/services/controller.ts
  25. 17 12
      src/services/controller_model.ts
  26. 0 7
      src/services/fingerprint.ts
  27. 11 11
      src/services/keystore.ts
  28. 9 9
      src/services/mediabox.ts
  29. 2 7
      src/services/message.ts
  30. 2 4
      src/services/mime.ts
  31. 27 24
      src/services/notification.ts
  32. 24 25
      src/services/peerconnection.ts
  33. 22 20
      src/services/push.ts
  34. 11 10
      src/services/settings.ts
  35. 17 15
      src/services/state.ts
  36. 15 20
      src/services/timeout.ts
  37. 3 6
      src/services/title.ts
  38. 19 17
      src/services/version.ts
  39. 142 129
      src/services/webclient.ts
  40. 4 3
      src/threema.d.ts
  41. 14 8
      src/threema/container.ts

+ 13 - 7
src/config.ts

@@ -52,16 +52,22 @@ export default {
     COMPOSE_AREA_LOG_LEVEL: 'warn',
     // SaltyRTC log level
     SALTYRTC_LOG_LEVEL: 'warn',
-    // Toggles logging verbose timer-related information.
-    DEBUG_TIMER: false,
-    // Toggles logging all chunks and messages exchanged by or associated with
-    // the app remote protocol. May also enable additional debug facilities of
-    // the app remote protocol.
+    // Timer (created by the TimeoutService) log level.
+    // Note: Log records filtered by this level will prevent them from being
+    //       picked up by the console and the report logger.
+    TIMER_LOG_LEVEL: 'info',
+    // App remote protocol log level.
+    // Note: Log records filtered by this level will prevent them from being
+    //       picked up by the console and the report logger.
+    ARP_LOG_LEVEL: 'debug',
+    // Toggles expensive or sensitive logging operations. Toggles logging of
+    // all chunks and messages exchanged by or associated with the app remote
+    // protocol.
     // Note: Affects performance and contains sensitive information.
-    DEBUG_ARP: false,
+    ARP_LOG_TRACE: false,
     // Toggles URL logging to visualise MsgPack messages for all incoming and
     // outgoing protocol messages.
     // Note: Affects performance and contains sensitive information.
-    DEBUG_MSGPACK: false,
+    MSGPACK_LOG_TRACE: false,
 
 } as threema.Config;

+ 6 - 8
src/controller_model/avatar.ts

@@ -16,33 +16,31 @@
  */
 
 import {hasValue} from '../helpers';
+import {LogService} from '../services/log';
 import {WebClientService} from '../services/webclient';
 
 export class AvatarControllerModel {
-    private logTag: string = '[AvatarControllerModel]';
-
-    private $log: ng.ILogService;
     private avatar: ArrayBuffer | null = null;
     private loadAvatar: Promise<ArrayBuffer | null>;
     private onChangeAvatar: (image: ArrayBuffer) => void;
     private _avatarChanged: boolean = false;
 
-    constructor($log: ng.ILogService,
+    constructor(logService: LogService,
                 webClientService: WebClientService,
                 receiver: threema.Receiver | null) {
-        this.$log = $log;
+        const log = logService.getLogger('Avatar-CM');
         this.loadAvatar = new Promise((resolve, reject) => {
             if (!hasValue(receiver)) {
-                $log.debug(this.logTag, 'loadAvatar: No receiver defined, no avatar');
+                log.debug('No receiver defined, no avatar');
                 resolve(null);
                 return;
             } else if (!hasValue(receiver.avatar) || !hasValue(receiver.avatar.high)) {
-                $log.debug(this.logTag, 'loadAvatar: Requesting high res avatar from app');
+                log.debug('Requesting high res avatar from app');
                 webClientService.requestAvatar(receiver, true)
                     .then((data: ArrayBuffer) => resolve(data))
                     .catch((error) => reject(error));
             } else {
-                $log.debug(this.logTag, 'loadAvatar: Returning cached version');
+                log.debug('Returning cached avatar');
                 resolve(receiver.avatar.high);
             }
         });

+ 14 - 13
src/controller_model/contact.ts

@@ -15,6 +15,8 @@
  * along with Threema Web. If not, see <http://www.gnu.org/licenses/>.
  */
 
+import {Logger} from 'ts-log';
+import {LogService} from '../services/log';
 import {WebClientService} from '../services/webclient';
 import {AvatarControllerModel} from './avatar';
 
@@ -22,10 +24,7 @@ import {AvatarControllerModel} from './avatar';
 import ControllerModelMode = threema.ControllerModelMode;
 
 export class ContactControllerModel implements threema.ControllerModel<threema.ContactReceiver> {
-    private logTag = '[ContactControllerModel]';
-
     // Angular services
-    private $log: ng.ILogService;
     private $translate: ng.translate.ITranslateService;
     private $mdDialog: ng.material.IDialogService;
 
@@ -37,19 +36,21 @@ export class ContactControllerModel implements threema.ControllerModel<threema.C
     public access: threema.ContactReceiverAccess;
     public isLoading = false;
 
+    private readonly log: Logger;
     private contact: threema.ContactReceiver | null;
     private webClientService: WebClientService;
     private firstNameLabel: string;
     private avatarController: AvatarControllerModel;
     private mode = ControllerModelMode.NEW;
 
-    constructor($log: ng.ILogService, $translate: ng.translate.ITranslateService, $mdDialog: ng.material.IDialogService,
-                webClientService: WebClientService,
+    constructor($translate: ng.translate.ITranslateService, $mdDialog: ng.material.IDialogService,
+                logService: LogService, webClientService: WebClientService,
                 mode: ControllerModelMode,
                 contact?: threema.ContactReceiver) {
-        this.$log = $log;
         this.$translate = $translate;
         this.$mdDialog = $mdDialog;
+        this.log = logService.getLogger('Contact-CM');
+
         if (contact === undefined) {
             if (mode !== ControllerModelMode.NEW) {
                 throw new Error('ContactControllerModel: Contact may not be undefined for mode ' + mode);
@@ -66,7 +67,7 @@ export class ContactControllerModel implements threema.ControllerModel<threema.C
                 this.firstName = this.contact!.firstName;
                 this.lastName = this.contact!.lastName;
                 this.avatarController = new AvatarControllerModel(
-                    this.$log, this.webClientService, this.contact,
+                    logService, this.webClientService, this.contact,
                 );
 
                 this.access = this.contact!.access;
@@ -86,7 +87,7 @@ export class ContactControllerModel implements threema.ControllerModel<threema.C
                 break;
 
             default:
-                $log.error(this.logTag, 'Invalid controller model mode: ', this.getMode());
+                this.log.error('Invalid controller model mode: ', this.getMode());
         }
     }
 
@@ -133,17 +134,17 @@ export class ContactControllerModel implements threema.ControllerModel<threema.C
         this.$mdDialog.show(confirm).then(() => {
             this.reallyClean();
         }, () => {
-            this.$log.debug(this.logTag, 'Clean canceled');
+            this.log.debug('Clean cancelled');
         });
     }
 
     private reallyClean(): any {
         if (!this.contact) {
-            this.$log.error(this.logTag, 'reallyClean: Contact is null');
+            this.log.error('reallyClean: Contact is null');
             return;
         }
         if (!this.canClean()) {
-            this.$log.error(this.logTag, 'Not allowed to clean this contact');
+            this.log.error('Not allowed to clean this contact');
             return;
         }
 
@@ -154,7 +155,7 @@ export class ContactControllerModel implements threema.ControllerModel<threema.C
             })
             .catch((error) => {
                 // TODO: Handle this properly / show an error message
-                this.$log.error(this.logTag, `Cleaning receiver conversation failed: ${error}`);
+                this.log.error(`Cleaning receiver conversation failed: ${error}`);
                 this.isLoading = false;
             });
     }
@@ -175,7 +176,7 @@ export class ContactControllerModel implements threema.ControllerModel<threema.C
             case ControllerModelMode.NEW:
                 return this.webClientService.addContact(this.identity);
             default:
-                this.$log.error(this.logTag, 'Cannot save contact, invalid mode');
+                this.log.error('Cannot save contact, invalid mode');
                 return Promise.reject('Cannot save contact, invalid mode');
         }
     }

+ 16 - 16
src/controller_model/distributionList.ts

@@ -15,17 +15,17 @@
  * along with Threema Web. If not, see <http://www.gnu.org/licenses/>.
  */
 
+import {Logger} from 'ts-log';
+import {LogService} from '../services/log';
 import {WebClientService} from '../services/webclient';
 
 // Type aliases
 import ControllerModelMode = threema.ControllerModelMode;
 
 export class DistributionListControllerModel implements threema.ControllerModel<threema.DistributionListReceiver> {
-    private logTag = '[DistributionListControllerModel]';
-
-    private $log: ng.ILogService;
     private $translate: ng.translate.ITranslateService;
     private $mdDialog: ng.material.IDialogService;
+    private readonly log: Logger;
     public members: string[];
     public name: string;
     public subject: string;
@@ -37,13 +37,13 @@ export class DistributionListControllerModel implements threema.ControllerModel<
     private mode: ControllerModelMode;
     private onRemovedCallback: threema.OnRemovedCallback;
 
-    constructor($log: ng.ILogService, $translate: ng.translate.ITranslateService, $mdDialog: ng.material.IDialogService,
-                webClientService: WebClientService,
+    constructor($translate: ng.translate.ITranslateService, $mdDialog: ng.material.IDialogService,
+                logService: LogService, webClientService: WebClientService,
                 mode: ControllerModelMode,
                 distributionList?: threema.DistributionListReceiver) {
-        this.$log = $log;
         this.$translate = $translate;
         this.$mdDialog = $mdDialog;
+        this.log = logService.getLogger('DistributionList-CM');
 
         if (distributionList === undefined) {
             if (mode !== ControllerModelMode.NEW) {
@@ -76,7 +76,7 @@ export class DistributionListControllerModel implements threema.ControllerModel<
                 break;
 
             default:
-                $log.error(this.logTag, 'Invalid controller model mode: ', this.getMode());
+                this.log.error('Invalid controller model mode: ', this.getMode());
         }
     }
 
@@ -118,17 +118,17 @@ export class DistributionListControllerModel implements threema.ControllerModel<
         this.$mdDialog.show(confirm).then(() => {
             this.reallyClean();
         }, () => {
-            this.$log.debug(this.logTag, 'Clean canceled');
+            this.log.debug('Clean cancelled');
         });
     }
 
     private reallyClean(): any {
         if (!this.distributionList) {
-            this.$log.error(this.logTag, 'reallyClean: Distribution list is null');
+            this.log.error('reallyClean: Distribution list is null');
             return;
         }
         if (!this.canClean()) {
-            this.$log.error(this.logTag, 'Not allowed to clean this distribution list');
+            this.log.error('Not allowed to clean this distribution list');
             return;
         }
 
@@ -139,7 +139,7 @@ export class DistributionListControllerModel implements threema.ControllerModel<
             })
             .catch((error) => {
                 // TODO: Handle this properly / show an error message
-                this.$log.error(this.logTag, `Cleaning receiver conversation failed: ${error}`);
+                this.log.error(`Cleaning receiver conversation failed: ${error}`);
                 this.isLoading = false;
             });
     }
@@ -159,17 +159,17 @@ export class DistributionListControllerModel implements threema.ControllerModel<
         this.$mdDialog.show(confirm).then(() => {
             this.reallyDelete();
         }, () => {
-            this.$log.debug(this.logTag, 'Delete canceled');
+            this.log.debug('Delete cancelled');
         });
     }
 
     private reallyDelete(): void {
         if (!this.distributionList) {
-            this.$log.error(this.logTag, 'reallyDelete: Distribution list is null');
+            this.log.error('reallyDelete: Distribution list is null');
             return;
         }
         if (!this.distributionList.access.canDelete) {
-            this.$log.error(this.logTag, 'Not allowed to delete this distribution list');
+            this.log.error('Not allowed to delete this distribution list');
             return;
         }
 
@@ -181,7 +181,7 @@ export class DistributionListControllerModel implements threema.ControllerModel<
             }
         }).catch((error) => {
             // TODO: Handle this properly / show an error message
-            this.$log.error(this.logTag, `Deleting distribution list failed: ${error}`);
+            this.log.error(`Deleting distribution list failed: ${error}`);
             this.isLoading = false;
         });
     }
@@ -199,7 +199,7 @@ export class DistributionListControllerModel implements threema.ControllerModel<
                     this.members,
                     this.name);
             default:
-                this.$log.error(this.logTag, 'Cannot save distribution list, invalid mode');
+                this.log.error('Cannot save distribution list, invalid mode');
                 return Promise.reject('Cannot save distribution list, invalid mode');
         }
     }

+ 24 - 24
src/controller_model/group.ts

@@ -15,6 +15,8 @@
  * along with Threema Web. If not, see <http://www.gnu.org/licenses/>.
  */
 
+import {Logger} from 'ts-log';
+import {LogService} from '../services/log';
 import {WebClientService} from '../services/webclient';
 import {AvatarControllerModel} from './avatar';
 
@@ -22,9 +24,7 @@ import {AvatarControllerModel} from './avatar';
 import ControllerModelMode = threema.ControllerModelMode;
 
 export class GroupControllerModel implements threema.ControllerModel<threema.GroupReceiver> {
-    private logTag = '[GroupControllerModel]';
-
-    private $log: ng.ILogService;
+    private log: Logger;
     private $translate: ng.translate.ITranslateService;
     private $mdDialog: ng.material.IDialogService;
     public members: string[];
@@ -41,13 +41,13 @@ export class GroupControllerModel implements threema.ControllerModel<threema.Gro
     private mode: ControllerModelMode;
     private onRemovedCallback: threema.OnRemovedCallback;
 
-    constructor($log: ng.ILogService, $translate: ng.translate.ITranslateService, $mdDialog: ng.material.IDialogService,
-                webClientService: WebClientService,
+    constructor($translate: ng.translate.ITranslateService, $mdDialog: ng.material.IDialogService,
+                logService: LogService, webClientService: WebClientService,
                 mode: ControllerModelMode,
                 group?: threema.GroupReceiver) {
-        this.$log = $log;
         this.$translate = $translate;
         this.$mdDialog = $mdDialog;
+        this.log = logService.getLogger('Group-CM');
 
         if (group === undefined) {
             if (mode !== ControllerModelMode.NEW) {
@@ -66,7 +66,7 @@ export class GroupControllerModel implements threema.ControllerModel<threema.Gro
                 this.name = this.group!.displayName;
                 this.members = this.group!.members;
                 this.avatarController = new AvatarControllerModel(
-                    this.$log, this.webClientService, this.group!,
+                    logService, this.webClientService, this.group!,
                 );
                 this.access = this.group!.access;
                 break;
@@ -82,12 +82,12 @@ export class GroupControllerModel implements threema.ControllerModel<threema.Gro
                 this.subject = $translate.instant('messenger.CREATE_GROUP');
                 this.members = [];
                 this.avatarController = new AvatarControllerModel(
-                    this.$log, this.webClientService, null,
+                    logService, this.webClientService, null,
                 );
                 break;
 
             default:
-                $log.error(this.logTag, 'Invalid controller model mode: ', this.getMode());
+                this.log.error('Invalid controller model mode: ', this.getMode());
         }
     }
 
@@ -136,17 +136,17 @@ export class GroupControllerModel implements threema.ControllerModel<threema.Gro
         this.$mdDialog.show(confirm).then(() => {
             this.reallyClean();
         }, () => {
-            this.$log.debug('clean canceled');
+            this.log.debug('Clean cancelled');
         });
     }
 
     private reallyClean(): any {
         if (!this.group) {
-            this.$log.error(this.logTag, 'reallyClean: Group is null');
+            this.log.error('reallyClean: Group is null');
             return;
         }
         if (!this.canClean()) {
-            this.$log.error(this.logTag, 'Not allowed to clean this group');
+            this.log.error('Not allowed to clean this group');
             return;
         }
 
@@ -157,7 +157,7 @@ export class GroupControllerModel implements threema.ControllerModel<threema.Gro
             })
             .catch((error) => {
                 // TODO: Handle this properly / show an error message
-                this.$log.error(this.logTag, `Cleaning receiver conversation failed: ${error}`);
+                this.log.error(`Cleaning receiver conversation failed: ${error}`);
                 this.isLoading = false;
             });
     }
@@ -168,7 +168,7 @@ export class GroupControllerModel implements threema.ControllerModel<threema.Gro
 
     public leave(ev): void {
         if (!this.group) {
-            this.$log.error(this.logTag, 'leave: Group is null');
+            this.log.error('leave: Group is null');
             return;
         }
         const confirm = this.$mdDialog.confirm()
@@ -184,13 +184,13 @@ export class GroupControllerModel implements threema.ControllerModel<threema.Gro
         this.$mdDialog.show(confirm).then(() => {
             this.reallyLeave(this.group!);
         }, () => {
-            this.$log.debug(this.logTag, 'Leave canceled');
+            this.log.debug('Leave cancelled');
         });
     }
 
     private reallyLeave(group: threema.GroupReceiver): void {
         if (!group.access.canLeave) {
-            this.$log.error(this.logTag, 'Cannot leave group');
+            this.log.error('Cannot leave group');
             return;
         }
 
@@ -201,14 +201,14 @@ export class GroupControllerModel implements threema.ControllerModel<threema.Gro
             })
             .catch((error) => {
                 // TODO: Handle this properly / show an error message
-                this.$log.error(`Leaving group failed: ${error}`);
+                this.log.error(`Leaving group failed: ${error}`);
                 this.isLoading = false;
             });
     }
 
     public delete(ev): void {
         if (!this.group) {
-            this.$log.error(this.logTag, 'delete: Group is null');
+            this.log.error('delete: Group is null');
             return;
         }
 
@@ -222,13 +222,13 @@ export class GroupControllerModel implements threema.ControllerModel<threema.Gro
         this.$mdDialog.show(confirm).then(() => {
             this.reallyDelete(this.group!);
         }, () => {
-            this.$log.debug('delete canceled');
+            this.log.debug('Delete cancelled');
         });
     }
 
     private reallyDelete(group: threema.GroupReceiver): void {
         if (!this.access.canDelete) {
-            this.$log.error('can not delete group');
+            this.log.error('Can not delete group');
             return;
         }
 
@@ -242,18 +242,18 @@ export class GroupControllerModel implements threema.ControllerModel<threema.Gro
             })
             .catch((error) => {
                 // TODO: Handle this properly / show an error message
-                this.$log.error(`Deleting group failed: ${error}`);
+                this.log.error(`Deleting group failed: ${error}`);
                 this.isLoading = false;
             });
     }
 
     public sync(ev): void {
         if (!this.group) {
-            this.$log.error(this.logTag, 'sync: Group is null');
+            this.log.error('sync: Group is null');
             return;
         }
         if (!this.access.canSync) {
-            this.$log.error(this.logTag, 'Cannot sync group');
+            this.log.error('Cannot sync group');
             return;
         }
 
@@ -284,7 +284,7 @@ export class GroupControllerModel implements threema.ControllerModel<threema.Gro
                     this.avatarController.avatarChanged ? this.avatarController.getAvatar() : undefined,
                 );
             default:
-                this.$log.error(this.logTag, 'Cannot save group, invalid mode');
+                this.log.error('Cannot save group, invalid mode');
                 return Promise.reject('Cannot save group, invalid mode');
         }
     }

+ 11 - 9
src/controller_model/me.ts

@@ -15,20 +15,22 @@
  * along with Threema Web. If not, see <http://www.gnu.org/licenses/>.
  */
 
+import {Logger} from 'ts-log';
 import {hasValue} from '../helpers';
+import {LogService} from '../services/log';
 import {WebClientService} from '../services/webclient';
 import {AvatarControllerModel} from './avatar';
 
 import ControllerModelMode = threema.ControllerModelMode;
 
 export class MeControllerModel implements threema.ControllerModel<threema.MeReceiver> {
-    private logTag: string = '[MeControllerModel]';
-
     // Angular services
-    private $log: ng.ILogService;
     private $translate: ng.translate.ITranslateService;
     private $mdDialog: ng.material.IDialogService;
 
+    // Logging
+    private readonly log: Logger;
+
     // Own services
     private webClientService: WebClientService;
 
@@ -48,13 +50,13 @@ export class MeControllerModel implements threema.ControllerModel<threema.MeRece
     // Editing mode
     private mode = ControllerModelMode.VIEW;
 
-    constructor($log: ng.ILogService,
-                $translate: ng.translate.ITranslateService,
+    constructor($translate: ng.translate.ITranslateService,
                 $mdDialog: ng.material.IDialogService,
+                logService: LogService,
                 webClientService: WebClientService,
                 mode: ControllerModelMode,
                 me: threema.MeReceiver) {
-        this.$log = $log;
+        this.log = logService.getLogger('Me-CM');
         this.$translate = $translate;
         this.$mdDialog = $mdDialog;
         this.me = me;
@@ -66,14 +68,14 @@ export class MeControllerModel implements threema.ControllerModel<threema.MeRece
             case ControllerModelMode.EDIT:
                 this.subject = $translate.instant('messenger.EDIT_RECEIVER');
                 this.avatarController = new AvatarControllerModel(
-                    this.$log, this.webClientService, this.me,
+                    logService, this.webClientService, this.me,
                 );
                 break;
             case ControllerModelMode.VIEW:
                 this.subject = $translate.instant('messenger.MY_THREEMA_ID');
                 break;
             default:
-                $log.error(this.logTag, 'Invalid controller model mode: ', this.getMode());
+                this.log.error('Invalid controller model mode: ', this.getMode());
         }
     }
 
@@ -173,7 +175,7 @@ export class MeControllerModel implements threema.ControllerModel<threema.MeRece
                     return this.me;
                 });
             default:
-                this.$log.error(this.logTag, 'Not allowed to save profile: Invalid mode');
+                this.log.error('Not allowed to save profile: Invalid mode');
                 return Promise.reject('unknown');
         }
     }

+ 23 - 19
src/controllers/status.ts

@@ -15,9 +15,12 @@
  * along with Threema Web. If not, see <http://www.gnu.org/licenses/>.
  */
 
+import {Logger} from 'ts-log';
+
 import {StateService as UiStateService} from '@uirouter/angularjs';
 
 import {ControllerService} from '../services/controller';
+import {LogService} from '../services/log';
 import {StateService} from '../services/state';
 import {TimeoutService} from '../services/timeout';
 import {WebClientService} from '../services/webclient';
@@ -33,8 +36,8 @@ import DisconnectReason = threema.DisconnectReason;
  * Status updates should be done through the state service.
  */
 export class StatusController {
-
-    private logTag: string = '[StatusController]';
+    // Logging
+    private readonly log: Logger;
 
     // State variable
     private state = GlobalConnectionState.Error;
@@ -50,7 +53,6 @@ export class StatusController {
 
     // Angular services
     private $timeout: ng.ITimeoutService;
-    private $log: ng.ILogService;
     private $state: UiStateService;
 
     // Custom services
@@ -60,16 +62,18 @@ export class StatusController {
     private webClientService: WebClientService;
 
     public static $inject = [
-        '$scope', '$timeout', '$log', '$state',
-        'ControllerService', 'StateService', 'TimeoutService', 'WebClientService',
+        '$scope', '$timeout', '$state',
+        'ControllerService', 'StateService', 'LogService', 'TimeoutService', 'WebClientService',
     ];
-    constructor($scope, $timeout: ng.ITimeoutService, $log: ng.ILogService, $state: UiStateService,
-                controllerService: ControllerService, stateService: StateService,
+    constructor($scope, $timeout: ng.ITimeoutService, $state: UiStateService,
+                controllerService: ControllerService, stateService: StateService, logService: LogService,
                 timeoutService: TimeoutService, webClientService: WebClientService) {
 
+        // Logging
+        this.log = logService.getLogger('Status-C', 'color: #000; background-color: #ffff99');
+
         // Angular services
         this.$timeout = $timeout;
-        this.$log = $log;
         this.$state = $state;
 
         // Custom services
@@ -105,7 +109,7 @@ export class StatusController {
      */
     private onStateChange(newValue: threema.GlobalConnectionState,
                           oldValue: threema.GlobalConnectionState): void {
-        this.$log.debug(this.logTag, 'State change:', oldValue, '->', newValue);
+        this.log.debug('State change:', oldValue, '->', newValue);
         if (newValue === oldValue) {
             return;
         }
@@ -136,7 +140,7 @@ export class StatusController {
                 }
                 break;
             default:
-                this.$log.error(this.logTag, 'Invalid state change: From', oldValue, 'to', newValue);
+                this.log.error('Invalid state change: From', oldValue, 'to', newValue);
         }
     }
 
@@ -163,7 +167,7 @@ export class StatusController {
      * Attempt to reconnect an Android device after a connection loss.
      */
     private reconnectAndroid(): void {
-        this.$log.info(this.logTag, `Connection lost (Android). Reconnect attempt #${this.stateService.attempt + 1}`);
+        this.log.info(`Connection lost (Android). Reconnect attempt #${this.stateService.attempt + 1}`);
 
         // Show expanded status bar (if on 'messenger')
         if (this.$state.includes('messenger')) {
@@ -198,12 +202,12 @@ export class StatusController {
             .then(
                 () => { /* ignored */ },
                 (error) => {
-                    this.$log.error(this.logTag, 'Error state:', error);
+                    this.log.error('Error state:', error);
                     // Note: The web client service has already been stopped at
                     // this point.
                 },
                 (progress: threema.ConnectionBuildupStateChange) => {
-                    this.$log.debug(this.logTag, 'Connection buildup advanced:', progress);
+                    this.log.debug('Connection buildup advanced:', progress);
                 },
             )
             .finally(() => {
@@ -217,7 +221,7 @@ export class StatusController {
      * Attempt to reconnect an iOS device after a connection loss.
      */
     private reconnectIos(): void {
-        this.$log.info(this.logTag, `Connection lost (iOS). Reconnect attempt #${++this.stateService.attempt}`);
+        this.log.info(`Connection lost (iOS). Reconnect attempt #${++this.stateService.attempt}`);
 
         // Get original keys
         const originalKeyStore = this.webClientService.salty.keyStore;
@@ -226,7 +230,7 @@ export class StatusController {
         // Delay connecting a bit to wait for old websocket to close
         // TODO: Make this more robust and hopefully faster
         const startTimeout = 500;
-        this.$log.debug(this.logTag, 'Stopping old connection');
+        this.log.debug('Stopping old connection');
         this.webClientService.stop({
             reason: DisconnectReason.SessionStopped,
             send: true,
@@ -262,9 +266,9 @@ export class StatusController {
         this.$timeout.cancel(this.reconnectTimeout);
         this.reconnectTimeout = this.$timeout(() => {
             if (push.send) {
-                this.$log.debug(`Starting new connection with push, reason: ${push.reason}`);
+                this.log.debug(`Starting new connection with push, reason: ${push.reason}`);
             } else {
-                this.$log.debug('Starting new connection without push');
+                this.log.debug('Starting new connection without push');
             }
             this.webClientService.init({
                 keyStore: originalKeyStore,
@@ -275,12 +279,12 @@ export class StatusController {
             this.webClientService.start(!push.send).then(
                 () => { /* ignored */ },
                 (error) => {
-                    this.$log.error(this.logTag, 'Error state:', error);
+                    this.log.error('Error state:', error);
                     // Note: The web client service has already been stopped at
                     // this point.
                 },
                 (progress: threema.ConnectionBuildupStateChange) => {
-                    this.$log.debug(this.logTag, 'Connection buildup advanced:', progress);
+                    this.log.debug('Connection buildup advanced:', progress);
                 },
             );
         }, startTimeout);

+ 7 - 10
src/directives/avatar.ts

@@ -15,21 +15,23 @@
  * along with Threema Web. If not, see <http://www.gnu.org/licenses/>.
  */
 
-import {bufferToUrl, hasValue, logAdapter} from '../helpers';
+import {bufferToUrl, hasValue} from '../helpers';
 import {isEchoContact, isGatewayContact} from '../receiver_helpers';
+import {LogService} from '../services/log';
 import {TimeoutService} from '../services/timeout';
 import {WebClientService} from '../services/webclient';
 import {isContactReceiver} from '../typeguards';
 
 export default [
     '$rootScope',
-    '$log',
+    'LogService',
     'TimeoutService',
     'WebClientService',
     function($rootScope: ng.IRootScopeService,
-             $log: ng.ILogService,
+             logService: LogService,
              timeoutService: TimeoutService,
              webClientService: WebClientService) {
+        const log = logService.getLogger('Avatar-C');
         return {
             restrict: 'E',
             scope: {},
@@ -81,8 +83,6 @@ export default [
             },
             controllerAs: 'ctrl',
             controller: [function() {
-                this.logTag = '[Directives.Avatar]';
-
                 let loadingPromise: ng.IPromise<any> = null;
 
                 /**
@@ -99,10 +99,7 @@ export default [
                     if (avatarUri[res] === null) {
                         // Cache avatar image URI
                         avatarUri[res] = bufferToUrl(
-                            data,
-                            webClientService.appCapabilities.imageFormat.avatar,
-                            logAdapter($log.warn, this.logTag),
-                        );
+                            data, webClientService.appCapabilities.imageFormat.avatar, log);
                     }
                     return avatarUri[res];
                 };
@@ -220,7 +217,7 @@ export default [
                                     })
                                     .catch((error) => {
                                         // TODO: Handle this properly / show an error message
-                                        $log.error(this.logTag, `Avatar request has been rejected: ${error}`);
+                                        log.error(`Avatar request has been rejected: ${error}`);
                                         $rootScope.$apply(() => {
                                             this.isLoading = false;
                                         });

+ 6 - 6
src/directives/avatar_area.ts

@@ -17,7 +17,8 @@
 
 // tslint:disable:max-line-length
 
-import {bufferToUrl, logAdapter} from '../helpers';
+import {bufferToUrl} from '../helpers';
+import {LogService} from '../services/log';
 import {WebClientService} from '../services/webclient';
 
 /**
@@ -25,13 +26,14 @@ import {WebClientService} from '../services/webclient';
  */
 export default [
     '$rootScope',
-    '$log',
     '$mdDialog',
+    'LogService',
     'WebClientService',
     function($rootScope: ng.IRootScopeService,
-             $log: ng.ILogService,
              $mdDialog: ng.material.IDialogService,
+             logService: LogService,
              webClientService: WebClientService) {
+        const log = logService.getLogger('AvatarArea-C');
         return {
             restrict: 'EA',
             scope: true,
@@ -43,8 +45,6 @@ export default [
             },
             controllerAs: 'ctrl',
             controller: [function() {
-                const logTag = '[AvatarAreaDirective]';
-
                 this.isLoading = false;
                 this.avatar = null; // String
                 const avatarFormat = webClientService.appCapabilities.imageFormat.avatar;
@@ -53,7 +53,7 @@ export default [
                     this.setAvatar = (avatarBytes: ArrayBuffer) => {
                         this.avatar = (avatarBytes === null)
                             ? null
-                            : bufferToUrl(avatarBytes, avatarFormat, logAdapter($log.warn, logTag));
+                            : bufferToUrl(avatarBytes, avatarFormat, log);
                     };
 
                     this.imageChanged = (image: ArrayBuffer, notify = true) => {

+ 10 - 10
src/directives/avatar_editor.ts

@@ -20,22 +20,22 @@
 
 // tslint:disable:max-line-length
 
-import {bufferToUrl, logAdapter} from '../helpers';
+import {bufferToUrl} from '../helpers';
+import {LogService} from '../services/log';
 
 /**
  * Support uploading and resizing avatar
  */
 export default [
-    '$log',
-    function($log: ng.ILogService) {
+    'LogService',
+    function(logService: LogService) {
+        const log = logService.getLogger('AvatarEditor-C');
         return {
             restrict: 'EA',
             scope: {
                 onChange: '=',
             },
             link(scope: any, element, attrs, controller) {
-                const logTag: string = '[AvatarEditorDirective]';
-
                 // Constants
                 const DRAGOVER_CSS_CLASS = 'is-dragover';
                 const VIEWPORT_SIZE = 220;
@@ -123,10 +123,10 @@ export default [
                     }
                     // get first
                     fetchFileContent(fileList[0]).then((data: ArrayBuffer) => {
-                        const image = bufferToUrl(data, 'image/jpeg', logAdapter($log.warn, logTag));
+                        const image = bufferToUrl(data, 'image/jpeg', log);
                         setImage(image);
                     }).catch((ev: ErrorEvent) => {
-                        $log.error(logTag, 'Could not load file:', ev.message);
+                        log.error('Could not load file:', ev.message);
                     });
                 }
 
@@ -177,7 +177,7 @@ export default [
                     // load image to calculate size
                     const img = new Image();
                     img.addEventListener('load', async () => {
-                        $log.debug(logTag, 'Image loaded');
+                        log.debug('Image loaded');
 
                         const w = img.naturalWidth;
                         const h = img.naturalHeight;
@@ -197,14 +197,14 @@ export default [
                                 points: imageSize,
                             });
                         } catch (error) {
-                            $log.error(logTag, 'Could not bind avatar preview:', error);
+                            log.error('Could not bind avatar preview:', error);
                         }
                         loading(false);
                     });
 
                     img.addEventListener('error', function(e) {
                         // this is not a valid image
-                        $log.error(logTag, 'Could not load image:', e);
+                        log.error('Could not load image:', e);
                         loading(false);
                     });
 

+ 10 - 11
src/directives/compose_area.ts

@@ -20,6 +20,7 @@ import {ComposeArea} from '@threema/compose-area';
 import {isActionTrigger} from '../helpers';
 import {emojifyNew, shortnameToUnicode} from '../helpers/emoji';
 import {BrowserService} from '../services/browser';
+import {LogService} from '../services/log';
 import {ReceiverService} from '../services/receiver';
 import {StringService} from '../services/string';
 import {TimeoutService} from '../services/timeout';
@@ -30,6 +31,7 @@ import {isEmojiInfo} from '../typeguards';
  */
 export default [
     'BrowserService',
+    'LogService',
     'StringService',
     'TimeoutService',
     'ReceiverService',
@@ -37,10 +39,10 @@ export default [
     '$translate',
     '$mdDialog',
     '$filter',
-    '$log',
     '$rootScope',
     'CONFIG',
     function(browserService: BrowserService,
+             logService: LogService,
              stringService: StringService,
              timeoutService: TimeoutService,
              receiverService: ReceiverService,
@@ -48,9 +50,9 @@ export default [
              $translate: ng.translate.ITranslateService,
              $mdDialog: ng.material.IDialogService,
              $filter: ng.IFilterService,
-             $log: ng.ILogService,
              $rootScope: ng.IRootScopeService,
              CONFIG: threema.Config) {
+        const log = logService.getLogger('ComposeArea-C');
         return {
             restrict: 'EA',
             scope: {
@@ -76,9 +78,6 @@ export default [
                 receiver: '<receiver',
             },
             link: function(scope: any, wrapper: JQLite) {
-                // Logging
-                const logTag = '[Directives.ComposeArea]';
-
                 // Constants
                 const TRIGGER_ENABLED_CSS_CLASS = 'is-enabled';
                 const TRIGGER_ACTIVE_CSS_CLASS = 'is-active';
@@ -111,7 +110,7 @@ export default [
                 // Function to update blocking state
                 function setChatBlocked(blocked: boolean) {
                     chatBlocked = blocked;
-                    $log.debug(logTag, 'Receiver blocked:', blocked);
+                    log.debug('Receiver blocked:', blocked);
                     if (blocked) {
                         sendTrigger.removeClass(TRIGGER_ENABLED_CSS_CLASS);
                         emojiTrigger.removeClass(TRIGGER_ENABLED_CSS_CLASS);
@@ -238,7 +237,7 @@ export default [
                             updateView();
                         }).catch(() => {
                             // do nothing
-                            $log.warn(logTag, 'Failed to submit text');
+                            log.warn('Failed to submit text');
                         });
 
                         return true;
@@ -376,11 +375,11 @@ export default [
                         });
                         scope
                             .submit('file', fileMessages)
-                            .catch((msg) => $log.error('Could not send file:', msg));
+                            .catch((e) => log.error('Could not send file:', e));
                         scope.onUploading(false);
 
                     }).catch((ev: ErrorEvent) => {
-                        $log.error(logTag, 'Could not load file:', ev.message);
+                        log.error('Could not load file:', ev.message);
                     });
                 }
 
@@ -428,7 +427,7 @@ export default [
                                 const fileExt = blob.type.split(';')[0].split('/')[1];
                                 fileName = 'clipboard.' + fileExt;
                             } else {
-                                $log.warn(logTag, 'Pasted file has an invalid MIME type: "' + blob.type + '"');
+                                log.warn('Pasted file has an invalid MIME type: "' + blob.type + '"');
                                 return;
                             }
 
@@ -441,7 +440,7 @@ export default [
                             };
                             scope
                                 .submit('file', [fileMessageData])
-                                .catch((msg) => $log.error('Could not send file:', msg));
+                                .catch((msg) => log.error('Could not send file:', msg));
                         };
                         reader.readAsArrayBuffer(blob);
 

+ 7 - 7
src/directives/drag_file.ts

@@ -15,12 +15,15 @@
  * along with Threema Web. If not, see <http://www.gnu.org/licenses/>.
  */
 
+import {LogService} from '../services/log';
+
 /**
  * Allow to drag and drop elements, set class to parent object
  */
 export default [
-    '$log',
-    function($log: ng.ILogService) {
+    'LogService',
+    function(logService: LogService) {
+        const log = logService.getLogger('DragFile-C');
         return {
             restrict: 'EA',
             scope: {
@@ -28,9 +31,6 @@ export default [
                 onUploading: '=',
             },
             link(scope: any, element) {
-                // Logging
-                const logTag = '[Directives.DragFile]';
-
                 // Constants
                 const DRAGOVER_CSS_CLASS = 'is-dragover';
 
@@ -49,7 +49,7 @@ export default [
                                 resolve(buffers);
                             }
                             if (error !== undefined) {
-                                $log.error(logTag, 'Error:', error);
+                                log.error('Error:', error);
                             }
                         };
 
@@ -91,7 +91,7 @@ export default [
                         scope.onUploading(false);
 
                     }).catch((ev: ErrorEvent) => {
-                        $log.error(logTag, 'Could not load file:', ev.message);
+                        log.error('Could not load file:', ev.message);
                     });
                 }
 

+ 6 - 9
src/directives/mediabox.ts

@@ -17,26 +17,26 @@
 
 import {saveAs} from 'file-saver';
 
-import {bufferToUrl, logAdapter} from '../helpers';
+import {bufferToUrl} from '../helpers';
+import {LogService} from '../services/log';
 import {MediaboxService} from '../services/mediabox';
 
 export default [
     '$rootScope',
     '$document',
-    '$log',
+    'LogService',
     'MediaboxService',
     function($rootScope: ng.IRootScopeService,
              $document: ng.IDocumentService,
-             $log: ng.ILogService,
+             logService: LogService,
              mediaboxService: MediaboxService) {
+        const log = logService.getLogger('Mediabox-C');
         return {
             restrict: 'E',
             scope: {},
             bindToController: {},
             controllerAs: 'ctrl',
             controller: [function() {
-                this.logTag = '[MediaboxDirective]';
-
                 // Data attributes
                 this.imageDataUrl = null;
                 this.caption = '';
@@ -62,10 +62,7 @@ export default [
                     $rootScope.$apply(() => {
                         if (dataAvailable) {
                             this.imageDataUrl = bufferToUrl(
-                                mediaboxService.data,
-                                mediaboxService.mimetype,
-                                logAdapter($log.debug, this.logTag),
-                            );
+                                mediaboxService.data, mediaboxService.mimetype, log);
                             this.caption = mediaboxService.caption || mediaboxService.filename;
                         } else {
                             this.close();

+ 5 - 3
src/directives/member_list_editor.ts

@@ -16,13 +16,15 @@
  */
 
 import {hasFeature} from '../helpers';
+import {LogService} from '../services/log';
 import {WebClientService} from '../services/webclient';
 
 const AUTOCOMPLETE_MAX_RESULTS = 20;
 
 export default [
-    '$log', 'WebClientService',
-    function($log: ng.ILogService, webClientService: WebClientService) {
+    'LogService', 'WebClientService',
+    function(logService: LogService, webClientService: WebClientService) {
+        const log = logService.getLogger('MemberListEditor-C');
         return {
             restrict: 'EA',
             scope: {},
@@ -39,7 +41,7 @@ export default [
                     .filter((contactReceiver: threema.ContactReceiver) => hasFeature(
                         contactReceiver,
                         threema.ContactReceiverFeature.GROUP_CHAT,
-                        $log,
+                        log,
                     )) as threema.ContactReceiver[];
 
                 this.selectedItemChange = (contactReceiver: threema.ContactReceiver) => {

+ 9 - 10
src/directives/message.ts

@@ -22,12 +22,14 @@ import {saveAs} from 'file-saver';
 import {BrowserInfo} from '../helpers/browser_info';
 import {getSenderIdentity} from '../helpers/messages';
 import {BrowserService} from '../services/browser';
+import {LogService} from '../services/log';
 import {MessageService} from '../services/message';
 import {ReceiverService} from '../services/receiver';
 import {WebClientService} from '../services/webclient';
 
 export default [
     'BrowserService',
+    'LogService',
     'MessageService',
     'ReceiverService',
     'WebClientService',
@@ -35,17 +37,16 @@ export default [
     '$mdToast',
     '$translate',
     '$rootScope',
-    '$log',
     function(browserService: BrowserService,
+             logService: LogService,
              messageService: MessageService,
              receiverService: ReceiverService,
              webClientService: WebClientService,
              $mdDialog: ng.material.IDialogService,
              $mdToast: ng.material.IToastService,
              $translate: ng.translate.ITranslateService,
-             $rootScope: ng.IRootScopeService,
-             $log: ng.ILogService) {
-
+             $rootScope: ng.IRootScopeService) {
+        const log = logService.getLogger('Message-C');
         return {
             restrict: 'E',
             scope: {},
@@ -57,8 +58,6 @@ export default [
             },
             controllerAs: 'ctrl',
             controller: [function() {
-                this.logTag = '[MessageDirective]';
-
                 // Determine browser
                 this.browserInfo = browserService.getBrowser();
 
@@ -147,12 +146,12 @@ export default [
                         try {
                             const successful = document.execCommand('copy');
                             if (!successful) {
-                                $log.warn(this.logTag, 'Could not copy text to clipboard');
+                                log.warn('Could not copy text to clipboard');
                             } else {
                                 toastString = 'messenger.COPIED';
                             }
                         } catch (err) {
-                            $log.warn(this.logTag, 'Could not copy text to clipboard:', err);
+                            log.warn('Could not copy text to clipboard:', err);
                         }
                         document.body.removeChild(textArea);
 
@@ -178,13 +177,13 @@ export default [
                                             saveAs(new Blob([blobInfo.buffer]), blobInfo.filename);
                                             break;
                                         default:
-                                            $log.warn(this.logTag, 'Ignored download request for message type', this.message.type);
+                                            log.warn('Ignored download request for message type', this.message.type);
                                     }
                                 });
                             })
                             .catch((error) => {
                                 // TODO: Handle this properly / show an error message
-                                $log.error(this.logTag, `Error downloading blob: ${error}`);
+                                log.error(`Error downloading blob: ${error}`);
                                 this.downloading = false;
                             });
                     };

+ 18 - 25
src/directives/message_media.ts

@@ -18,7 +18,8 @@
 import {Transition as UiTransition, TransitionService as UiTransitionService} from '@uirouter/angularjs';
 import {saveAs} from 'file-saver';
 
-import {bufferToUrl, hasValue, logAdapter} from '../helpers';
+import {bufferToUrl, hasValue} from '../helpers';
+import {LogService} from '../services/log';
 import {MediaboxService} from '../services/mediabox';
 import {MessageService} from '../services/message';
 import {TimeoutService} from '../services/timeout';
@@ -26,18 +27,15 @@ import {WebClientService} from '../services/webclient';
 
 function showAudioDialog(
     $mdDialog: ng.material.IDialogService,
-    $log: ng.ILogService,
+    logService: LogService,
     blobInfo: threema.BlobInfo,
 ): void {
+    const log = logService.getLogger('AudioPlayerDialog-C');
     $mdDialog.show({
         controllerAs: 'ctrl',
         controller: function() {
             this.cancel = () => $mdDialog.cancel();
-            this.audioSrc = bufferToUrl(
-                blobInfo.buffer,
-                blobInfo.mimetype,
-                logAdapter($log.warn, '[AudioPlayerDialog]'),
-            );
+            this.audioSrc = bufferToUrl(blobInfo.buffer, blobInfo.mimetype, log);
         },
         template: `
             <md-dialog translate-attr="{'aria-label': 'messageTypes.AUDIO_MESSAGE'}">
@@ -63,6 +61,7 @@ function showAudioDialog(
 }
 
 export default [
+    'LogService',
     'WebClientService',
     'MediaboxService',
     'MessageService',
@@ -72,10 +71,10 @@ export default [
     '$timeout',
     '$transitions',
     '$translate',
-    '$log',
     '$filter',
     '$window',
-    function(webClientService: WebClientService,
+    function(logService: LogService,
+             webClientService: WebClientService,
              mediaboxService: MediaboxService,
              messageService: MessageService,
              timeoutService: TimeoutService,
@@ -84,9 +83,9 @@ export default [
              $timeout: ng.ITimeoutService,
              $transitions: UiTransitionService,
              $translate: ng.translate.ITranslateService,
-             $log: ng.ILogService,
              $filter: ng.IFilterService,
              $window: ng.IWindowService) {
+        const log = logService.getLogger('MessageMedia-C');
         return {
             restrict: 'EA',
             scope: {},
@@ -97,8 +96,6 @@ export default [
             },
             controllerAs: 'ctrl',
             controller: [function() {
-                this.logTag = '[MessageMedia]';
-
                 // On state transitions, clear mediabox
                 $transitions.onStart({}, function(trans: UiTransition) {
                     mediaboxService.clearMedia();
@@ -130,7 +127,7 @@ export default [
                             thumbnailPreviewUri = bufferToUrl(
                                 (this.message as threema.Message).thumbnail.preview,
                                 webClientService.appCapabilities.imageFormat.thumbnail,
-                                logAdapter($log.warn, this.logTag),
+                                log,
                             );
                         }
                         return thumbnailPreviewUri;
@@ -175,7 +172,7 @@ export default [
                                     this.thumbnail = bufferToUrl(
                                         buf,
                                         webClientService.appCapabilities.imageFormat.thumbnail,
-                                        logAdapter($log.warn, this.logTag),
+                                        log,
                                     );
                                 };
 
@@ -194,7 +191,7 @@ export default [
                                             .catch((error) => {
                                                 // TODO: Handle this properly / show an error message
                                                 const message = `Thumbnail request has been rejected: ${error}`;
-                                                this.$log.error(this.logTag, message);
+                                                this.log.error(message);
                                             });
                                     }, 1000, false, 'thumbnail');
                                 }
@@ -215,13 +212,13 @@ export default [
                     };
 
                     // Play a Audio file in a dialog
-                    this.playAudio = (blobInfo: threema.BlobInfo) => showAudioDialog($mdDialog, $log, blobInfo);
+                    this.playAudio = (blobInfo: threema.BlobInfo) => showAudioDialog($mdDialog, logService, blobInfo);
 
                     // Download function
                     this.download = () => {
-                        $log.debug(this.logTag, 'Download blob');
+                        log.debug('Download blob');
                         if (this.downloading) {
-                            $log.debug(this.logTag, 'Download already in progress...');
+                            log.debug('Download already in progress...');
                             return;
                         }
                         const message: threema.Message = this.message;
@@ -230,7 +227,7 @@ export default [
                         webClientService.requestBlob(message.id, receiver)
                             .then((blobInfo: threema.BlobInfo) => {
                                 $rootScope.$apply(() => {
-                                    $log.debug(this.logTag, 'Blob loaded');
+                                    log.debug('Blob loaded');
                                     this.downloading = false;
                                     this.downloaded = true;
 
@@ -251,10 +248,7 @@ export default [
                                             if (this.message.file.type === 'image/gif') {
                                                 // Show inline
                                                 this.blobBufferUrl = bufferToUrl(
-                                                    blobInfo.buffer,
-                                                    'image/gif',
-                                                    logAdapter($log.warn, this.logTag),
-                                                );
+                                                    blobInfo.buffer, 'image/gif', log);
                                                 // Hide thumbnail
                                                 this.showThumbnail = false;
                                             } else {
@@ -266,8 +260,7 @@ export default [
                                             this.playAudio(blobInfo);
                                             break;
                                         default:
-                                            $log.warn(this.logTag,
-                                                'Ignored download request for message type', this.message.type);
+                                            log.warn('Ignored download request for message type', this.message.type);
                                     }
                                 });
                             })

+ 5 - 3
src/directives/verification_level.ts

@@ -14,10 +14,12 @@
  * You should have received a copy of the GNU Affero General Public License
  * along with Threema Web. If not, see <http://www.gnu.org/licenses/>.
  */
+import {LogService} from '../services/log';
 
 export default [
-    '$log', '$translate',
-    function($log: ng.ILogService, $translate: ng.translate.ITranslateService) {
+    '$translate', 'LogService',
+    function($translate: ng.translate.ITranslateService, logService: LogService) {
+        const log = logService.getLogger('VerificationLevel-C');
         return {
             restrict: 'EA',
             scope: {},
@@ -52,7 +54,7 @@ export default [
                     }
 
                     if (label === undefined) {
-                        $log.error('invalid verification level', this.level);
+                        log.error('invalid verification level', this.level);
                         return;
                     }
 

+ 2 - 25
src/filters.ts

@@ -17,13 +17,11 @@
 
 import Autolinker from 'autolinker';
 
-import {bufferToUrl, escapeRegExp, filter, hasValue, logAdapter} from './helpers';
-import {emojify, enlargeSingleEmoji} from './helpers/emoji';
+import {bufferToUrl, escapeRegExp, filter, hasValue} from './helpers';
+import {emojify} from './helpers/emoji';
 import {markify} from './markup_parser';
 import {MimeService} from './services/mime';
-import {NotificationService} from './services/notification';
 import {WebClientService} from './services/webclient';
-import {isContactReceiver} from './typeguards';
 
 angular.module('3ema.filters', [])
 
@@ -197,27 +195,6 @@ angular.module('3ema.filters', [])
     };
 })
 
-/**
- * Convert an ArrayBuffer to a data URL.
- *
- * Warning: Make sure that this is not called repeatedly on big data, or performance will decrease.
- */
-.filter('bufferToUrl', ['$sce', '$log', function($sce, $log) {
-    const logTag = '[filters.bufferToUrl]';
-    return function(buffer: ArrayBuffer, mimeType: string, trust: boolean = true) {
-        if (!buffer) {
-            $log.error(logTag, 'Could not apply bufferToUrl filter: buffer is', buffer);
-            return '';
-        }
-        const uri = bufferToUrl(buffer, mimeType, logAdapter($log.warn, logTag));
-        if (trust) {
-            return $sce.trustAsResourceUrl(uri);
-        } else {
-            return uri;
-        }
-    };
-}])
-
 .filter('mapLink', function() {
     return function(location: threema.LocationInfo) {
         return 'https://www.openstreetmap.org/?mlat='

+ 9 - 21
src/helpers.ts

@@ -17,6 +17,8 @@
 // tslint:disable:no-reference
 /// <reference path="threema.d.ts" />
 
+import {Logger} from 'ts-log';
+
 /**
  * Convert an Uint8Array to a hex string.
  *
@@ -282,28 +284,24 @@ export function msgpackVisualizer(array: Uint8Array): string {
  */
 export function hasFeature(contactReceiver: threema.ContactReceiver,
                            feature: threema.ContactReceiverFeature,
-                           $log: ng.ILogService): boolean {
-    const logTag = '[helpers.hasFeature]';
+                           log: Logger): boolean {
     if (contactReceiver !== undefined) {
         if (contactReceiver.featureMask === 0) {
-            $log.warn(logTag, contactReceiver.id, 'featureMask', contactReceiver.featureMask);
+            log.warn(`Contact receiver with id ${contactReceiver.id} has featureMask 0`);
             return false;
         }
         // tslint:disable:no-bitwise
         return (contactReceiver.featureMask & feature) !== 0;
         // tslint:enable:no-bitwise
     }
-    $log.warn(logTag, 'Cannot check featureMask of a undefined contactReceiver');
+    log.warn('Cannot check featureMask of a undefined contact receiver');
     return false;
 }
 
 /**
  * Convert an ArrayBuffer to a data URL.
  */
-export function bufferToUrl(buffer: ArrayBuffer, mimeType: string, logWarning: (msg: string) => void): string {
-    if (buffer === null || buffer === undefined) {
-        throw new Error('Called bufferToUrl on null or undefined');
-    }
+export function bufferToUrl(buffer: ArrayBuffer, mimeType: string, log: Logger): string {
     switch (mimeType) {
         case 'image/jpg':
         case 'image/jpeg':
@@ -317,24 +315,14 @@ export function bufferToUrl(buffer: ArrayBuffer, mimeType: string, logWarning: (
             // OK
             break;
         default:
-            logWarning('bufferToUrl: Unknown mimeType: ' + mimeType);
-            mimeType = 'image/jpeg';
+            const fallbackMimeType = 'image/jpeg';
+            log.warn(`Unknown mimeType "${mimeType}", falling back to "${fallbackMimeType}"`);
+            mimeType = fallbackMimeType;
             break;
     }
     return 'data:' + mimeType + ';base64,' + u8aToBase64(new Uint8Array(buffer));
 }
 
-/**
- * Adapter for creating a logging function.
- *
- * Example usage:
- *
- * const logWarning = logAdapter($log.warn, '[AvatarService]');
- */
-export function logAdapter(logFunc: (...msg: string[]) => void, logTag: string): ((msg: string) => void) {
-    return (msg: string) => logFunc(logTag, msg);
-}
-
 /**
  * Return whether a value is not null and not undefined.
  */

+ 69 - 74
src/partials/messenger.ts

@@ -22,15 +22,17 @@ import {
     Transition as UiTransition,
     TransitionService as UiTransitionService,
 } from '@uirouter/angularjs';
+import {Logger} from 'ts-log';
 
 import {ContactControllerModel} from '../controller_model/contact';
-import {bufferToUrl, hasValue, logAdapter, supportsPassive, throttle, u8aToHex} from '../helpers';
+import {bufferToUrl, hasValue, supportsPassive, throttle, u8aToHex} from '../helpers';
 import {emojify} from '../helpers/emoji';
 import {ContactService} from '../services/contact';
 import {ControllerService} from '../services/controller';
 import {ControllerModelService} from '../services/controller_model';
 import {FingerPrintService} from '../services/fingerprint';
 import {TrustedKeyStoreService} from '../services/keystore';
+import {LogService} from '../services/log';
 import {MimeService} from '../services/mime';
 import {NotificationService} from '../services/notification';
 import {ReceiverService} from '../services/receiver';
@@ -86,8 +88,7 @@ class DialogController {
  * Handle sending of files.
  */
 class SendFileController extends DialogController {
-    public static $inject = ['$mdDialog', '$log', 'CONFIG', 'preview'];
-    private logTag: string = '[SendFileController]';
+    public static $inject = ['$mdDialog', 'CONFIG', 'LogService', 'preview'];
 
     public caption: string;
     public sendAsFile: boolean = false;
@@ -95,17 +96,14 @@ class SendFileController extends DialogController {
     public previewDataUrl: string | null = null;
 
     constructor($mdDialog: ng.material.IDialogService,
-                $log: ng.ILogService,
                 CONFIG: threema.Config,
+                logService: LogService,
                 preview: threema.FileMessageData) {
         super($mdDialog, CONFIG);
+        const log = logService.getLogger('SendFile-C');
         this.preview = preview;
         if (preview !== null) {
-            this.previewDataUrl = bufferToUrl(
-                this.preview.data,
-                this.preview.fileType,
-                logAdapter($log.warn, this.logTag),
-            );
+            this.previewDataUrl = bufferToUrl(this.preview.data, this.preview.fileType, log);
         }
     }
 
@@ -274,12 +272,10 @@ interface ConversationStateParams extends UiStateParams {
 
 class ConversationController {
     public name = 'navigation';
-    private logTag: string = '[ConversationController]';
 
     // Angular services
     private $stateParams;
     private $state: UiStateService;
-    private $log: ng.ILogService;
     private $scope: ng.IScope;
     private $rootScope: ng.IRootScopeService;
     private $filter: ng.IFilterService;
@@ -296,6 +292,9 @@ class ConversationController {
     private $mdDialog: ng.material.IDialogService;
     private $mdToast: ng.material.IToastService;
 
+    // Logging
+    private readonly log: Logger;
+
     // Controller model
     private controllerModel: threema.ControllerModel<threema.Receiver>;
 
@@ -343,14 +342,13 @@ class ConversationController {
     };
 
     public static $inject = [
-        '$stateParams', '$log', '$scope', '$rootScope',
+        '$stateParams', '$scope', '$rootScope',
         '$mdDialog', '$mdToast', '$translate', '$filter',
         '$state', '$transitions',
-        'WebClientService', 'StateService', 'ReceiverService', 'MimeService', 'VersionService',
-        'ControllerModelService', 'TimeoutService',
+        'LogService', 'WebClientService', 'StateService', 'ReceiverService', 'MimeService',
+        'VersionService', 'ControllerModelService', 'TimeoutService',
     ];
     constructor($stateParams: ConversationStateParams,
-                $log: ng.ILogService,
                 $scope: ng.IScope,
                 $rootScope: ng.IRootScopeService,
                 $mdDialog: ng.material.IDialogService,
@@ -359,6 +357,7 @@ class ConversationController {
                 $filter: ng.IFilterService,
                 $state: UiStateService,
                 $transitions: UiTransitionService,
+                logService: LogService,
                 webClientService: WebClientService,
                 stateService: StateService,
                 receiverService: ReceiverService,
@@ -367,13 +366,14 @@ class ConversationController {
                 controllerModelService: ControllerModelService,
                 timeoutService: TimeoutService) {
         this.$stateParams = $stateParams;
-        this.$log = $log;
         this.webClientService = webClientService;
         this.receiverService = receiverService;
         this.stateService = stateService;
         this.mimeService = mimeService;
         this.timeoutService = timeoutService;
 
+        this.log = logService.getLogger('Conversation-C');
+
         this.$state = $state;
         this.$scope = $scope;
         this.$filter = $filter;
@@ -400,7 +400,7 @@ class ConversationController {
 
         // Redirect to welcome if necessary
         if (stateService.state === 'error') {
-            $log.debug('ConversationController: WebClient not yet running, redirecting to welcome screen');
+            this.log.debug('WebClient not yet running, redirecting to welcome screen');
             $state.go('welcome');
             return;
         }
@@ -447,7 +447,7 @@ class ConversationController {
                         this.receiver as threema.DistributionListReceiver, mode);
                     break;
                 default:
-                    $log.error(this.logTag, 'Cannot initialize controller model:',
+                    this.log.error('Cannot initialize controller model:',
                         'Invalid receiver type "' + this.receiver.type + '"');
                     $state.go('messenger.home');
                     return;
@@ -455,7 +455,7 @@ class ConversationController {
 
             // Check if this receiver may be chatted with
             if (this.controllerModel.canChat() === false) {
-                $log.warn(this.logTag, 'Cannot chat with this receiver, redirecting to home');
+                this.log.warn('Cannot chat with this receiver, redirecting to home');
                 $state.go('messenger.home');
                 return;
             }
@@ -536,8 +536,7 @@ class ConversationController {
                 }
             }
         } catch (error) {
-            $log.error('Could not set receiver and type');
-            $log.debug(error.stack);
+            this.log.error('Could not set receiver and type');
             $state.go('messenger.home');
         }
 
@@ -547,7 +546,7 @@ class ConversationController {
         }, () => {
             if (this.locked !== this.receiver.locked) {
                 $state.reload().catch((error) => {
-                    this.$log.error('Unable to reload state:', error);
+                    this.log.error('Unable to reload state:', error);
                 });
             }
         });
@@ -707,7 +706,7 @@ class ConversationController {
                                     nextCallback(index);
                                 })
                                 .catch((error) => {
-                                    this.$log.error(error);
+                                    this.log.error(error);
                                     // TODO: Should probably be an alert instead of a toast
                                     this.showError(error);
                                     success = false;
@@ -731,7 +730,7 @@ class ConversationController {
                                 nextCallback(index);
                             })
                             .catch((error) => {
-                                this.$log.error(error);
+                                this.log.error(error);
                                 // TODO: Should probably be an alert instead of a toast
                                 this.showError(error);
                                 success = false;
@@ -740,7 +739,7 @@ class ConversationController {
                     });
                     return;
                 default:
-                    this.$log.warn('Invalid message type:', type);
+                    this.log.warn('Invalid message type:', type);
                     reject();
             }
         });
@@ -934,15 +933,15 @@ class ConversationController {
      */
     public pinConversation(): void {
         if (!hasValue(this.conversation)) {
-            this.$log.warn(this.logTag, 'Cannot pin, no conversation exists');
+            this.log.warn('Cannot pin, no conversation exists');
             return;
         }
         this.webClientService
             .modifyConversation(this.conversation, true)
             .then(() => this.showMessage('messenger.PINNED_CONVERSATION_OK'))
-            .catch((msg) => {
+            .catch((e) => {
                 this.showMessage('messenger.PINNED_CONVERSATION_ERROR');
-                this.$log.error(this.logTag, 'Pinning conversation failed: ' + msg);
+                this.log.error('Pinning conversation failed: ' + e);
             });
     }
 
@@ -951,15 +950,15 @@ class ConversationController {
      */
     public unpinConversation(): void {
         if (!hasValue(this.conversation)) {
-            this.$log.warn(this.logTag, 'Cannot unpin, no conversation exists');
+            this.log.warn('Cannot unpin, no conversation exists');
             return;
         }
         this.webClientService
             .modifyConversation(this.conversation, false)
             .then(() => this.showMessage('messenger.UNPINNED_CONVERSATION_OK'))
-            .catch((msg) => {
+            .catch((e) => {
                 this.showMessage('messenger.UNPINNED_CONVERSATION_ERROR');
-                this.$log.error(this.logTag, 'Unpinning conversation failed: ' + msg);
+                this.log.error('Unpinning conversation failed: ' + e);
             });
     }
 }
@@ -983,19 +982,20 @@ class NavigationController {
     private $state: UiStateService;
 
     public static $inject = [
-        '$log', '$state', '$mdDialog', '$translate',
-        'WebClientService', 'StateService', 'ReceiverService', 'NotificationService', 'TrustedKeyStore',
+        '$state', '$mdDialog', '$translate',
+        'LogService', 'WebClientService', 'StateService', 'ReceiverService', 'NotificationService', 'TrustedKeyStore',
     ];
 
-    constructor($log: ng.ILogService, $state: UiStateService,
-                $mdDialog: ng.material.IDialogService, $translate: ng.translate.ITranslateService,
-                webClientService: WebClientService, stateService: StateService,
+    constructor($state: UiStateService, $mdDialog: ng.material.IDialogService,
+                $translate: ng.translate.ITranslateService,
+                logService: LogService, webClientService: WebClientService, stateService: StateService,
                 receiverService: ReceiverService, notificationService: NotificationService,
                 trustedKeyStoreService: TrustedKeyStoreService) {
+        const log = logService.getLogger('Navigation-C');
 
         // Redirect to welcome if necessary
         if (stateService.state === 'error') {
-            $log.debug('NavigationController: WebClient not yet running, redirecting to welcome screen');
+            log.debug('WebClient not yet running, redirecting to welcome screen');
             $state.go('welcome');
             return;
         }
@@ -1236,24 +1236,23 @@ class NavigationController {
 }
 
 class MessengerController {
-    private logTag: string = '[MessengerController]';
-
     public name = 'messenger';
     private receiverService: ReceiverService;
     private $state;
     private webClientService: WebClientService;
 
     public static $inject = [
-        '$scope', '$state', '$log', '$mdDialog', '$translate',
-        'StateService', 'ReceiverService', 'WebClientService', 'ControllerService',
+        '$scope', '$state', '$mdDialog', '$translate',
+        'LogService', 'StateService', 'ReceiverService', 'WebClientService', 'ControllerService',
     ];
-    constructor($scope, $state, $log: ng.ILogService, $mdDialog: ng.material.IDialogService,
-                $translate: ng.translate.ITranslateService,
-                stateService: StateService, receiverService: ReceiverService,
+    constructor($scope, $state, $mdDialog: ng.material.IDialogService, $translate: ng.translate.ITranslateService,
+                logService: LogService, stateService: StateService, receiverService: ReceiverService,
                 webClientService: WebClientService, controllerService: ControllerService) {
+        const log = logService.getLogger('Messenger-C');
+
         // Redirect to welcome if necessary
         if (stateService.state === 'error') {
-            $log.debug(this.logTag, 'MessengerController: WebClient not yet running, redirecting to welcome screen');
+            log.debug('WebClient not yet running, redirecting to welcome screen');
             $state.go('welcome');
             return;
         }
@@ -1280,7 +1279,6 @@ class MessengerController {
             }
         }, true);
 
-        const logTag = this.logTag;
         this.webClientService.setReceiverListener({
             onConversationRemoved(receiver: threema.Receiver) {
                 switch ($state.current.name) {
@@ -1298,7 +1296,7 @@ class MessengerController {
                         }
                         break;
                     default:
-                        $log.debug(logTag, 'Ignored onRemoved event for state', $state.current.name);
+                        log.debug('Ignored onRemoved event for state', $state.current.name);
                 }
             },
         });
@@ -1310,8 +1308,6 @@ class MessengerController {
 }
 
 class ReceiverDetailController {
-    private logTag: string = '[ReceiverDetailController]';
-
     // Angular services
     private $mdDialog: any;
     private $scope: ng.IScope;
@@ -1338,14 +1334,13 @@ class ReceiverDetailController {
     private controllerModel: threema.ControllerModel<threema.Receiver>;
 
     public static $inject = [
-        '$scope', '$log', '$stateParams', '$state', '$mdDialog', '$translate',
-        'WebClientService', 'FingerPrintService', 'ContactService', 'ControllerModelService',
+        '$scope', '$stateParams', '$state', '$mdDialog', '$translate',
+        'LogService', 'WebClientService', 'FingerPrintService', 'ContactService', 'ControllerModelService',
     ];
-    constructor($scope: ng.IScope, $log: ng.ILogService, $stateParams, $state: UiStateService,
+    constructor($scope: ng.IScope, $stateParams, $state: UiStateService,
                 $mdDialog: ng.material.IDialogService, $translate: ng.translate.ITranslateService,
-                webClientService: WebClientService, fingerPrintService: FingerPrintService,
+                logService: LogService, webClientService: WebClientService, fingerPrintService: FingerPrintService,
                 contactService: ContactService, controllerModelService: ControllerModelService) {
-
         this.$mdDialog = $mdDialog;
         this.$scope = $scope;
         this.$state = $state;
@@ -1356,6 +1351,8 @@ class ReceiverDetailController {
         this.receiver = webClientService.receivers.getData($stateParams);
         this.me = webClientService.me;
 
+        const log = logService.getLogger('ReceiverDetail-C');
+
         // Append group membership
         if (isContactReceiver(this.receiver)) {
             const contactReceiver = this.receiver;
@@ -1369,7 +1366,7 @@ class ReceiverDetailController {
                 })
                 .catch((error) => {
                     // TODO: Redirect or show an alert?
-                    $log.error(this.logTag, `Contact detail request has been rejected: ${error}`);
+                    log.error(`Contact detail request has been rejected: ${error}`);
                 });
 
             this.isWorkReceiver = contactReceiver.identityType === threema.IdentityType.Work;
@@ -1424,7 +1421,7 @@ class ReceiverDetailController {
                     .distributionList(this.receiver as threema.DistributionListReceiver, ControllerModelMode.VIEW);
                 break;
             default:
-                $log.error(this.logTag, 'Cannot initialize controller model:',
+                log.error('Cannot initialize controller model:',
                     'Invalid receiver type "' + this.receiver.type + '"');
                 $state.go('messenger.home');
                 return;
@@ -1432,7 +1429,7 @@ class ReceiverDetailController {
 
         // If this receiver was removed, navigate to "home" view
         this.controllerModel.setOnRemoved((receiverId: string) => {
-            $log.warn(this.logTag, 'Receiver removed, redirecting to home');
+            log.warn('Receiver removed, redirecting to home');
             this.$state.go('messenger.home');
         });
 
@@ -1508,8 +1505,6 @@ class ReceiverDetailController {
  * fields, validate and save routines are implemented in the specific ControllerModel
  */
 class ReceiverEditController {
-    private logTag: string = '[ReceiverEditController]';
-
     public $mdDialog: any;
     private $scope: ng.IScope;
     public $state: UiStateService;
@@ -1523,19 +1518,21 @@ class ReceiverEditController {
     public type: string;
 
     public static $inject = [
-        '$log', '$scope', '$stateParams', '$state', '$mdDialog',
-        '$timeout', '$translate', 'WebClientService', 'ControllerModelService',
+        '$scope', '$stateParams', '$state', '$mdDialog',
+        '$timeout', '$translate', 'LogService', 'WebClientService', 'ControllerModelService',
     ];
-    constructor($log: ng.ILogService, $scope: ng.IScope, $stateParams, $state: UiStateService,
+    constructor($scope: ng.IScope, $stateParams, $state: UiStateService,
                 $mdDialog, $timeout: ng.ITimeoutService, $translate: ng.translate.ITranslateService,
-                webClientService: WebClientService, controllerModelService: ControllerModelService) {
-
+                logService: LogService, webClientService: WebClientService,
+                controllerModelService: ControllerModelService) {
         this.$scope = $scope;
         this.$mdDialog = $mdDialog;
         this.$state = $state;
         this.$timeout = $timeout;
         this.$translate = $translate;
 
+        const log = logService.getLogger('ReceiverEdit-C');
+
         const receiver = webClientService.receivers.getData($stateParams);
         switch (receiver.type) {
             case 'me':
@@ -1563,7 +1560,7 @@ class ReceiverEditController {
                 );
                 break;
             default:
-                $log.error(this.logTag, 'Cannot initialize controller model:',
+                log.error('Cannot initialize controller model:',
                     'Invalid receiver type "' + receiver.type + '"');
                 $state.go('messenger.home');
                 return;
@@ -1624,12 +1621,9 @@ interface CreateReceiverStateParams extends UiStateParams {
  * fields, validate and save routines are implemented in the specific ControllerModel
  */
 class ReceiverCreateController {
-    private logTag: string = '[ReceiverEditController]';
-
     public $mdDialog: any;
     private $scope: ng.IScope;
     private $timeout: ng.ITimeoutService;
-    private $log: ng.ILogService;
     private $state: UiStateService;
     private $mdToast: any;
     public identity = '';
@@ -1640,22 +1634,23 @@ class ReceiverCreateController {
     public controllerModel: threema.ControllerModel<threema.Receiver>;
 
     public static $inject = ['$stateParams', '$mdDialog', '$scope', '$mdToast', '$translate',
-        '$timeout', '$state', '$log', 'ControllerModelService'];
+        '$timeout', '$state', 'LogService', 'ControllerModelService'];
     constructor($stateParams: CreateReceiverStateParams, $mdDialog, $scope: ng.IScope, $mdToast, $translate,
-                $timeout: ng.ITimeoutService, $state: UiStateService, $log: ng.ILogService,
-                controllerModelService: ControllerModelService) {
+                $timeout: ng.ITimeoutService, $state: UiStateService,
+                logService: LogService, controllerModelService: ControllerModelService) {
         this.$mdDialog = $mdDialog;
         this.$scope = $scope;
         this.$timeout = $timeout;
         this.$state = $state;
-        this.$log = $log;
         this.$mdToast = $mdToast;
         this.$translate = $translate;
 
+        const log = logService.getLogger('ReceiverEdit-C');
+
         this.type = $stateParams.type;
         switch (this.type) {
             case 'me':
-                $log.warn(this.logTag, 'Cannot create own contact');
+                log.warn('Cannot create own contact');
                 $state.go('messenger.home');
                 return;
             case 'contact':
@@ -1672,7 +1667,7 @@ class ReceiverCreateController {
                 this.controllerModel = controllerModelService.distributionList(null, ControllerModelMode.NEW);
                 break;
             default:
-                this.$log.error('invalid type', this.type);
+                log.error('Invalid type', this.type);
         }
     }
 

+ 31 - 29
src/partials/welcome.ts

@@ -19,6 +19,8 @@
 
 /// <reference path="../types/broadcastchannel.d.ts" />
 
+import {Logger} from 'ts-log';
+
 import {
     StateProvider as UiStateProvider,
     StateService as UiStateService,
@@ -28,6 +30,7 @@ import {BrowserInfo} from '../helpers/browser_info';
 import {BrowserService} from '../services/browser';
 import {ControllerService} from '../services/controller';
 import {TrustedKeyStoreService} from '../services/keystore';
+import {LogService} from '../services/log';
 import {PushService} from '../services/push';
 import {SettingsService} from '../services/settings';
 import {StateService} from '../services/state';
@@ -55,14 +58,10 @@ class DialogController {
 }
 
 class WelcomeController {
-
     private static REDIRECT_DELAY = 500;
 
-    private logTag: string = '[WelcomeController]';
-
     // Angular services
     private $scope: ng.IScope;
-    private $log: ng.ILogService;
     private $window: ng.IWindowService;
     private $state: UiStateService;
 
@@ -79,6 +78,9 @@ class WelcomeController {
     private timeoutService: TimeoutService;
     private config: threema.Config;
 
+    // Logging
+    private readonly log: Logger;
+
     // Other
     public name = 'welcome';
     private mode: 'scan' | 'unlock';
@@ -90,14 +92,15 @@ class WelcomeController {
     private browserWarningShown: boolean = false;
 
     public static $inject = [
-        '$scope', '$state', '$log', '$window', '$mdDialog', '$translate',
-        'WebClientService', 'TrustedKeyStore', 'StateService', 'PushService', 'BrowserService',
+        '$scope', '$state', '$window', '$mdDialog', '$translate',
+        'LogService', 'WebClientService', 'TrustedKeyStore', 'StateService', 'PushService', 'BrowserService',
         'VersionService', 'SettingsService', 'TimeoutService', 'ControllerService',
         'BROWSER_MIN_VERSIONS', 'CONFIG',
     ];
     constructor($scope: ng.IScope, $state: UiStateService,
-                $log: ng.ILogService, $window: ng.IWindowService, $mdDialog: ng.material.IDialogService,
+                $window: ng.IWindowService, $mdDialog: ng.material.IDialogService,
                 $translate: ng.translate.ITranslateService,
+                logService: LogService,
                 webClientService: WebClientService, trustedKeyStore: TrustedKeyStoreService,
                 stateService: StateService, pushService: PushService,
                 browserService: BrowserService,
@@ -111,7 +114,6 @@ class WelcomeController {
         // Angular services
         this.$scope = $scope;
         this.$state = $state;
-        this.$log = $log;
         this.$window = $window;
         this.$mdDialog = $mdDialog;
         this.$translate = $translate;
@@ -125,37 +127,40 @@ class WelcomeController {
         this.timeoutService = timeoutService;
         this.config = config;
 
+        // Logging
+        this.log = logService.getLogger('Welcome-C');
+
         // TODO: Allow to trigger below behaviour by using state parameters
 
         // Determine whether browser warning should be shown
         this.browser = browserService.getBrowser();
         const version = this.browser.version;
-        $log.debug('Detected browser:', this.browser.description());
+        this.log.debug('Detected browser:', this.browser.description());
         if (!this.browser.wasDetermined()) {
-            $log.warn('Could not determine browser version');
+            this.log.warn('Could not determine browser version');
             this.showBrowserWarning();
         } else if (this.browser.name === threema.BrowserName.Chrome) {
             if (version < minVersions.CHROME) {
-                $log.warn('Chrome is too old (' + version + ' < ' + minVersions.CHROME + ')');
+                this.log.warn('Chrome is too old (' + version + ' < ' + minVersions.CHROME + ')');
                 this.showBrowserWarning();
             }
         } else if (this.browser.name === threema.BrowserName.Firefox) {
             if (version < minVersions.FF) {
-                $log.warn('Firefox is too old (' + version + ' < ' + minVersions.FF + ')');
+                this.log.warn('Firefox is too old (' + version + ' < ' + minVersions.FF + ')');
                 this.showBrowserWarning();
             }
         } else if (this.browser.name === threema.BrowserName.Opera) {
             if (version < minVersions.OPERA) {
-                $log.warn('Opera is too old (' + version + ' < ' + minVersions.OPERA + ')');
+                this.log.warn('Opera is too old (' + version + ' < ' + minVersions.OPERA + ')');
                 this.showBrowserWarning();
             }
         } else if (this.browser.name === threema.BrowserName.Safari) {
             if (version < minVersions.SAFARI) {
-                $log.warn('Safari is too old (' + version + ' < ' + minVersions.SAFARI + ')');
+                this.log.warn('Safari is too old (' + version + ' < ' + minVersions.SAFARI + ')');
                 this.showBrowserWarning();
             }
         } else {
-            $log.warn('Non-supported browser, please use Chrome, Firefox or Opera');
+            this.log.warn('Non-supported browser, please use Chrome, Firefox or Opera');
             this.showBrowserWarning();
         }
 
@@ -165,7 +170,7 @@ class WelcomeController {
 
         // Determine whether local storage is available
         if (this.trustedKeyStore.blocked === true) {
-            $log.error('Cannot access local storage. Is it being blocked by a browser add-on?');
+            this.log.error('Cannot access local storage. Is it being blocked by a browser add-on?');
             this.showLocalStorageWarning();
         }
 
@@ -190,7 +195,7 @@ class WelcomeController {
         try {
             hasTrustedKey = this.trustedKeyStore.hasTrustedKey();
         } catch (e) {
-            $log.error('Exception while accessing local storage:', e);
+            this.log.error('Exception while accessing local storage:', e);
             this.showLocalStorageException(e);
         }
 
@@ -257,7 +262,7 @@ class WelcomeController {
      * Initiate a new session by scanning a new QR code.
      */
     private scan(stopArguments?: threema.WebClientServiceStopArguments): void {
-        this.$log.info(this.logTag, 'Initialize session by scanning QR code...');
+        this.log.info('Initialize session by scanning QR code...');
 
         // Initialize webclient with new keystore
         this.webClientService.stop(stopArguments !== undefined ? stopArguments : {
@@ -288,7 +293,7 @@ class WelcomeController {
      */
     private unlock(): void {
         this.stateService.reset('new');
-        this.$log.info(this.logTag, 'Initialize session by unlocking trusted key...');
+        this.log.info('Initialize session by unlocking trusted key...');
     }
 
     /**
@@ -324,7 +329,7 @@ class WelcomeController {
     private setupBroadcastChannel(publicKeyHex: string) {
         if (!('BroadcastChannel' in this.$window)) {
             // No BroadcastChannel support in this browser
-            this.$log.warn(this.logTag, 'BroadcastChannel not supported in this browser');
+            this.log.warn('BroadcastChannel not supported in this browser');
             return;
         }
 
@@ -347,10 +352,7 @@ class WelcomeController {
                             && (this.stateService.connectionBuildupState === 'loading'
                              || this.stateService.connectionBuildupState === 'done')) {
                         // Yes it is, notify them that the session is already active
-                        this.$log.debug(
-                            this.logTag,
-                            'Another tab is trying to connect to our session. Respond with a broadcast.',
-                        );
+                        this.log.debug('Another tab is trying to connect to our session. Respond with a broadcast.');
                         channel.postMessage(JSON.stringify({
                             type: TYPE_ALREADY_OPEN,
                             key: publicKeyHex,
@@ -361,7 +363,7 @@ class WelcomeController {
                     // Another tab notified us that the session we're trying to connect to
                     // is already active.
                     if (message.key === publicKeyHex && this.stateService.connectionBuildupState !== 'done') {
-                        this.$log.error(this.logTag, 'Session already connected in another tab or window');
+                        this.log.error('Session already connected in another tab or window');
                         this.timeoutService.register(() => {
                             this.stateService.updateConnectionBuildupState('already_connected');
                             this.stateService.state = GlobalConnectionState.Error;
@@ -369,13 +371,13 @@ class WelcomeController {
                     }
                     break;
                 default:
-                    this.$log.warn(this.logTag, 'Unknown broadcast message type:', message.type);
+                    this.log.warn('Unknown broadcast message type:', message.type);
                     break;
             }
         };
 
         // Notify other tabs that we're trying to connect
-        this.$log.debug(this.logTag, 'Checking if the session is already open in another tab or window');
+        this.log.debug('Checking if the session is already open in another tab or window');
         channel.postMessage(JSON.stringify({
             type: TYPE_PUBLIC_KEY,
             key: publicKeyHex,
@@ -543,7 +545,7 @@ class WelcomeController {
         } else if (len <= 586) {
             version = 16;
         } else {
-            this.$log.error(this.logTag, 'QR Code payload too large: Is your SaltyRTC host string huge?');
+            this.log.error('QR Code payload too large: Is your SaltyRTC host string huge?');
             version = 40;
         }
         return {
@@ -581,7 +583,7 @@ class WelcomeController {
 
             // If an error occurs...
             (error) => {
-                this.$log.error(this.logTag, 'Error state:', error);
+                this.log.error('Error state:', error);
                 // Note: On rejection, the web client service will already
                 //       redirect to 'welcome' and show a protocol error.
             },

+ 12 - 2
src/services/battery.ts

@@ -15,6 +15,9 @@
  * along with Threema Web. If not, see <http://www.gnu.org/licenses/>.
  */
 
+import {Logger} from 'ts-log';
+
+import {LogService} from './log';
 import {NotificationService} from './notification';
 
 export class BatteryStatusService {
@@ -31,17 +34,24 @@ export class BatteryStatusService {
     private $translate: ng.translate.ITranslateService;
     private notificationService: NotificationService;
 
-    public static $inject = ['$translate', 'NotificationService'];
+    // Logging
+    private readonly log: Logger;
+
+    public static $inject = ['$translate', 'LogService', 'NotificationService'];
 
-    constructor($translate: ng.translate.ITranslateService, notificationService: NotificationService) {
+    constructor($translate: ng.translate.ITranslateService,
+                logService: LogService, notificationService: NotificationService) {
         this.$translate = $translate;
         this.notificationService = notificationService;
+        this.log = logService.getLogger('BatteryStatus-S');
     }
 
     /**
      * Update the battery status.
      */
     public setStatus(batteryStatus: threema.BatteryStatus): void {
+        this.log.debug('Status:', batteryStatus);
+
         // Handle null percent value. This can happen if the battery status could not be determined.
         if (batteryStatus.percent === null) {
             this.clearStatus();

+ 8 - 10
src/services/browser.ts

@@ -15,23 +15,23 @@
  * along with Threema Web. If not, see <http://www.gnu.org/licenses/>.
  */
 
+import {Logger} from 'ts-log';
+
 import {BrowserInfo} from '../helpers/browser_info';
+import {LogService} from './log';
 
 import BrowserName = threema.BrowserName;
 
 export class BrowserService {
-    private logTag: string = '[BrowserService]';
-
     private browser: BrowserInfo;
-    private $log: ng.ILogService;
     private $window: ng.IWindowService;
+    private readonly log: Logger;
     private supportsExtendedLocaleCompareCache: boolean;
 
-    public static $inject = ['$log', '$window'];
-    constructor($log: ng.ILogService, $window: ng.IWindowService) {
-        // Angular services
-        this.$log = $log;
+    public static $inject = ['$window', 'LogService'];
+    constructor($window: ng.IWindowService, logService: LogService) {
         this.$window = $window;
+        this.log = logService.getLogger('Browser-S');
     }
 
     public getBrowser(): BrowserInfo {
@@ -158,9 +158,7 @@ export class BrowserService {
 
         const support = getSupport();
         this.supportsExtendedLocaleCompareCache = support;
-        this.$log.debug(this.logTag, 'Browser',
-            support ? 'supports' : 'does not support',
-            'extended locale compare options');
+        this.log.debug(`Browser ${support ? 'supports' : 'does not support'} extended locale compare options`);
         return support;
     }
 }

+ 0 - 7
src/services/controller.ts

@@ -20,13 +20,6 @@
  */
 export class ControllerService {
     private currentController: string;
-    private $log: ng.ILogService;
-    public static $inject = ['$log'];
-
-    constructor($log: ng.ILogService) {
-        // Angular services
-        this.$log = $log;
-    }
 
     public setControllerName(name: string): void {
         this.currentController = name;

+ 17 - 12
src/services/controller_model.ts

@@ -15,10 +15,13 @@
  * along with Threema Web. If not, see <http://www.gnu.org/licenses/>.
  */
 
+import {Logger} from 'ts-log';
+
 import {ContactControllerModel} from '../controller_model/contact';
 import {DistributionListControllerModel} from '../controller_model/distributionList';
 import {GroupControllerModel} from '../controller_model/group';
 import {MeControllerModel} from '../controller_model/me';
+import {LogService} from './log';
 import {WebClientService} from './webclient';
 
 // Type aliases
@@ -28,18 +31,20 @@ import ControllerModelMode = threema.ControllerModelMode;
  * Factory to create ControllerModels
  */
 export class ControllerModelService {
-    private $log: ng.ILogService;
-    private $translate: ng.translate.ITranslateService;
-    private $mdDialog: ng.material.IDialogService;
-    private webClientService: WebClientService;
+    private readonly $translate: ng.translate.ITranslateService;
+    private readonly $mdDialog: ng.material.IDialogService;
+    private readonly logService: LogService;
+    private readonly webClientService: WebClientService;
+    private readonly log: Logger;
 
-    public static $inject = ['$log', '$translate', '$mdDialog', 'WebClientService'];
-    constructor($log: ng.ILogService, $translate: ng.translate.ITranslateService,
-                $mdDialog: ng.material.IDialogService, webClientService: WebClientService) {
-        this.$log = $log;
+    public static $inject = ['$translate', '$mdDialog', 'LogService', 'WebClientService'];
+    constructor($translate: ng.translate.ITranslateService, $mdDialog: ng.material.IDialogService,
+                logService: LogService, webClientService: WebClientService) {
         this.$translate = $translate;
         this.$mdDialog = $mdDialog;
+        this.logService = logService;
         this.webClientService = webClientService;
+        this.log = logService.getLogger('ControllerModel-S');
     }
 
     public me(
@@ -47,9 +52,9 @@ export class ControllerModelService {
         mode: ControllerModelMode,
     ): threema.ControllerModel<threema.MeReceiver> {
         return new MeControllerModel(
-            this.$log,
             this.$translate,
             this.$mdDialog,
+            this.logService,
             this.webClientService,
             mode,
             receiver,
@@ -61,9 +66,9 @@ export class ControllerModelService {
         mode: ControllerModelMode,
     ): threema.ControllerModel<threema.ContactReceiver> {
         return new ContactControllerModel(
-            this.$log,
             this.$translate,
             this.$mdDialog,
+            this.logService,
             this.webClientService,
             mode,
             receiver,
@@ -75,9 +80,9 @@ export class ControllerModelService {
         mode: ControllerModelMode,
     ): threema.ControllerModel<threema.GroupReceiver> {
         return new GroupControllerModel(
-            this.$log,
             this.$translate,
             this.$mdDialog,
+            this.logService,
             this.webClientService,
             mode,
             receiver,
@@ -89,9 +94,9 @@ export class ControllerModelService {
         mode: ControllerModelMode,
     ): threema.ControllerModel<threema.DistributionListReceiver> {
         return new DistributionListControllerModel(
-            this.$log,
             this.$translate,
             this.$mdDialog,
+            this.logService,
             this.webClientService,
             mode,
             receiver,

+ 0 - 7
src/services/fingerprint.ts

@@ -18,13 +18,6 @@
 import {sha256} from '../helpers/crypto';
 
 export class FingerPrintService {
-    private $log: ng.ILogService;
-
-    public static $inject = ['$log'];
-    constructor($log: ng.ILogService) {
-        this.$log = $log;
-    }
-
     public async generate(publicKey: ArrayBuffer): Promise<string> {
         if (publicKey !== undefined && publicKey.byteLength === 32) {
             const sha256PublicKey = await sha256(publicKey);

+ 11 - 11
src/services/keystore.ts

@@ -15,9 +15,12 @@
  * along with Threema Web. If not, see <http://www.gnu.org/licenses/>.
  */
 
+import {Logger} from 'ts-log';
+
 import * as nacl from 'tweetnacl';
 import {hexToU8a, u8aToHex} from '../helpers';
 import {stringToUtf8a, utf8aToString} from '../helpers';
+import {LogService} from './log';
 
 /**
  * This service stores trusted keys in the local browser storage.
@@ -41,24 +44,21 @@ import {stringToUtf8a, utf8aToString} from '../helpers';
 export class TrustedKeyStoreService {
     private STORAGE_KEY = 'trusted';
 
-    private logTag: string = '[TrustedKeyStoreService]';
-
-    private $log: ng.ILogService;
+    private readonly log: Logger;
     private storage: Storage = null;
 
     public blocked = false;
 
-    public static $inject = ['$log', '$window'];
-    constructor($log: ng.ILogService, $window: ng.IWindowService) {
-        this.$log = $log;
-
+    public static $inject = ['$window', 'LogService'];
+    constructor($window: ng.IWindowService, logService: LogService) {
+        this.log = logService.getLogger('TrustedKeyStore-S', 'color: #fff; background-color: #666699');
         try {
             if ($window.localStorage === null) {
                 this.blocked = true;
             }
             this.storage = $window.localStorage;
         } catch (e) {
-            $log.warn(this.logTag, 'LocalStorage blocked:', e);
+            this.log.warn('LocalStorage blocked:', e);
             this.blocked = true;
         }
     }
@@ -119,7 +119,7 @@ export class TrustedKeyStoreService {
         data.set(peerPublicKey, 64);
         data.set(token, 96);
         const encrypted: Uint8Array = nacl.secretbox(data, nonce, this.pwToKey(password));
-        this.$log.debug(this.logTag, 'Storing trusted key');
+        this.log.debug('Storing trusted key');
         this.storage.setItem(this.STORAGE_KEY, u8aToHex(nonce) + ':' + u8aToHex(encrypted));
     }
 
@@ -167,7 +167,7 @@ export class TrustedKeyStoreService {
                     tokenType = threema.PushTokenType.Apns;
                     break;
                 default:
-                    this.$log.error(this.logTag, 'Invalid push token type:', tokenString[0]);
+                    this.log.error('Invalid push token type:', tokenString[0]);
                     return null;
             }
             token = tokenString.slice(2);
@@ -190,7 +190,7 @@ export class TrustedKeyStoreService {
      * Delete any stored trusted keys.
      */
     public clearTrustedKey(): void {
-        this.$log.debug(this.logTag, 'Clearing trusted key');
+        this.log.debug('Clearing trusted key');
         this.storage.removeItem(this.STORAGE_KEY);
     }
 }

+ 9 - 9
src/services/mediabox.ts

@@ -16,15 +16,15 @@
  */
 
 import {AsyncEvent} from 'ts-events';
+import {Logger} from 'ts-log';
+
+import {LogService} from './log';
 
 /**
  * This service is responsible for showing / hiding the media box.
  */
 export class MediaboxService {
-
-    private logTag: string = '[MediaboxService]';
-
-    private $log: ng.ILogService;
+    private readonly log: Logger;
 
     /**
      * This event is triggered every time the media element changes.
@@ -41,16 +41,16 @@ export class MediaboxService {
     public filename: string = '';
     public mimetype: string = '';
 
-    public static $inject = ['$log'];
-    constructor($log: ng.ILogService) {
-        this.$log = $log;
+    public static $inject = ['LogService'];
+    constructor(logService: LogService) {
+        this.log = logService.getLogger('Mediabox-S');
     }
 
     /**
      * Update media data.
      */
     public setMedia(data: ArrayBuffer, filename: string, mimetype: string, caption: string) {
-        this.$log.debug(this.logTag, 'Media data updated');
+        this.log.debug('Media data updated');
         this.data = data;
         this.filename = filename;
         this.mimetype = mimetype;
@@ -62,7 +62,7 @@ export class MediaboxService {
      * Clear media data.
      */
     public clearMedia() {
-        this.$log.debug(this.logTag, 'Media data cleared');
+        this.log.debug('Media data cleared');
         this.data = null;
         this.filename = '';
         this.mimetype = '';

+ 2 - 7
src/services/message.ts

@@ -29,10 +29,6 @@ export class MessageAccess {
 }
 
 export class MessageService {
-
-    // Angular services
-    private $log: ng.ILogService;
-
     // Own services
     private receiverService: ReceiverService;
     private timeoutService: TimeoutService;
@@ -40,9 +36,8 @@ export class MessageService {
     // Other
     private timeoutDelaySeconds = 30;
 
-    public static $inject = ['$log', 'ReceiverService', 'TimeoutService'];
-    constructor($log: ng.ILogService, receiverService: ReceiverService, timeoutService: TimeoutService) {
-        this.$log = $log;
+    public static $inject = ['ReceiverService', 'TimeoutService'];
+    constructor(receiverService: ReceiverService, timeoutService: TimeoutService) {
         this.receiverService = receiverService;
         this.timeoutService = timeoutService;
     }

+ 2 - 4
src/services/mime.ts

@@ -18,9 +18,8 @@
 import {hasValue} from '../helpers';
 
 export class MimeService {
-    public static $inject = ['$log', '$translate'];
+    public static $inject = ['$translate'];
 
-    private $log: ng.ILogService;
     private $translate: ng.translate.ITranslateService;
 
     private imageMimeTypes: string[] = ['image/png', 'image/jpg', 'image/jpeg'];
@@ -28,8 +27,7 @@ export class MimeService {
     private audioMimeTypesIos: string[] = ['audio/m4a', 'audio/x-m4a', 'audio/mp4'];
     private videoMimeTypes: string[] = ['video/mp4', 'video/mpg', 'video/mpeg'];
 
-    constructor($log: ng.ILogService, $translate: ng.translate.ITranslateService) {
-        this.$log = $log;
+    constructor($translate: ng.translate.ITranslateService) {
         this.$translate = $translate;
     }
 

+ 27 - 24
src/services/notification.ts

@@ -15,8 +15,11 @@
  * along with Threema Web. If not, see <http://www.gnu.org/licenses/>.
  */
 
+import {Logger} from 'ts-log';
+
 import {StateService as UiStateService} from '@uirouter/angularjs';
 
+import {LogService} from './log';
 import {SettingsService} from './settings';
 
 export class NotificationService {
@@ -26,12 +29,12 @@ export class NotificationService {
     private static SETTINGS_NOTIFICATION_SOUND = 'notificationSound';
     private static NOTIFICATION_SOUND = 'sounds/notification.mp3';
 
-    private $log: ng.ILogService;
     private $window: ng.IWindowService;
     private $state: UiStateService;
 
     private settingsService: SettingsService;
-    private logTag = '[NotificationService]';
+
+    private readonly log: Logger;
 
     // Whether user has granted notification permission
     private notificationPermission: boolean = null;
@@ -47,14 +50,14 @@ export class NotificationService {
     // Cache notifications
     private notificationCache: any = {};
 
-    public static $inject = ['$log', '$window', '$state', 'SettingsService'];
+    public static $inject = ['$window', '$state', 'LogService', 'SettingsService'];
 
-    constructor($log: ng.ILogService, $window: ng.IWindowService,
-                $state: UiStateService, settingsService: SettingsService) {
-        this.$log = $log;
+    constructor($window: ng.IWindowService, $state: UiStateService,
+                logService: LogService, settingsService: SettingsService) {
         this.$window = $window;
         this.$state = $state;
         this.settingsService = settingsService;
+        this.log = logService.getLogger('Notification-S');
     }
 
     public init(): void {
@@ -77,7 +80,7 @@ export class NotificationService {
     private requestNotificationPermission(): void {
         if (this.notificationAPIAvailable) {
             const Notification = this.$window.Notification;
-            this.$log.debug(this.logTag, 'Requesting notification permission...');
+            this.log.debug('Requesting notification permission...');
             Notification.requestPermission((result) => {
                 switch (result) {
                     case 'denied':
@@ -96,7 +99,7 @@ export class NotificationService {
                         this.notificationPermission = false;
                         break;
                 }
-                this.$log.debug(this.logTag, 'Notification permission', this.notificationPermission);
+                this.log.debug('Notification permission', this.notificationPermission);
             });
         }
     }
@@ -109,7 +112,7 @@ export class NotificationService {
      */
     private checkNotificationAPI(): void {
         this.notificationAPIAvailable = ('Notification' in this.$window);
-        this.$log.debug(this.logTag, 'Notification API available:', this.notificationAPIAvailable);
+        this.log.debug('Notification API available:', this.notificationAPIAvailable);
         if (this.notificationAPIAvailable) {
             const Notification = this.$window.Notification;
             switch (Notification.permission) {
@@ -128,47 +131,47 @@ export class NotificationService {
                     break;
             }
         }
-        this.$log.debug(this.logTag, 'Initial notificationPermission', this.notificationPermission);
+        this.log.debug('Initial notificationPermission', this.notificationPermission);
     }
 
     /**
      * Get the initial settings from local storage
      */
     private fetchSettings(): void {
-        this.$log.debug(this.logTag, 'Fetching settings...');
+        this.log.debug('Fetching settings...');
         const notifications = this.retrieveSetting(NotificationService.SETTINGS_NOTIFICATIONS);
         const preview = this.retrieveSetting(NotificationService.SETTINGS_NOTIFICATION_PREVIEW);
         const sound = this.retrieveSetting(NotificationService.SETTINGS_NOTIFICATION_SOUND);
         if (notifications === 'true') {
-            this.$log.debug(this.logTag, 'Desktop notifications:', notifications);
+            this.log.debug('Desktop notifications:', notifications);
             this.desktopNotifications = true;
             // check permission because user may have revoked them
             this.requestNotificationPermission();
         } else if (notifications === 'false') {
-            this.$log.debug(this.logTag, 'Desktop notifications:', notifications);
+            this.log.debug('Desktop notifications:', notifications);
             // user does not want notifications
             this.desktopNotifications = false;
         } else {
-            this.$log.debug(this.logTag, 'Desktop notifications:', notifications, 'Asking user...');
+            this.log.debug('Desktop notifications:', notifications, 'Asking user...');
             // Neither true nor false was in local storage, so we have to ask the user if he wants notifications
             // If he grants (or already has granted) us the permission, we will set the flag true (default setting)
             this.requestNotificationPermission();
         }
         if (preview === 'false') {
-            this.$log.debug(this.logTag, 'Notification preview:', preview);
+            this.log.debug('Notification preview:', preview);
             this.notificationPreview = false;
         } else {
             // set the flag true if true/nothing or sth. else is in local storage (default setting)
-            this.$log.debug(this.logTag, 'Notification preview:', preview, 'Using default value (true)');
+            this.log.debug('Notification preview:', preview, 'Using default value (true)');
             this.notificationPreview = true;
             this.storeSetting(NotificationService.SETTINGS_NOTIFICATION_PREVIEW, 'true');
         }
         if (sound === 'true') {
-            this.$log.debug(this.logTag, 'Notification sound:', sound);
+            this.log.debug('Notification sound:', sound);
             this.notificationSound = true;
         } else {
             // set the flag false if false/nothing or sth. else is in local storage (default setting)
-            this.$log.debug(this.logTag, 'Notification sound:', sound, 'Using default value (false)');
+            this.log.debug('Notification sound:', sound, 'Using default value (false)');
             this.notificationSound = false;
             this.storeSetting(NotificationService.SETTINGS_NOTIFICATION_SOUND, 'false');
         }
@@ -213,7 +216,7 @@ export class NotificationService {
      * Sets if the user wants desktop notifications
      */
     public setWantsNotifications(wantsNotifications: boolean): void {
-        this.$log.debug(this.logTag, 'Requesting notification preference change to', wantsNotifications);
+        this.log.debug('Requesting notification preference change to', wantsNotifications);
         if (wantsNotifications) {
             this.requestNotificationPermission();
         } else {
@@ -226,7 +229,7 @@ export class NotificationService {
      * Sets if the user wants a message preview
      */
     public setWantsPreview(wantsPreview: boolean): void {
-        this.$log.debug(this.logTag, 'Requesting preview preference change to', wantsPreview);
+        this.log.debug('Requesting preview preference change to', wantsPreview);
         this.notificationPreview = wantsPreview;
         this.storeSetting(NotificationService.SETTINGS_NOTIFICATION_PREVIEW, wantsPreview ? 'true' : 'false');
     }
@@ -235,7 +238,7 @@ export class NotificationService {
      * Sets if the user wants sound when a new message arrives
      */
     public setWantsSound(wantsSound: boolean): void {
-        this.$log.debug(this.logTag, 'Requesting sound preference change to', wantsSound);
+        this.log.debug('Requesting sound preference change to', wantsSound);
         this.notificationSound = wantsSound;
         this.storeSetting(NotificationService.SETTINGS_NOTIFICATION_SOUND, wantsSound ? 'true' : 'false');
     }
@@ -365,7 +368,7 @@ export class NotificationService {
         }
 
         // Show notification
-        this.$log.debug(this.logTag, 'Showing notification', tag);
+        this.log.debug('Showing notification', tag);
         const notification = new this.$window.Notification(title, {
             icon: avatar,
             body: body.trim(),
@@ -380,7 +383,7 @@ export class NotificationService {
             if (clickCallback !== undefined) {
                 clickCallback();
             }
-            this.$log.debug(this.logTag, 'Hiding notification', tag, 'on click');
+            this.log.debug('Hiding notification', tag, 'on click');
             notification.close();
             this.clearCache(tag);
         };
@@ -399,7 +402,7 @@ export class NotificationService {
     public hideNotification(tag: string): boolean {
         const notification = this.notificationCache[tag];
         if (notification !== undefined) {
-            this.$log.debug(this.logTag, 'Hiding notification', tag);
+            this.log.debug('Hiding notification', tag);
             notification.close();
             this.clearCache(tag);
             return true;

+ 24 - 25
src/services/peerconnection.ts

@@ -18,15 +18,15 @@
 import * as SDPUtils from 'sdp';
 
 import TaskConnectionState = threema.TaskConnectionState;
+import {Logger} from 'ts-log';
+import {LogService} from './log';
 
 /**
  * Wrapper around the WebRTC PeerConnection.
  */
 export class PeerConnectionHelper {
-    private logTag: string = '[PeerConnectionHelper]';
-
     // Angular services
-    private $log: ng.ILogService;
+    private log: Logger;
     private $q: ng.IQService;
     private $timeout: ng.ITimeoutService;
     private $rootScope: ng.IRootScopeService;
@@ -42,14 +42,13 @@ export class PeerConnectionHelper {
     // Debugging
     private censorCandidates: boolean;
 
-    constructor($log: ng.ILogService, $q: ng.IQService,
-                $timeout: ng.ITimeoutService, $rootScope: ng.IRootScopeService,
-                webrtcTask: saltyrtc.tasks.webrtc.WebRTCTask,
+    constructor($q: ng.IQService, $timeout: ng.ITimeoutService, $rootScope: ng.IRootScopeService,
+                logService: LogService, webrtcTask: saltyrtc.tasks.webrtc.WebRTCTask,
                 iceServers: RTCIceServer[],
                 censorCandidates: boolean = true) {
-        this.$log = $log;
-        this.$log.info(this.logTag, 'Initialize WebRTC PeerConnection');
-        this.$log.debug(this.logTag, 'ICE servers used:', [].concat(...iceServers.map((c) => c.urls)).join(', '));
+        this.log = logService.getLogger('PeerConnection', 'color: #fff; background-color: #3333ff');
+        this.log.info('Initialize WebRTC PeerConnection');
+        this.log.debug('ICE servers used:', [].concat(...iceServers.map((c) => c.urls)));
         this.$q = $q;
         this.$timeout = $timeout;
         this.$rootScope = $rootScope;
@@ -61,18 +60,18 @@ export class PeerConnectionHelper {
         // Set up peer connection
         this.pc = new RTCPeerConnection({iceServers: iceServers});
         this.pc.onnegotiationneeded = (e: Event) => {
-            this.$log.debug(this.logTag, 'RTCPeerConnection: negotiation needed');
+            this.log.debug('RTCPeerConnection: negotiation needed');
             this.initiatorFlow().then(
-                (_) => this.$log.debug(this.logTag, 'Initiator flow done'),
+                (_) => this.log.debug('Initiator flow done'),
             );
         };
 
         // Handle state changes
         this.pc.onconnectionstatechange = (e: Event) => {
-            $log.debug(this.logTag, 'Connection state change:', this.pc.connectionState);
+            this.log.debug('Connection state change:', this.pc.connectionState);
         };
         this.pc.onsignalingstatechange = (e: Event) => {
-            $log.debug(this.logTag, 'Signaling state change:', this.pc.signalingState);
+            this.log.debug('Signaling state change:', this.pc.signalingState);
         };
 
         // Set up ICE candidate handling
@@ -80,7 +79,7 @@ export class PeerConnectionHelper {
 
         // Log incoming data channels
         this.pc.ondatachannel = (e: RTCDataChannelEvent) => {
-            $log.debug(this.logTag, 'New data channel was created:', e.channel.label);
+            this.log.debug('New data channel was created:', e.channel.label);
         };
     }
 
@@ -95,10 +94,10 @@ export class PeerConnectionHelper {
      * Set up receiving / sending of ICE candidates.
      */
     private setupIceCandidateHandling() {
-        this.$log.debug(this.logTag, 'Setting up ICE candidate handling');
+        this.log.debug('Setting up ICE candidate handling');
         this.pc.onicecandidate = (e: RTCPeerConnectionIceEvent) => {
             if (e.candidate) {
-                this.$log.debug(this.logTag, 'Gathered local ICE candidate:',
+                this.log.debug('Gathered local ICE candidate:',
                     this.censorCandidate(e.candidate.candidate));
                 this.webrtcTask.sendCandidate({
                     candidate: e.candidate.candidate,
@@ -106,14 +105,14 @@ export class PeerConnectionHelper {
                     sdpMLineIndex: e.candidate.sdpMLineIndex,
                 });
             } else {
-                this.$log.debug(this.logTag, 'No more local ICE candidates');
+                this.log.debug('No more local ICE candidates');
             }
         };
         this.pc.onicecandidateerror = (e: RTCPeerConnectionIceErrorEvent) => {
-            this.$log.error(this.logTag, 'ICE candidate error:', e);
+            this.log.error('ICE candidate error:', e);
         };
         this.pc.oniceconnectionstatechange = (e: Event) => {
-            this.$log.debug(this.logTag, 'ICE connection state change:', this.pc.iceConnectionState);
+            this.log.debug('ICE connection state change:', this.pc.iceConnectionState);
             this.$rootScope.$apply(() => {
                 switch (this.pc.iceConnectionState) {
                     case 'new':
@@ -132,21 +131,21 @@ export class PeerConnectionHelper {
                         this.setConnectionState(TaskConnectionState.Disconnected);
                         break;
                     default:
-                        this.$log.warn(this.logTag, 'Ignored ICE connection state change to',
+                        this.log.warn('Ignored ICE connection state change to',
                                        this.pc.iceConnectionState);
                 }
             });
         };
         this.pc.onicegatheringstatechange = (e: Event) => {
-            this.$log.debug(this.logTag, 'ICE gathering state change:', this.pc.iceGatheringState);
+            this.log.debug('ICE gathering state change:', this.pc.iceGatheringState);
         };
         this.webrtcTask.on('candidates', (e: saltyrtc.tasks.webrtc.CandidatesEvent) => {
             for (const candidateInit of e.data) {
                 if (candidateInit) {
-                    this.$log.debug(this.logTag, 'Adding remote ICE candidate:',
+                    this.log.debug('Adding remote ICE candidate:',
                         this.censorCandidate(candidateInit.candidate));
                 } else {
-                    this.$log.debug(this.logTag, 'No more remote ICE candidates');
+                    this.log.debug('No more remote ICE candidates');
                 }
                 this.pc.addIceCandidate(candidateInit);
             }
@@ -157,7 +156,7 @@ export class PeerConnectionHelper {
         // Send offer
         const offer: RTCSessionDescriptionInit = await this.pc.createOffer();
         await this.pc.setLocalDescription(offer);
-        this.$log.debug(this.logTag, 'Created offer, set local description');
+        this.log.debug('Created offer, set local description');
         this.webrtcTask.sendOffer(offer);
 
         // Receive answer
@@ -170,7 +169,7 @@ export class PeerConnectionHelper {
         };
         const answer: RTCSessionDescriptionInit = await receiveAnswer();
         await this.pc.setRemoteDescription(answer);
-        this.$log.debug(this.logTag, 'Received answer, set remote description');
+        this.log.debug('Received answer, set remote description');
     }
 
     /**

+ 22 - 20
src/services/push.ts

@@ -15,9 +15,12 @@
  * along with Threema Web. If not, see <http://www.gnu.org/licenses/>.
  */
 
+import {Logger} from 'ts-log';
+
 import {TimeoutError} from '../exceptions';
 import {randomString, sleep} from '../helpers';
 import {sha256} from '../helpers/crypto';
+import {LogService} from './log';
 
 /**
  * A push session will send pushes continuously until an undefined goal has
@@ -44,13 +47,12 @@ import {sha256} from '../helpers/crypto';
  * stored on the push server.
  */
 export class PushSession {
-    private readonly $log: ng.ILogService;
     private readonly service: PushService;
     private readonly session: Uint8Array;
     private readonly config: threema.PushSessionConfig;
     private readonly doneFuture: Future<any> = new Future();
     private readonly affiliation: string = randomString(6);
-    private logTag: string = '[Push]';
+    private log: Logger;
     private running: boolean = false;
     private retryTimeoutMs: number;
     private tries: number = 0;
@@ -99,7 +101,7 @@ export class PushSession {
      * @param config Push session configuration.
      */
     public constructor(service: PushService, session: Uint8Array, config?: threema.PushSessionConfig) {
-        this.$log = service.$log;
+        this.log = service.logService.getLogger(`Push.${this.affiliation}`, 'color: #fff; background-color: #9900cc');
         this.service = service;
         this.session = session;
         this.config = config !== undefined ? config : PushSession.defaultConfig;
@@ -129,7 +131,7 @@ export class PushSession {
         // Start sending
         if (!this.running) {
             this.run().catch((error) => {
-                this.$log.error(this.logTag, 'Push runner failed:', error);
+                this.log.error('Push runner failed:', error);
                 this.doneFuture.reject(error);
             });
             this.running = true;
@@ -143,14 +145,13 @@ export class PushSession {
      * This will resolve all pending promises.
      */
     public done(): void {
-        this.$log.info(this.logTag, 'Push done');
+        this.log.info('Push done');
         this.doneFuture.resolve();
     }
 
     private async run(): Promise<void> {
         // Calculate session hash
         const sessionHash = await sha256(this.session.buffer);
-        this.logTag = `[Push.${sessionHash}]`;
 
         // Prepare data
         const data = new URLSearchParams();
@@ -193,9 +194,9 @@ export class PushSession {
             ++this.tries;
 
             // Send push
-            this.$log.debug(this.logTag, `Sending push ${this.tries}/${this.config.triesMax} (ttl=${timeToLive})`);
-            if (this.service.config.DEBUG_ARP) {
-                this.$log.debug(this.logTag, 'Push data:', `${data}`);
+            this.log.debug(`Sending push ${this.tries}/${this.config.triesMax} (ttl=${timeToLive})`);
+            if (this.service.config.ARP_LOG_TRACE) {
+                this.log.debug('Push data:', `${data}`);
             }
             try {
                 const response = await fetch(this.service.url, {
@@ -209,18 +210,18 @@ export class PushSession {
                 // Check if successful
                 if (response.ok) {
                     // Success: Retry
-                    this.$log.debug(this.logTag, 'Push sent successfully');
+                    this.log.debug('Push sent successfully');
                 } else if (response.status >= 400 && response.status < 500) {
                     // Client error: Don't retry
                     const error = `Push rejected (client error), status: ${response.status}`;
-                    this.$log.warn(this.logTag, error);
+                    this.log.warn(error);
                     this.doneFuture.reject(new Error(error));
                 } else {
                     // Server error: Retry
-                    this.$log.warn(this.logTag, `Push rejected (server error), status: ${response.status}`);
+                    this.log.warn(`Push rejected (server error), status: ${response.status}`);
                 }
             } catch (error) {
-                this.$log.warn(this.logTag, 'Sending push failed:', error);
+                this.log.warn('Sending push failed:', error);
             }
 
             // Retry after timeout
@@ -232,7 +233,7 @@ export class PushSession {
             // Maximum tries reached?
             if (!this.doneFuture.done && this.tries === this.config.triesMax) {
                 const error = `Push session timeout after ${this.tries} tries`;
-                this.$log.warn(this.logTag, error);
+                this.log.warn(error);
                 this.doneFuture.reject(new TimeoutError(error));
             }
         }
@@ -240,7 +241,7 @@ export class PushSession {
 }
 
 export class PushService {
-    public static readonly $inject = ['$log', 'CONFIG', 'PROTOCOL_VERSION'];
+    public static readonly $inject = ['CONFIG', 'PROTOCOL_VERSION', 'LogService'];
 
     public static readonly ARG_TYPE = 'type';
     public static readonly ARG_TOKEN = 'token';
@@ -252,19 +253,20 @@ export class PushService {
     public static readonly ARG_TIME_TO_LIVE = 'ttl';
     public static readonly ARG_COLLAPSE_KEY = 'collapse_key';
 
-    private readonly logTag: string = '[PushService]';
-    public readonly $log: ng.ILogService;
     public readonly config: threema.Config;
     public readonly url: string;
     public readonly version: number = null;
+    public readonly logService: LogService;
+    public readonly log: Logger;
     private _pushToken: string = null;
     private _pushType = threema.PushTokenType.Gcm;
 
-    constructor($log: ng.ILogService, CONFIG: threema.Config, PROTOCOL_VERSION: number) {
-        this.$log = $log;
+    constructor(CONFIG: threema.Config, PROTOCOL_VERSION: number, logService: LogService) {
         this.config = CONFIG;
         this.url = CONFIG.PUSH_URL;
         this.version = PROTOCOL_VERSION;
+        this.logService = logService;
+        this.log = logService.getLogger(`Push-S`, 'color: #fff; background-color: #9900ff');
     }
 
     public get pushToken(): string {
@@ -279,7 +281,7 @@ export class PushService {
      * Initiate the push service with a push token.
      */
     public init(pushToken: string, pushTokenType: threema.PushTokenType): void {
-        this.$log.info(this.logTag, 'Initialized with', pushTokenType, 'token');
+        this.log.info('Initialized with', pushTokenType, 'token');
         this._pushToken = pushToken;
         this._pushType = pushTokenType;
     }

+ 11 - 10
src/services/settings.ts

@@ -15,21 +15,22 @@
  * along with Threema Web. If not, see <http://www.gnu.org/licenses/>.
  */
 
+import {Logger} from 'ts-log';
+
+import {LogService} from './log';
+
 /**
  * The settings service can update variables for settings and persist them to
  * LocalStorage.
  */
 export class SettingsService {
-    private $log: ng.ILogService;
-
     private static STORAGE_KEY_PREFIX = 'settings-';
-    private logTag: string = '[SettingsService]';
-
+    private readonly log: Logger;
     private storage: Storage;
 
-    public static $inject = ['$log', '$window'];
-    constructor($log: ng.ILogService, $window: ng.IWindowService) {
-        this.$log = $log;
+    public static $inject = ['$window', 'LogService'];
+    constructor($window: ng.IWindowService, logService: LogService) {
+        this.log = logService.getLogger('Settings-S');
         this.storage = $window.localStorage;
     }
 
@@ -37,7 +38,7 @@ export class SettingsService {
      * Store settings key-value pair in LocalStorage.
      */
     public storeUntrustedKeyValuePair(key: string, value: string): void {
-        this.$log.debug(this.logTag, 'Storing settings key:', key);
+        this.log.debug('Storing settings key:', key);
         this.storage.setItem(SettingsService.STORAGE_KEY_PREFIX + key, value);
     }
 
@@ -48,7 +49,7 @@ export class SettingsService {
      * with an empty value if it does not yet exist.
      */
     public retrieveUntrustedKeyValuePair(key: string, alwaysCreate: boolean = true): string {
-        this.$log.debug(this.logTag, 'Retrieving settings key:', key);
+        this.log.debug('Retrieving settings key:', key);
         if (this.hasUntrustedKeyValuePair(key)) {
             return this.storage.getItem(SettingsService.STORAGE_KEY_PREFIX + key);
         } else {
@@ -63,7 +64,7 @@ export class SettingsService {
      * Remove settings key-value pair from LocalStorage if it exists.
      */
     public removeUntrustedKeyValuePair(key: string): void {
-        this.$log.debug(this.logTag, 'Removing settings key:', key);
+        this.log.debug('Removing settings key:', key);
         this.storage.removeItem(SettingsService.STORAGE_KEY_PREFIX + key);
     }
 

+ 17 - 15
src/services/state.ts

@@ -16,6 +16,9 @@
  */
 
 import {AsyncEvent} from 'ts-events';
+import {Logger} from 'ts-log';
+
+import {LogService} from './log';
 
 import TaskConnectionState = threema.TaskConnectionState;
 import GlobalConnectionState = threema.GlobalConnectionState;
@@ -27,13 +30,12 @@ const enum Stage {
 }
 
 export class StateService {
-
-    private logTag: string = '[StateService]';
-
     // Angular services
-    private $log: ng.ILogService;
     private $interval: ng.IIntervalService;
 
+    // Logging
+    private readonly log: Logger;
+
     // Events
     public evtConnectionBuildupStateChange = new AsyncEvent<threema.ConnectionBuildupStateChange>();
     public evtGlobalConnectionStateChange = new AsyncEvent<threema.GlobalConnectionStateChange>();
@@ -57,10 +59,10 @@ export class StateService {
     // Unread messages
     private _unreadCount: number = 0;
 
-    public static $inject = ['$log', '$interval'];
-    constructor($log: ng.ILogService, $interval: ng.IIntervalService) {
-        this.$log = $log;
+    public static $inject = ['$interval', 'LogService'];
+    constructor($interval: ng.IIntervalService, logService: LogService) {
         this.$interval = $interval;
+        this.log = logService.getLogger('State-S', 'color: #fff; background-color: #cc9900');
         this.reset();
     }
 
@@ -89,7 +91,7 @@ export class StateService {
         const prevState = this.signalingConnectionState;
         this.signalingConnectionState = state;
         if (!handoverDone) {
-            this.$log.debug(this.logTag, 'Signaling connection state:', prevState, '=>', state);
+            this.log.debug('Signaling connection state:', prevState, '=>', state);
             switch (state) {
                 case 'new':
                 case 'ws-connecting':
@@ -110,10 +112,10 @@ export class StateService {
                     this.state = GlobalConnectionState.Error;
                     break;
                 default:
-                    this.$log.warn(this.logTag, `Unknown signaling connection state: ${state}`);
+                    this.log.warn(`Unknown signaling connection state: ${state}`);
             }
         } else {
-            this.$log.debug(this.logTag, 'Ignored signaling connection state to "' + state + '"');
+            this.log.debug('Ignored signaling connection state to "' + state + '"');
         }
     }
 
@@ -124,7 +126,7 @@ export class StateService {
         const prevState = this.taskConnectionState;
         this.taskConnectionState = state;
         if (this.stage === Stage.Task) {
-            this.$log.debug(this.logTag, 'Task connection state:', prevState, '=>', state);
+            this.log.debug('Task connection state:', prevState, '=>', state);
             switch (state) {
                 case TaskConnectionState.New:
                 case TaskConnectionState.Connecting:
@@ -139,10 +141,10 @@ export class StateService {
                     this.state = GlobalConnectionState.Error;
                     break;
                 default:
-                    this.$log.warn(this.logTag, 'Ignored task connection state change to "' + state + '"');
+                    this.log.warn('Ignored task connection state change to "' + state + '"');
             }
         } else {
-            this.$log.debug(this.logTag, 'Ignored task connection state change to "' + state + '"');
+            this.log.debug('Ignored task connection state change to "' + state + '"');
         }
     }
 
@@ -154,7 +156,7 @@ export class StateService {
             return;
         }
         const prevState = this.connectionBuildupState;
-        this.$log.debug(this.logTag, 'Connection buildup state:', prevState, '=>', state);
+        this.log.debug('Connection buildup state:', prevState, '=>', state);
 
         // Update state
         this.connectionBuildupState = state;
@@ -265,7 +267,7 @@ export class StateService {
      * Reset all states.
      */
     public reset(connectionBuildupState: threema.ConnectionBuildupState = 'new'): void {
-        this.$log.debug(this.logTag, 'Reset states');
+        this.log.debug('Reset states');
 
         // Reset state
         this.signalingConnectionState = 'new';

+ 15 - 20
src/services/timeout.ts

@@ -15,46 +15,42 @@
  * along with Threema Web. If not, see <http://www.gnu.org/licenses/>.
  */
 
-export class TimeoutService {
-    private logTag: string = '[TimeoutService]';
+import {Logger} from 'ts-log';
+
+import {LogService} from './log';
 
+export class TimeoutService {
     // Config
     private config: threema.Config;
 
     // Angular services
-    private $log: ng.ILogService;
     private $timeout: ng.ITimeoutService;
 
+    // Logging
+    private readonly log: Logger;
+
     // List of registered timeouts
     private timeouts: Set<ng.IPromise<any>> = new Set();
 
-    public static $inject = ['CONFIG', '$log', '$timeout'];
-    constructor(config: threema.Config, $log: ng.ILogService, $timeout: ng.ITimeoutService) {
+    public static $inject = ['CONFIG', '$timeout', 'LogService'];
+    constructor(config: threema.Config, $timeout: ng.ITimeoutService, logService: LogService) {
         this.config = config;
-        this.$log = $log;
         this.$timeout = $timeout;
-    }
-
-    /**
-     * Log a message on debug log level, but only if the `DEBUG` flag is enabled.
-     */
-    private logDebug(msg: string): void {
-        if (this.config.DEBUG_TIMER) {
-            this.$log.debug(this.logTag, msg);
-        }
+        this.log = logService.getLogger(
+            'Timeout-S', 'color: #fff; background-color: #669900', this.config.TIMER_LOG_LEVEL);
     }
 
     /**
      * Register a timeout.
      */
     public register<T>(fn: (...args: any[]) => T, delay: number, invokeApply: boolean, name?: string): ng.IPromise<T> {
-        this.logDebug('Registering timeout' + (name === undefined ? '' : ` (${name})`));
+        this.log.debug(`Registering timeout${name === undefined ? '' : ` (${name})`}`);
         const timeout = this.$timeout(fn, delay, invokeApply);
         timeout
             .then(() => this.timeouts.delete(timeout))
             .catch((reason) => {
                 if (reason !== 'canceled') { // We can safely ignore cancellation
-                    this.$log.error(this.logTag, 'Registered timeout promise rejected:', reason);
+                    this.log.error('Registered timeout promise rejected:', reason);
                 }
             });
 
@@ -75,8 +71,7 @@ export class TimeoutService {
         // Retrieve name from promise for debugging purposes
         // tslint:disable-next-line: no-string-literal
         const name = timeout['_timeout_name'];
-
-        this.logDebug('Cancelling timeout' + (name === undefined ? '' : ` (${name})`));
+        this.log.debug(`Cancelling timeout${name === undefined ? '' : ` (${name})`}`);
         const cancelled = this.$timeout.cancel(timeout);
 
         this.timeouts.delete(timeout);
@@ -87,7 +82,7 @@ export class TimeoutService {
      * Cancel all pending timeouts.
      */
     public cancelAll() {
-        this.$log.debug(this.logTag, 'Cancelling ' + this.timeouts.size + ' timeouts');
+        this.log.debug('Cancelling ' + this.timeouts.size + ' timeouts');
         for (const timeout of this.timeouts) {
             this.$timeout.cancel(timeout);
         }

+ 3 - 6
src/services/title.ts

@@ -14,14 +14,13 @@
  * You should have received a copy of the GNU Affero General Public License
  * along with Threema Web. If not, see <http://www.gnu.org/licenses/>.
  */
+
 import {StateService} from './state';
 
 /**
  * The title service can update the window title.
  */
 export class TitleService {
-
-    private $log: ng.ILogService;
     private $document: ng.IDocumentService;
 
     private stateService: StateService;
@@ -30,9 +29,8 @@ export class TitleService {
     private title: string;
     private unreadCount: number = 0;
 
-    public static $inject = ['$log', '$document', 'StateService'];
-    constructor($log: ng.ILogService, $document: ng.IDocumentService, stateService: StateService) {
-        this.$log = $log;
+    public static $inject = ['$document', 'StateService'];
+    constructor($document: ng.IDocumentService, stateService: StateService) {
         this.$document = $document;
         this.stateService = stateService;
 
@@ -55,5 +53,4 @@ export class TitleService {
         }
         this.$document[0].title = this.title;
     }
-
 }

+ 19 - 17
src/services/version.ts

@@ -15,32 +15,36 @@
  * along with Threema Web. If not, see <http://www.gnu.org/licenses/>.
  */
 
-export class VersionService {
-    private logTag: string = '[VersionService]';
+import {Logger} from 'ts-log';
+
+import {LogService} from './log';
 
-    private $log: ng.ILogService;
+export class VersionService {
     private $http: ng.IHttpService;
     private $mdDialog: ng.material.IDialogService;
     private $translate: ng.translate.ITranslateService;
     private $window: ng.IWindowService;
 
+    private readonly config: threema.Config;
+    private readonly log: Logger;
+
     private version: string;
-    private config: threema.Config;
     private dialogShowing = false;
 
-    public static $inject = ['$log', '$http', '$mdDialog', '$translate', '$window', 'CONFIG'];
-    constructor($log: ng.ILogService,
-                $http: ng.IHttpService,
+    public static $inject = ['$http', '$mdDialog', '$translate', '$window', 'CONFIG', 'LogService'];
+    constructor($http: ng.IHttpService,
                 $mdDialog: ng.material.IDialogService,
                 $translate: ng.translate.ITranslateService,
                 $window: ng.IWindowService,
-                CONFIG: threema.Config) {
-        this.$log = $log;
+                CONFIG: threema.Config,
+                logService: LogService) {
         this.$http = $http;
         this.$mdDialog = $mdDialog;
         this.$translate = $translate;
         this.$window = $window;
+
         this.config = CONFIG;
+        this.log = logService.getLogger('Version-S');
     }
 
     /**
@@ -55,10 +59,10 @@ export class VersionService {
         this.fetchVersion()
             .then((version: string) => {
                 this.version = version;
-                this.$log.info(this.logTag, 'Using Threema Web version', this.version);
+                this.log.info('Using Threema Web version', this.version);
             })
             .catch((error: string) => {
-                this.$log.error(this.logTag, 'Could not fetch version.txt:', error);
+                this.log.error('Could not fetch version.txt:', error);
             });
     }
 
@@ -94,22 +98,20 @@ export class VersionService {
      * Check for a version update. If the version was updated, show a dialog.
      */
     public checkForUpdate(): void {
-        this.$log.debug(this.logTag, 'Checking for version update...');
+        this.log.debug('Checking for version update...');
         if (this.version === undefined) {
-            this.$log.error(this.logTag, 'Cannot check for update, version is not initialized');
+            this.log.error('Cannot check for update, version is not initialized');
             return;
         }
         this.fetchVersion()
             .then((version: string) => {
                 if (version !== this.version) {
-                    this.$log.warn(this.logTag,
-                        'A new version of Threema Web is available:',
-                        this.version, '->', version);
+                    this.log.warn('A new version of Threema Web is available:', this.version, '->', version);
                     this.notifyNewVersion(version);
                 }
             })
             .catch((error: string) => {
-                this.$log.error('Could not fetch version.txt:', error);
+                this.log.error('Could not fetch version.txt:', error);
             });
     }
 

Diff do ficheiro suprimidas por serem muito extensas
+ 142 - 129
src/services/webclient.ts


+ 4 - 3
src/threema.d.ts

@@ -667,9 +667,10 @@ declare namespace threema {
         REPORT_LOG_LIMIT: number;
         COMPOSE_AREA_LOG_LEVEL: LogLevel;
         SALTYRTC_LOG_LEVEL: saltyrtc.LogLevel;
-        DEBUG_TIMER: boolean,
-        DEBUG_ARP: boolean;
-        DEBUG_MSGPACK: boolean;
+        TIMER_LOG_LEVEL: LogLevel;
+        ARP_LOG_LEVEL: LogLevel;
+        ARP_LOG_TRACE: boolean;
+        MSGPACK_LOG_TRACE: boolean;
     }
 
     interface InitialConversationData {

+ 14 - 8
src/threema/container.ts

@@ -15,8 +15,12 @@
  * along with Threema Web. If not, see <http://www.gnu.org/licenses/>.
  */
 
+import {Logger} from 'ts-log';
+
 import {copyShallow, randomString} from '../helpers';
+import {ConfidentialArray, ConfidentialObjectValues} from '../helpers/confidential';
 import {isFirstUnreadStatusMessage} from '../message_helpers';
+import {LogService} from '../services/log';
 import {ReceiverService} from '../services/receiver';
 
 type ContactMap = Map<string, threema.ContactReceiver>;
@@ -408,6 +412,9 @@ class ReceiverMessages {
  * This class manages all messages for the current user.
  */
 class Messages implements threema.Container.Messages {
+    // Logging
+    private readonly log: Logger;
+
     // The messages are stored in date-ascending order,
     // newest messages are appended, older messages are prepended.
     private messages: MessageMap = new Map();
@@ -415,10 +422,8 @@ class Messages implements threema.Container.Messages {
     // Message converter
     public converter: MessageConverter = null;
 
-    private $log: ng.ILogService;
-
-    constructor($log: ng.ILogService) {
-        this.$log = $log;
+    constructor(logService: LogService) {
+        this.log = logService.getLogger('MessagesContainer');
     }
 
     /**
@@ -594,7 +599,8 @@ class Messages implements threema.Container.Messages {
         const lastId = messages[messages.length - 1].id;
         const predicate = (msg: threema.Message) => msg.id === firstId || msg.id === lastId;
         if (receiverMessages.list.findIndex(predicate, receiverMessages.list) !== -1) {
-            this.$log.warn('Messages to be prepended intersect with existing messages:', messages);
+            this.log.warn('Messages to be prepended intersect with existing messages:',
+                new ConfidentialArray(messages.map((message) => new ConfidentialObjectValues(message))));
             return;
         }
 
@@ -866,8 +872,8 @@ class Drafts implements threema.Container.Drafts {
 }
 
 angular.module('3ema.container', [])
-.factory('Container', ['$filter', '$log', 'ReceiverService',
-    function($filter, $log, receiverService: ReceiverService) {
+.factory('Container', ['$filter', 'LogService', 'ReceiverService',
+    function($filter, logService: LogService, receiverService: ReceiverService) {
         class Filters  {
             public static hasData(receivers) {
                 return (obj) => $filter('hasData')(obj, receivers);
@@ -887,7 +893,7 @@ angular.module('3ema.container', [])
             Filters: Filters as threema.Container.Filters,
             createReceivers: () => new Receivers(),
             createConversations: () => new Conversations(receiverService),
-            createMessages: () => new Messages($log),
+            createMessages: () => new Messages(logService),
             createTyping: () => new Typing(),
             createDrafts: () => new Drafts(),
         } as threema.Container.Factory;

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff