Browse Source

Refactor logging out into its own module

So that we don't need to `_converse` obj in order to log messages
JC Brand 5 năm trước cách đây
mục cha
commit
caadb24310

+ 2 - 2
spec/mam.js

@@ -230,9 +230,9 @@
                                             'from': contact_jid,
                                             'type':'chat'
                                         }).c('body').t("Meet me at the dance");
-                    spyOn(_converse, 'log');
+                    spyOn(converse.env.log, 'warn');
                     _converse.connection._dataRecv(test_utils.createRequest(msg));
-                    expect(_converse.log).toHaveBeenCalledWith(`Ignoring alleged MAM message from ${msg.nodeTree.getAttribute('from')}`, Strophe.LogLevel.WARN);
+                    expect(converse.env.log.warn).toHaveBeenCalledWith(`Ignoring alleged MAM message from ${msg.nodeTree.getAttribute('from')}`);
 
                     msg = $msg({'id': _converse.connection.getUniqueId(), 'to': _converse.bare_jid})
                                 .c('result',  {'xmlns': 'urn:xmpp:mam:2', 'queryid':queryid, 'id': _converse.connection.getUniqueId()})

+ 8 - 9
spec/messages.js

@@ -518,7 +518,7 @@
 
             // Ideally we wouldn't have to filter out headline
             // messages, but Prosody gives them the wrong 'type' :(
-            sinon.spy(_converse, 'log');
+            sinon.spy(converse.env.log, 'info');
             sinon.spy(_converse.api.chatboxes, 'get');
             sinon.spy(u, 'isHeadlineMessage');
             const msg = $msg({
@@ -528,15 +528,14 @@
                     id: (new Date()).getTime()
                 }).c('body').t("This headline message will not be shown").tree();
             await _converse.handleMessageStanza(msg);
-            expect(_converse.log.calledWith(
-                "onMessage: Ignoring incoming headline message from JID: montague.lit",
-                Strophe.LogLevel.INFO
+            expect(converse.env.log.info.calledWith(
+                "onMessage: Ignoring incoming headline message from JID: montague.lit"
             )).toBeTruthy();
             expect(u.isHeadlineMessage.called).toBeTruthy();
             expect(u.isHeadlineMessage.returned(true)).toBeTruthy();
             expect(_converse.api.chatboxes.get.called).toBeFalsy();
             // Remove sinon spies
-            _converse.log.restore();
+            converse.env.log.info.restore();
             _converse.api.chatboxes.get.restore();
             u.isHeadlineMessage.restore();
             done();
@@ -1853,7 +1852,7 @@
                 await test_utils.waitForRoster(_converse, 'current');
                 await u.waitUntil(() => _converse.rosterview.el.querySelectorAll('.roster-group').length)
                 // Send a message from a different resource
-                spyOn(_converse, 'log');
+                spyOn(converse.env.log, 'info');
                 spyOn(_converse.api.chatboxes, 'create').and.callThrough();
                 _converse.filter_by_resource = true;
                 const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
@@ -1866,9 +1865,9 @@
                     .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
                 await _converse.handleMessageStanza(msg);
 
-                expect(_converse.log).toHaveBeenCalledWith(
-                        "onMessage: Ignoring incoming message intended for a different resource: romeo@montague.lit/some-other-resource",
-                        Strophe.LogLevel.INFO);
+                expect(converse.env.log.info).toHaveBeenCalledWith(
+                    "onMessage: Ignoring incoming message intended for a different resource: romeo@montague.lit/some-other-resource",
+                );
                 expect(_converse.api.chatboxes.create).not.toHaveBeenCalled();
                 _converse.filter_by_resource = false;
 

+ 6 - 8
spec/muc_messages.js

@@ -35,11 +35,10 @@
             `);
             const view = _converse.api.chatviews.get(muc_jid);
             await view.model.onMessage(received_stanza);
-            spyOn(_converse, 'log');
+            spyOn(converse.env.log, 'warn');
             _converse.connection._dataRecv(test_utils.createRequest(received_stanza));
-            expect(_converse.log).toHaveBeenCalledWith(
-                'onMessage: Ignoring unencapsulated forwarded groupchat message',
-                Strophe.LogLevel.WARN
+            expect(converse.env.log.warn).toHaveBeenCalledWith(
+                'onMessage: Ignoring unencapsulated forwarded groupchat message'
             );
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(0);
             expect(view.model.messages.length).toBe(0);
@@ -193,12 +192,11 @@
                         'type': 'groupchat'
                 }).c('body').t('I am groot').tree();
             const view = _converse.api.chatviews.get(muc_jid);
-            spyOn(_converse, 'log');
+            spyOn(converse.env.log, 'warn');
             await view.model.onMessage(msg);
-            expect(_converse.log).toHaveBeenCalledWith(
+            expect(converse.env.log.warn).toHaveBeenCalledWith(
                 'onMessage: Ignoring XEP-0280 "groupchat" message carbon, '+
-                'according to the XEP groupchat messages SHOULD NOT be carbon copied',
-                Strophe.LogLevel.WARN
+                'according to the XEP groupchat messages SHOULD NOT be carbon copied'
             );
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(0);
             expect(view.model.messages.length).toBe(0);

+ 7 - 9
spec/roster.js

@@ -39,7 +39,7 @@
             expect(_converse.roster.models.length).toBe(1);
             expect(_converse.roster.at(0).get('jid')).toBe(contact_jid);
 
-            spyOn(_converse, 'log');
+            spyOn(converse.env.log, 'warn');
             let roster_push = u.toStanza(`
                 <iq type="set" to="${_converse.jid}" from="eve@siacs.eu">
                     <query xmlns='jabber:iq:roster'>
@@ -47,10 +47,9 @@
                     </query>
                 </iq>`);
             _converse.connection._dataRecv(test_utils.createRequest(roster_push));
-            expect(_converse.log.calls.count()).toBe(2);
-            expect(_converse.log).toHaveBeenCalledWith(
-                `Ignoring roster illegitimate roster push message from ${roster_push.getAttribute('from')}`,
-                Strophe.LogLevel.WARN
+            expect(converse.env.log.warn.calls.count()).toBe(1);
+            expect(converse.env.log.warn).toHaveBeenCalledWith(
+                `Ignoring roster illegitimate roster push message from ${roster_push.getAttribute('from')}`
             );
             roster_push = u.toStanza(`
                 <iq type="set" to="${_converse.jid}" from="eve@siacs.eu">
@@ -59,10 +58,9 @@
                     </query>
                 </iq>`);
             _converse.connection._dataRecv(test_utils.createRequest(roster_push));
-            expect(_converse.log.calls.count()).toBe(4);
-            expect(_converse.log).toHaveBeenCalledWith(
-                `Ignoring roster illegitimate roster push message from ${roster_push.getAttribute('from')}`,
-                Strophe.LogLevel.WARN
+            expect(converse.env.log.warn.calls.count()).toBe(2);
+            expect(converse.env.log.warn).toHaveBeenCalledWith(
+                `Ignoring roster illegitimate roster push message from ${roster_push.getAttribute('from')}`
             );
             expect(_converse.roster.models.length).toBe(1);
             expect(_converse.roster.at(0).get('jid')).toBe(contact_jid);

+ 3 - 2
src/converse-chatview.js

@@ -14,6 +14,7 @@ import "converse-modal";
 import { debounce, get, isString } from "lodash";
 import { Overview } from "backbone.overview";
 import converse from "@converse/headless/converse-core";
+import log from "@converse/headless/log";
 import tpl_chatbox from "templates/chatbox.html";
 import tpl_chatbox_head from "templates/chatbox_head.html";
 import tpl_chatbox_message_form from "templates/chatbox_message_form.html";
@@ -184,7 +185,7 @@ converse.plugins.add('converse-chatview', {
                 try {
                     await _converse.api.vcard.update(this.model.contact.vcard, true);
                 } catch (e) {
-                    _converse.log(e, Strophe.LogLevel.FATAL);
+                    log.fatal(e);
                     this.alert(__('Sorry, something went wrong while trying to refresh'), 'danger');
                 }
                 u.removeClass('fa-spin', refresh_icon);
@@ -199,7 +200,7 @@ converse.plugins.add('converse-chatview', {
                     this.model.contact.removeFromRoster(
                         () => this.model.contact.destroy(),
                         (err) => {
-                            _converse.log(err, Strophe.LogLevel.ERROR);
+                            log.error(err);
                             _converse.api.alert('error', __('Error'), [
                                 __('Sorry, there was an error while trying to remove %1$s as a contact.',
                                 this.model.contact.getDisplayName())

+ 3 - 2
src/converse-controlbox.js

@@ -12,6 +12,7 @@ import "formdata-polyfill";
 import bootstrap from "bootstrap.native";
 import converse from "@converse/headless/converse-core";
 import { get } from "lodash";
+import log from "@converse/headless/log";
 import tpl_brand_heading from "templates/converse_brand_heading.html";
 import tpl_controlbox from "templates/controlbox.html";
 import tpl_controlbox_toggle from "templates/controlbox_toggle.html";
@@ -514,7 +515,7 @@ converse.plugins.add('converse-controlbox', {
                 _converse.chatboxviews.insertRowColumn(this.render().el);
                 _converse.api.waitUntil('initialized')
                     .then(this.render.bind(this))
-                    .catch(e => _converse.log(e, Strophe.LogLevel.FATAL));
+                    .catch(e => log.fatal(e));
             },
 
             render () {
@@ -597,7 +598,7 @@ converse.plugins.add('converse-controlbox', {
 
         _converse.api.waitUntil('chatBoxViewsInitialized')
            .then(addControlBox)
-           .catch(e => _converse.log(e, Strophe.LogLevel.FATAL));
+           .catch(e => log.fatal(e));
 
         _converse.api.listen.on('chatBoxesFetched', () => {
             const controlbox = _converse.chatboxes.get('controlbox') || addControlBox();

+ 2 - 1
src/converse-message-view.js

@@ -12,6 +12,7 @@ import URI from "urijs";
 import converse from  "@converse/headless/converse-core";
 import { debounce } from 'lodash'
 import filesize from "filesize";
+import log from "@converse/headless/log";
 import tpl_csn from "templates/csn.html";
 import tpl_file_progress from "templates/file_progress.html";
 import tpl_info from "templates/info.html";
@@ -128,7 +129,7 @@ converse.plugins.add('converse-message-view', {
                     this.renderChatStateNotification()
                 } else if (this.model.get('file') && !this.model.get('oob_url')) {
                     if (!this.model.file) {
-                        _converse.log("Attempted to render a file upload message with no file data");
+                        log.error("Attempted to render a file upload message with no file data");
                         return this.el;
                     }
                     this.renderFileUploadProgresBar();

+ 4 - 4
src/converse-minimize.js

@@ -9,15 +9,16 @@
 import "converse-chatview";
 import { Overview } from "backbone.overview";
 import converse from "@converse/headless/converse-core";
+import log from "@converse/headless/log";
 import tpl_chatbox_minimize from "templates/chatbox_minimize.html";
 import tpl_chats_panel from "templates/chats_panel.html";
 import tpl_toggle_chats from "templates/toggle_chats.html";
 import tpl_trimmed_chat from "templates/trimmed_chat.html";
 
-
-const { _ , Backbone, Strophe, dayjs } = converse.env;
+const { _ , Backbone, dayjs } = converse.env;
 const u = converse.env.utils;
 
+
 converse.plugins.add('converse-minimize', {
     /* Optional dependencies are other plugins which might be
      * overridden or relied upon, and therefore need to be loaded before
@@ -572,8 +573,7 @@ converse.plugins.add('converse-minimize', {
              * @example _converse.api.listen.on('minimizedChatsInitialized', () => { ... });
              */
             _converse.api.trigger('minimizedChatsInitialized');
-        }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
-
+        }).catch(e => log.fatal(e));
 
         const debouncedTrimChats = _.debounce(() => _converse.chatboxviews.trimChats(), 250);
         _converse.api.listen.on('chatBoxInsertedIntoDOM', view => _converse.chatboxviews.trimChats(view));

+ 7 - 6
src/converse-muc-views.js

@@ -15,6 +15,7 @@ import "formdata-polyfill";
 import "@converse/headless/utils/muc";
 import { OrderedListView } from "backbone.overview";
 import converse from "@converse/headless/converse-core";
+import log from "@converse/headless/log";
 import tpl_add_chatroom_modal from "templates/add_chatroom_modal.html";
 import tpl_chatarea from "templates/chatarea.html";
 import tpl_chatroom from "templates/chatroom.html";
@@ -219,7 +220,7 @@ converse.plugins.add('converse-muc-views', {
                 parent_el.insertAdjacentHTML('beforeend', tpl_spinner());
                 _converse.api.disco.info(ev.target.getAttribute('data-room-jid'), null)
                     .then(stanza => insertRoomInfo(parent_el, stanza))
-                    .catch(e => _converse.log(e, Strophe.LogLevel.ERROR));
+                    .catch(e => log.error(e));
             }
         }
 
@@ -353,7 +354,7 @@ converse.plugins.add('converse-muc-views', {
                     })
                     .catch(err => {
                         this.alert(__('Sorry, something went wrong while trying to set the affiliation'), 'danger');
-                        _converse.log(err, Strophe.LogLevel.ERROR);
+                        log.error(err);
                     });
             },
 
@@ -377,7 +378,7 @@ converse.plugins.add('converse-muc-views', {
                         } else {
                             this.alert(__('Sorry, something went wrong while trying to set the role'), 'danger');
                             if (u.isErrorObject(e)) {
-                                _converse.log(e, Strophe.LogLevel.ERROR);
+                                log.error(e);
                             }
                         }
                     }
@@ -1149,7 +1150,7 @@ converse.plugins.add('converse-muc-views', {
             },
 
             onCommandError (err) {
-                _converse.log(err, Strophe.LogLevel.FATAL);
+                log.fatal(err);
                 this.showErrorMessage(__("Sorry, an error happened while running the command. Check your browser's developer console for details."));
             },
 
@@ -1366,7 +1367,7 @@ converse.plugins.add('converse-muc-views', {
                     this.showSpinner();
                     this.model.fetchRoomConfiguration()
                         .then(iq => this.renderConfigurationForm(iq))
-                        .catch(e => _converse.log(e, Strophe.LogLevel.ERROR));
+                        .catch(e => log.error(e));
                 } else {
                     this.closeForm();
                 }
@@ -2125,7 +2126,7 @@ converse.plugins.add('converse-muc-views', {
                 // Features could have been added before the controlbox was
                 // initialized. We're only interested in MUC
                 _converse.disco_entities.each(entity => featureAdded(entity.features.findWhere({'var': Strophe.NS.MUC })));
-            }).catch(e => _converse.log(e, Strophe.LogLevel.ERROR));
+            }).catch(e => log.error(e));
         }
 
         function fetchAndSetMUCDomain (controlboxview) {

+ 2 - 3
src/converse-notification.js

@@ -9,6 +9,7 @@
  */
 import converse from "@converse/headless/converse-core";
 import { get } from "lodash";
+import log from "@converse/headless/log";
 
 const { Strophe, sizzle } = converse.env;
 const u = converse.env.utils;
@@ -150,9 +151,7 @@ converse.plugins.add('converse-notification', {
                 title = __("%1$s says", Strophe.getResourceFromJid(full_from_jid));
             } else {
                 if (_converse.roster === undefined) {
-                    _converse.log(
-                        "Could not send notification, because roster is undefined",
-                        Strophe.LogLevel.ERROR);
+                    log.error("Could not send notification, because roster is undefined");
                     return;
                 }
                 roster_item = _converse.roster.get(from_jid);

+ 20 - 25
src/converse-omemo.js

@@ -10,6 +10,7 @@
  */
 import "converse-profile";
 import converse from "@converse/headless/converse-core";
+import log from "@converse/headless/log";
 import tpl_toolbar_omemo from "templates/toolbar_omemo.html";
 
 const { Backbone, Strophe, sizzle, $build, $iq, $msg, _ } = converse.env;
@@ -122,7 +123,7 @@ converse.plugins.add('converse-omemo', {
                     .catch(err => {
                         const { _converse } = this.__super__,
                               { __ } = _converse;
-                        _converse.log(err, Strophe.LogLevel.ERROR);
+                        log.error(err);
                         _converse.api.alert(
                             Strophe.LogLevel.ERROR,
                             __('Error'), [__('Sorry, an error occurred while trying to remove the devices.')]
@@ -312,7 +313,7 @@ converse.plugins.add('converse-omemo', {
                         'type': 'error',
                     });
                 }
-                _converse.log(`${e.name} ${e.message}`, Strophe.LogLevel.ERROR);
+                log.error(`${e.name} ${e.message}`);
             },
 
             async handleDecryptedWhisperMessage (attrs, key_and_tag) {
@@ -414,10 +415,10 @@ converse.plugins.add('converse-omemo', {
                         err_msgs.push(e.iq.outerHTML);
                     }
                     _converse.api.alert('error', __('Error'), err_msgs);
-                    _converse.log(e, Strophe.LogLevel.ERROR);
+                    log.error(e);
                 } else if (e.user_facing) {
                     _converse.api.alert('error', __('Error'), [e.message]);
-                    _converse.log(e, Strophe.LogLevel.ERROR);
+                    log.error(e);
                 } else {
                     throw e;
                 }
@@ -557,11 +558,8 @@ converse.plugins.add('converse-omemo', {
                     const session = await buildSession(device);
                     return session;
                 } catch (e) {
-                    _converse.log(
-                        `Could not build an OMEMO session for device ${device.get('id')}`,
-                        Strophe.LogLevel.ERROR
-                    );
-                    _converse.log(e, Strophe.LogLevel.ERROR);
+                    log.error(`Could not build an OMEMO session for device ${device.get('id')}`);
+                    log.error(e);
                     return null;
                 }
             }
@@ -845,7 +843,7 @@ converse.plugins.add('converse-omemo', {
                     Object.keys(this.getPreKeys())
                 );
                 if (missing_keys.length < 1) {
-                    _converse.log("No missing prekeys to generate for our own device", Strophe.LogLevel.WARN);
+                    log.warn("No missing prekeys to generate for our own device");
                     return Promise.resolve();
                 }
                 const keys = await Promise.all(missing_keys.map(id => libsignal.KeyHelper.generatePreKey(parseInt(id, 10))));
@@ -908,11 +906,8 @@ converse.plugins.add('converse-omemo', {
                                 }
                             },
                             'error': (model, resp) => {
-                                _converse.log(
-                                    "Could not fetch OMEMO session from cache, we'll generate a new one.",
-                                    Strophe.LogLevel.WARN
-                                );
-                                _converse.log(resp, Strophe.LogLevel.WARN);
+                                log.warn("Could not fetch OMEMO session from cache, we'll generate a new one.");
+                                log.warn(resp);
                                 this.generateBundle().then(resolve).catch(reject);
                             }
                         });
@@ -996,10 +991,10 @@ converse.plugins.add('converse-omemo', {
                         ids = await this.fetchDevicesFromServer()
                     } catch (e) {
                         if (e === null) {
-                            _converse.log(`Timeout error while fetching devices for ${this.get('jid')}`, Strophe.LogLevel.ERROR);
+                            log.error(`Timeout error while fetching devices for ${this.get('jid')}`);
                         } else {
-                            _converse.log(`Could not fetch devices for ${this.get('jid')}`, Strophe.LogLevel.ERROR);
-                            _converse.log(e, Strophe.LogLevel.ERROR);
+                            log.error(`Could not fetch devices for ${this.get('jid')}`);
+                            log.error(e);
                         }
                         this.destroy();
                     }
@@ -1014,7 +1009,7 @@ converse.plugins.add('converse-omemo', {
                     this._devices_promise = new Promise(resolve => {
                         this.devices.fetch({
                             'success': c => resolve(this.onDevicesFound(c)),
-                            'error': (m, e) => { _converse.log(e, Strophe.LogLevel.ERROR); resolve(); }
+                            'error': (m, e) => { log.error(e); resolve(); }
                         });
                     });
                 }
@@ -1049,7 +1044,7 @@ converse.plugins.add('converse-omemo', {
                 try {
                     iq = await _converse.api.sendIQ(stanza);
                 } catch (e) {
-                    _converse.log(e, Strophe.LogLevel.ERROR);
+                    log.error(e);
                     return [];
                 }
                 const device_ids = sizzle(`list[xmlns="${Strophe.NS.OMEMO}"] device`, iq).map(dev => dev.getAttribute('id'));
@@ -1161,7 +1156,7 @@ converse.plugins.add('converse-omemo', {
                         updateBundleFromStanza(message);
                     }
                 } catch (e) {
-                    _converse.log(e.message, Strophe.LogLevel.ERROR);
+                    log.error(e.message);
                 }
                 return true;
             }, null, 'message', 'headline');
@@ -1189,8 +1184,8 @@ converse.plugins.add('converse-omemo', {
                 await restoreOMEMOSession();
                 await _converse.omemo_store.publishBundle();
             } catch (e) {
-                _converse.log("Could not initialize OMEMO support", Strophe.LogLevel.ERROR);
-                _converse.log(e, Strophe.LogLevel.ERROR);
+                log.error("Could not initialize OMEMO support");
+                log.error(e);
                 return;
             }
             /**
@@ -1252,11 +1247,11 @@ converse.plugins.add('converse-omemo', {
 
         _converse.api.listen.on('userDetailsModalInitialized', (contact) => {
             const jid = contact.get('jid');
-            _converse.generateFingerprints(jid).catch(e => _converse.log(e, Strophe.LogLevel.ERROR));
+            _converse.generateFingerprints(jid).catch(e => log.error(e));
         });
 
         _converse.api.listen.on('profileModalInitialized', () => {
-            _converse.generateFingerprints(_converse.bare_jid).catch(e => _converse.log(e, Strophe.LogLevel.ERROR));
+            _converse.generateFingerprints(_converse.bare_jid).catch(e => log.error(e));
         });
 
         _converse.api.listen.on('afterTearDown', () => (delete _converse.omemo_store));

+ 3 - 2
src/converse-profile.js

@@ -13,12 +13,13 @@ import "converse-modal";
 import "formdata-polyfill";
 import bootstrap from "bootstrap.native";
 import converse from "@converse/headless/converse-core";
+import log from "@converse/headless/log";
 import tpl_chat_status_modal from "templates/chat_status_modal.html";
 import tpl_client_info_modal from "templates/client_info_modal.html";
 import tpl_profile_modal from "templates/profile_modal.html";
 import tpl_profile_view from "templates/profile_view.html";
 
-const { Strophe, sizzle } = converse.env;
+const { sizzle } = converse.env;
 const u = converse.env.utils;
 
 
@@ -102,7 +103,7 @@ converse.plugins.add('converse-profile', {
                 _converse.api.vcard.set(_converse.bare_jid, data)
                 .then(() => _converse.api.vcard.update(this.model.vcard, true))
                 .catch((err) => {
-                    _converse.log(err, Strophe.LogLevel.FATAL);
+                    log.fatal(err);
                     _converse.api.show('error', __('Error'), [
                         __("Sorry, an error happened while trying to save your profile data."),
                         __("You can check your browser's developer console for any error output.")

+ 11 - 15
src/converse-push.js

@@ -10,6 +10,7 @@
  * an "App Server" as defined in  XEP-0357
  */
 import converse from "@converse/headless/converse-core";
+import log from "@converse/headless/log";
 
 const { Strophe, $iq, _ } = converse.env;
 
@@ -34,10 +35,8 @@ converse.plugins.add('converse-push', {
                 return;
             }
             if (!(await _converse.api.disco.supports(Strophe.NS.PUSH, domain || _converse.bare_jid))) {
-                return _converse.log(
-                    `Not disabling push app server "${push_app_server.jid}", no disco support from your server.`,
-                    Strophe.LogLevel.WARN
-                );
+                log.warn(`Not disabling push app server "${push_app_server.jid}", no disco support from your server.`);
+                return;
             }
             const stanza = $iq({'type': 'set'});
             if (domain !== _converse.bare_jid) {
@@ -52,8 +51,8 @@ converse.plugins.add('converse-push', {
             }
             _converse.api.sendIQ(stanza)
             .catch(e => {
-                _converse.log(`Could not disable push app server for ${push_app_server.jid}`, Strophe.LogLevel.ERROR);
-                _converse.log(e, Strophe.LogLevel.ERROR);
+                log.error(`Could not disable push app server for ${push_app_server.jid}`);
+                log.error(e);
             });
         }
 
@@ -63,9 +62,8 @@ converse.plugins.add('converse-push', {
             }
             const identity = await _converse.api.disco.getIdentity('pubsub', 'push', push_app_server.jid);
             if (!identity) {
-                return _converse.log(
-                    `Not enabling push the service "${push_app_server.jid}", it doesn't have the right disco identtiy.`,
-                    Strophe.LogLevel.WARN
+                return log.warn(
+                    `Not enabling push the service "${push_app_server.jid}", it doesn't have the right disco identtiy.`
                 );
             }
             const result = await Promise.all([
@@ -73,10 +71,8 @@ converse.plugins.add('converse-push', {
                 _converse.api.disco.supports(Strophe.NS.PUSH, domain)
             ]);
             if (!result[0] && !result[1]) {
-                return _converse.log(
-                    `Not enabling push app server "${push_app_server.jid}", no disco support from your server.`,
-                    Strophe.LogLevel.WARN
-                );
+                log.warn(`Not enabling push app server "${push_app_server.jid}", no disco support from your server.`);
+                return;
             }
             const stanza = $iq({'type': 'set'});
             if (domain !== _converse.bare_jid) {
@@ -110,8 +106,8 @@ converse.plugins.add('converse-push', {
             try {
                 await Promise.all(enabled.concat(disabled));
             } catch (e) {
-                _converse.log('Could not enable or disable push App Server', Strophe.LogLevel.ERROR);
-                if (e) _converse.log(e, Strophe.LogLevel.ERROR);
+                log.error('Could not enable or disable push App Server');
+                if (e) log.error(e);
             } finally {
                 push_enabled.push(domain);
             }

+ 8 - 8
src/converse-register.js

@@ -12,6 +12,7 @@
  */
 import "converse-controlbox";
 import converse from "@converse/headless/converse-core";
+import log from "@converse/headless/log";
 import tpl_form_input from "templates/form_input.html";
 import tpl_form_username from "templates/form_username.html";
 import tpl_register_link from "templates/register_link.html";
@@ -142,7 +143,7 @@ converse.plugins.add('converse-register', {
             _converse.api.waitUntil('controlBoxInitialized').then(() => {
                 const controlbox = _converse.chatboxes.get('controlbox')
                 controlbox.set({'active-form': value});
-            }).catch(e => _converse.log(e, Strophe.LogLevel.FATAL));
+            }).catch(e => log.fatal(e));
         }
         _converse.router.route('converse/login', () => setActiveForm('login'));
         _converse.router.route('converse/register', () => setActiveForm('register'));
@@ -402,7 +403,7 @@ converse.plugins.add('converse-register', {
              * @param { integer } status_code - The Strophe.Status status code
              */
             onConnectStatusChanged(status_code) {
-                _converse.log('converse-register: onConnectStatusChanged');
+                log.debug('converse-register: onConnectStatusChanged');
                 if (_.includes([
                             Strophe.Status.DISCONNECTED,
                             Strophe.Status.CONNFAIL,
@@ -411,13 +412,12 @@ converse.plugins.add('converse-register', {
                             Strophe.Status.CONFLICT
                         ], status_code)) {
 
-                    _converse.log(
-                        `Problem during registration: Strophe.Status is ${_converse.CONNECTION_STATUS[status_code]}`,
-                        Strophe.LogLevel.ERROR
+                    log.error(
+                        `Problem during registration: Strophe.Status is ${_converse.CONNECTION_STATUS[status_code]}`
                     );
                     this.abortRegistration();
                 } else if (status_code === Strophe.Status.REGISTERED) {
-                    _converse.log("Registered successfully.");
+                    log.debug("Registered successfully.");
                     _converse.connection.reset();
                     this.showSpinner();
 
@@ -645,7 +645,7 @@ converse.plugins.add('converse-register', {
                         this.fields[_var.toLowerCase()] = _.get(field.querySelector('value'), 'textContent', '');
                     } else {
                         // TODO: other option seems to be type="fixed"
-                        _converse.log("Found field we couldn't parse", Strophe.LogLevel.WARN);
+                        log.warn("Found field we couldn't parse");
                     }
                 });
                 this.form_type = 'xform';
@@ -661,7 +661,7 @@ converse.plugins.add('converse-register', {
              */
             _onRegisterIQ (stanza) {
                 if (stanza.getAttribute("type") === "error") {
-                    _converse.log("Registration failed.", Strophe.LogLevel.ERROR);
+                    log.error("Registration failed.");
                     this.reportErrors(stanza);
 
                     let error = stanza.getElementsByTagName("error");

+ 5 - 5
src/converse-rosterview.js

@@ -13,6 +13,7 @@ import "formdata-polyfill";
 import { OrderedListView } from "backbone.overview";
 import SHA1 from 'strophe.js/src/sha1';
 import converse from "@converse/headless/converse-core";
+import log from "@converse/headless/log";
 import tpl_add_contact_modal from "templates/add_contact_modal.html";
 import tpl_group_header from "templates/group_header.html";
 import tpl_pending_contact from "templates/pending_contact.html";
@@ -504,7 +505,7 @@ converse.plugins.add('converse-rosterview', {
                         this.model.destroy();
                     }
                 } catch (e) {
-                    _converse.log(e, Strophe.LogLevel.ERROR);
+                    log.error(e);
                     _converse.api.alert('error', __('Error'),
                         [__('Sorry, there was an error while trying to remove %1$s as a contact.', this.model.getDisplayName())]
                     );
@@ -933,10 +934,9 @@ converse.plugins.add('converse-rosterview', {
                     this.addExistingContact(contact, options);
                 } else {
                     if (!_converse.allow_contact_requests) {
-                        _converse.log(
+                        log.debug(
                             `Not adding requesting or pending contact ${contact.get('jid')} `+
-                            `because allow_contact_requests is false`,
-                            Strophe.LogLevel.DEBUG
+                            `because allow_contact_requests is false`
                         );
                         return;
                     }
@@ -971,7 +971,7 @@ converse.plugins.add('converse-rosterview', {
                 /* Place the rosterview inside the "Contacts" panel. */
                 _converse.api.waitUntil('rosterViewInitialized')
                     .then(() => view.controlbox_pane.el.insertAdjacentElement('beforeEnd', _converse.rosterview.el))
-                    .catch(e => _converse.log(e, Strophe.LogLevel.FATAL));
+                    .catch(e => log.fatal(e));
             }
             insertRoster();
             view.model.on('change:connected', insertRoster);

+ 8 - 7
src/headless/converse-bookmarks.js

@@ -12,6 +12,7 @@
 import "@converse/headless/converse-muc";
 import converse from "@converse/headless/converse-core";
 import { get } from "lodash";
+import log from "./log";
 
 const { Backbone, Strophe, $iq, sizzle } = converse.env;
 const u = converse.env.utils;
@@ -105,7 +106,7 @@ converse.plugins.add('converse-bookmarks', {
             initialize () {
                 this.on('add', bm => this.openBookmarkedRoom(bm)
                     .then(bm => this.markRoomAsBookmarked(bm))
-                    .catch(e => _converse.log(e, Strophe.LogLevel.FATAL))
+                    .catch(e => log.fatal(e))
                 );
 
                 this.on('remove', this.markRoomAsUnbookmarked, this);
@@ -172,8 +173,8 @@ converse.plugins.add('converse-bookmarks', {
             },
 
             onBookmarkError (iq, options) {
-                _converse.log("Error while trying to add bookmark", Strophe.LogLevel.ERROR);
-                _converse.log(iq);
+                log.error("Error while trying to add bookmark");
+                log.error(iq);
                 _converse.api.alert(
                     'error', __('Error'), [__("Sorry, something went wrong while trying to save your bookmark.")]
                 );
@@ -232,14 +233,14 @@ converse.plugins.add('converse-bookmarks', {
 
             onBookmarksReceivedError (deferred, iq) {
                 if (iq === null) {
-                    _converse.log('Error: timeout while fetching bookmarks', Strophe.LogLevel.ERROR);
+                    log.error('Error: timeout while fetching bookmarks');
                     _converse.api.alert('error', __('Timeout Error'),
                         [__("The server did not return your bookmarks within the allowed time. "+
                             "You can reload the page to request them again.")]
                     );
                 } else {
-                    _converse.log('Error while fetching bookmarks', Strophe.LogLevel.ERROR);
-                    _converse.log(iq, Strophe.LogLevel.DEBUG);
+                    log.error('Error while fetching bookmarks');
+                    log.error(iq);
                 }
                 if (deferred) {
                     if (iq.querySelector('error[type="cancel"] item-not-found')) {
@@ -305,7 +306,7 @@ converse.plugins.add('converse-bookmarks', {
                 if (sizzle('event[xmlns="'+Strophe.NS.PUBSUB+'#event"] items[node="storage:bookmarks"]', message).length) {
                     _converse.api.waitUntil('bookmarksInitialized')
                         .then(() => _converse.bookmarks.createBookmarksFromStanza(message))
-                        .catch(e => _converse.log(e, Strophe.LogLevel.FATAL));
+                        .catch(e => log.fatal(e));
                 }
             }, null, 'message', 'headline', null, _converse.bare_jid);
 

+ 2 - 3
src/headless/converse-bosh.js

@@ -10,6 +10,7 @@
  */
 import 'strophe.js/src/bosh';
 import converse from "./converse-core";
+import log from "./log";
 
 const { Backbone, Strophe } = converse.env;
 
@@ -94,9 +95,7 @@ converse.plugins.add('converse-bosh', {
                     _converse.connection.restore(jid, _converse.onConnectStatusChanged);
                     return true;
                 } catch (e) {
-                    _converse.log(
-                        "Could not restore session for jid: "+
-                        jid+" Error message: "+e.message, Strophe.LogLevel.WARN);
+                    log.warn("Could not restore session for jid: "+jid+" Error message: "+e.message);
                     return false;
                 }
             }

+ 24 - 49
src/headless/converse-chat.js

@@ -1,6 +1,7 @@
 import { get, isObject, isString, propertyOf } from "lodash";
 import converse from "./converse-core";
 import filesize from "filesize";
+import log from "./log";
 
 const { $msg, Backbone, Strophe, dayjs, sizzle, utils } = converse.env;
 const u = converse.env.utils;
@@ -98,7 +99,7 @@ converse.plugins.add('converse-chat', {
                 try {
                     this.destroy()
                 } catch (e) {
-                    _converse.log(e, Strophe.LogLevel.ERROR);
+                    log.error(e);
                 }
             },
 
@@ -164,7 +165,7 @@ converse.plugins.add('converse-chat', {
                 try {
                     stanza = await this.sendSlotRequestStanza();
                 } catch (e) {
-                    _converse.log(e, Strophe.LogLevel.ERROR);
+                    log.error(e);
                     return this.save({
                         'type': 'error',
                         'message': __("Sorry, could not determine upload URL."),
@@ -190,7 +191,7 @@ converse.plugins.add('converse-chat', {
                 const xhr = new XMLHttpRequest();
                 xhr.onreadystatechange = () => {
                     if (xhr.readyState === XMLHttpRequest.DONE) {
-                        _converse.log("Status: " + xhr.status, Strophe.LogLevel.INFO);
+                        log.info("Status: " + xhr.status);
                         if (xhr.status === 200 || xhr.status === 201) {
                             this.save({
                                 'upload': _converse.SUCCESS,
@@ -318,7 +319,7 @@ converse.plugins.add('converse-chat', {
 
             fetchMessages () {
                 if (this.messages.fetched) {
-                    _converse.log(`Not re-fetching messages for ${this.get('jid')}`, Strophe.LogLevel.INFO);
+                    log.info(`Not re-fetching messages for ${this.get('jid')}`);
                     return;
                 }
                 this.messages.fetched = u.getResolveablePromise();
@@ -356,7 +357,7 @@ converse.plugins.add('converse-chat', {
                     this.messages.reset();
                 } catch (e) {
                     this.messages.trigger('reset');
-                    _converse.log(e, Strophe.LogLevel.ERROR);
+                    log.error(e);
                 } finally {
                     delete this.messages.fetched;
                 }
@@ -368,7 +369,7 @@ converse.plugins.add('converse-chat', {
                         return this.destroy({success, 'error': (m, e) => reject(e)})
                     });
                 } catch (e) {
-                    _converse.log(e, Strophe.LogLevel.ERROR);
+                    log.error(e);
                 } finally {
                     if (_converse.clear_messages_on_reconnection) {
                         await this.clearMessages();
@@ -401,7 +402,7 @@ converse.plugins.add('converse-chat', {
                 const auto_join = _converse.auto_join_private_chats.concat(room_jids);
                 if (_converse.singleton && !auto_join.includes(attrs.jid) && !_converse.auto_join_on_invite) {
                     const msg = `${attrs.jid} is not allowed because singleton is true and it's not being auto_joined`;
-                    _converse.log(msg, Strophe.LogLevel.WARN);
+                    log.warn(msg);
                     return msg;
                 }
             },
@@ -625,11 +626,8 @@ converse.plugins.add('converse-chat', {
                 if (markers.length === 0) {
                     return false;
                 } else if (markers.length > 1) {
-                    _converse.log(
-                        'handleChatMarker: Ignoring incoming stanza with multiple message markers',
-                        Strophe.LogLevel.ERROR
-                    );
-                    _converse.log(stanza, Strophe.LogLevel.ERROR);
+                    log.error('handleChatMarker: Ignoring incoming stanza with multiple message markers');
+                    log.error(stanza);
                     return false;
                 } else {
                     const marker = markers.pop();
@@ -1088,8 +1086,8 @@ converse.plugins.add('converse-chat', {
                     .c('not-allowed', {xmlns:"urn:ietf:params:xml:ns:xmpp-stanzas"}).up()
                     .c('text', {xmlns:"urn:ietf:params:xml:ns:xmpp-stanzas"}).t(text)
             );
-            _converse.log(`Rejecting message stanza with the following reason: ${text}`, Strophe.LogLevel.WARN);
-            _converse.log(stanza, Strophe.LogLevel.WARN);
+            log.warn(`Rejecting message stanza with the following reason: ${text}`);
+            log.warn(stanza);
         }
 
 
@@ -1123,17 +1121,11 @@ converse.plugins.add('converse-chat', {
             const to_resource = Strophe.getResourceFromJid(to_jid);
 
             if (_converse.filter_by_resource && (to_resource && to_resource !== _converse.resource)) {
-                return _converse.log(
-                    `onMessage: Ignoring incoming message intended for a different resource: ${to_jid}`,
-                    Strophe.LogLevel.INFO
-                );
+                return log.info(`onMessage: Ignoring incoming message intended for a different resource: ${to_jid}`);
             } else if (utils.isHeadlineMessage(_converse, stanza)) {
                 // XXX: Prosody sends headline messages with the
                 // wrong type ('chat'), so we need to filter them out here.
-                return _converse.log(
-                    `onMessage: Ignoring incoming headline message from JID: ${stanza.getAttribute('from')}`,
-                    Strophe.LogLevel.INFO
-                );
+                return log.info(`onMessage: Ignoring incoming headline message from JID: ${stanza.getAttribute('from')}`);
             }
 
             const bare_forward = sizzle(`message > forwarded[xmlns="${Strophe.NS.FORWARD}"]`, stanza).length;
@@ -1163,29 +1155,20 @@ converse.plugins.add('converse-chat', {
                     to_jid = stanza.getAttribute('to');
                     from_jid = stanza.getAttribute('from');
                 } else {
-                    return _converse.log(
-                        `onMessage: Ignoring alleged MAM message from ${stanza.getAttribute('from')}`,
-                        Strophe.LogLevel.WARN
-                    );
+                    return log.warn(`onMessage: Ignoring alleged MAM message from ${stanza.getAttribute('from')}`);
                 }
             }
 
             const from_bare_jid = Strophe.getBareJidFromJid(from_jid);
             const is_me = from_bare_jid === _converse.bare_jid;
             if (is_me && to_jid === null) {
-                return _converse.log(
-                    `Don't know how to handle message stanza without 'to' attribute. ${stanza.outerHTML}`,
-                    Strophe.LogLevel.ERROR
-                );
+                return log.error(`Don't know how to handle message stanza without 'to' attribute. ${stanza.outerHTML}`);
             }
             const contact_jid = is_me ? Strophe.getBareJidFromJid(to_jid) : from_bare_jid;
             const contact = await _converse.api.contacts.get(contact_jid);
             if (contact === undefined && !_converse.allow_non_roster_messaging) {
-                _converse.log(
-                    `Blocking messaging with a JID not in our roster because allow_non_roster_messaging is false.`,
-                    Strophe.LogLevel.ERROR
-                );
-                return _converse.log(stanza, Strophe.LogLevel.ERROR);
+                log.error(`Blocking messaging with a JID not in our roster because allow_non_roster_messaging is false.`);
+                return log.error(stanza);
             }
             // Get chat box, but only create when the message has something to show to the user
             const has_body = sizzle(`body, encrypted[xmlns="${Strophe.NS.OMEMO}"]`, stanza).length > 0;
@@ -1210,7 +1193,7 @@ converse.plugins.add('converse-chat', {
                    // MAM messages are handled in converse-mam.
                    // We shouldn't get MAM messages here because
                    // they shouldn't have a `type` attribute.
-                   _converse.log(`Received a MAM message with type "chat".`, Strophe.LogLevel.WARN);
+                   log.warn(`Received a MAM message with type "chat".`);
                    return true;
                }
                _converse.handleMessageStanza(stanza);
@@ -1247,9 +1230,7 @@ converse.plugins.add('converse-chat', {
                 if (isString(jid)) {
                     _converse.api.chats.open(jid);
                 } else {
-                    _converse.log(
-                        'Invalid jid criteria specified for "auto_join_private_chats"',
-                        Strophe.LogLevel.ERROR);
+                    log.error('Invalid jid criteria specified for "auto_join_private_chats"');
                 }
             });
             /**
@@ -1267,10 +1248,7 @@ converse.plugins.add('converse-chat', {
         /************************ BEGIN Route Handlers ************************/
         function openChat (jid) {
             if (!utils.isValidJID(jid)) {
-                return _converse.log(
-                    `Invalid JID "${jid}" provided in URL fragment`,
-                    Strophe.LogLevel.WARN
-                );
+                return log.warn(`Invalid JID "${jid}" provided in URL fragment`);
             }
             _converse.api.chats.open(jid);
         }
@@ -1312,7 +1290,7 @@ converse.plugins.add('converse-chat', {
                         }
                         const chatbox = _converse.api.chats.get(jids, attrs, true);
                         if (!chatbox) {
-                            _converse.log("Could not open chatbox for JID: "+jids, Strophe.LogLevel.ERROR);
+                            log.error("Could not open chatbox for JID: "+jids);
                             return;
                         }
                         return chatbox;
@@ -1324,10 +1302,7 @@ converse.plugins.add('converse-chat', {
                             return _converse.api.chats.get(jid, attrs, true).maybeShow();
                         }));
                     }
-                    _converse.log(
-                        "chats.create: You need to provide at least one JID",
-                        Strophe.LogLevel.ERROR
-                    );
+                    log.error("chats.create: You need to provide at least one JID");
                     return null;
                 },
 
@@ -1385,7 +1360,7 @@ converse.plugins.add('converse-chat', {
                         );
                     }
                     const err_msg = "chats.open: You need to provide at least one JID";
-                    _converse.log(err_msg, Strophe.LogLevel.ERROR);
+                    log.error(err_msg);
                     throw new Error(err_msg);
                 },
 

+ 2 - 1
src/headless/converse-chatboxes.js

@@ -9,6 +9,7 @@
 import "./converse-emoji";
 import converse from "./converse-core";
 import { isString } from "lodash";
+import log from "./log";
 
 const { Strophe } = converse.env;
 
@@ -104,7 +105,7 @@ converse.plugins.add('converse-chatboxes', {
             try {
                 chatbox = new model(attrs, {'collection': _converse.chatboxes});
             } catch (e) {
-                _converse.log(e, Strophe.LogLevel.ERROR);
+                log.error(e);
                 return null;
             }
             await chatbox.initialized;

+ 31 - 65
src/headless/converse-core.js

@@ -15,6 +15,7 @@ import _ from './lodash.noconflict';
 import advancedFormat from 'dayjs/plugin/advancedFormat';
 import dayjs from 'dayjs';
 import i18n from './i18n';
+import log from '@converse/headless/log';
 import pluggable from 'pluggable.js/src/pluggable';
 import sizzle from 'sizzle';
 import u from '@converse/headless/utils/core';
@@ -119,7 +120,7 @@ _converse.Collection = Backbone.Collection.extend({
                     Object.assign(options, {
                         success,
                         'error': (m, e) => {
-                            _converse.log(e, Strophe.LogLevel.ERROR);
+                            log.error(e);
                             success()
                         }
                     })
@@ -271,46 +272,12 @@ _converse.default_settings = {
     whitelisted_plugins: []
 };
 
+const loglevel = _converse.debug ? Strophe.LogLevel.DEBUG : Strophe.LogLevel.INFO;
+log.initialize(loglevel);
+_converse.log = log.log;
 
-/**
- * Logs messages to the browser's developer console.
- * Available loglevels are 0 for 'debug', 1 for 'info', 2 for 'warn',
- * 3 for 'error' and 4 for 'fatal'.
- * When using the 'error' or 'warn' loglevels, a full stacktrace will be
- * logged as well.
- * @method log
- * @private
- * @memberOf _converse
- * @param { string } message - The message to be logged
- * @param { integer } level - The loglevel which allows for filtering of log messages
- */
-_converse.log = function (message, level, style='') {
-    if (level === Strophe.LogLevel.ERROR || level === Strophe.LogLevel.FATAL) {
-        style = style || 'color: maroon';
-    }
-    if (message instanceof Error) {
-        message = message.stack;
-    } else if (_.isElement(message)) {
-        message = message.outerHTML;
-    }
-    const prefix = style ? '%c' : '';
-    if (level === Strophe.LogLevel.ERROR) {
-        u.logger.error(`${prefix} ERROR: ${message}`, style);
-    } else if (level === Strophe.LogLevel.WARN) {
-        u.logger.warn(`${prefix} ${(new Date()).toISOString()} WARNING: ${message}`, style);
-    } else if (level === Strophe.LogLevel.FATAL) {
-        u.logger.error(`${prefix} FATAL: ${message}`, style);
-    } else if (_converse.debug) {
-        if (level === Strophe.LogLevel.DEBUG) {
-            u.logger.debug(`${prefix} ${(new Date()).toISOString()} DEBUG: ${message}`, style);
-        } else {
-            u.logger.info(`${prefix} ${(new Date()).toISOString()} INFO: ${message}`, style);
-        }
-    }
-};
-
-Strophe.log = function (level, msg) { _converse.log(level+' '+msg, level); };
-Strophe.error = function (msg) { _converse.log(msg, Strophe.LogLevel.ERROR); };
+Strophe.log = function (level, msg) { log.log(level+' '+msg, level); };
+Strophe.error = function (msg) { log.log(msg, Strophe.LogLevel.ERROR); };
 
 
 /**
@@ -532,7 +499,7 @@ async function attemptNonPreboundSession (credentials, automatic) {
         } else if (!_converse.isTestEnv() && window.PasswordCredential) {
             connect(await getLoginCredentialsFromBrowser());
         } else {
-            _converse.log("attemptNonPreboundSession: Could not find any credentials to log in with", Strophe.LogLevel.WARN);
+            log.warn("attemptNonPreboundSession: Could not find any credentials to log in with");
         }
     } else if ([_converse.ANONYMOUS, _converse.EXTERNAL].includes(_converse.authentication) && (!automatic || _converse.auto_login)) {
         connect();
@@ -577,7 +544,7 @@ function connect (credentials) {
 
 
 async function reconnect () {
-    _converse.log('RECONNECTING: the connection has dropped, attempting to reconnect.');
+    log.debug('RECONNECTING: the connection has dropped, attempting to reconnect.');
     _converse.setConnectionStatus(
         Strophe.Status.RECONNECTING,
         __('The connection has dropped, attempting to reconnect.')
@@ -619,7 +586,7 @@ async function onDomainDiscovered (response) {
     const text = await response.text();
     const xrd = (new window.DOMParser()).parseFromString(text, "text/xml").firstElementChild;
     if (xrd.nodeName != "XRD" || xrd.namespaceURI != "http://docs.oasis-open.org/ns/xri/xrd-1.0") {
-        return _converse.log("Could not discover XEP-0156 connection methods", Strophe.LogLevel.WARN);
+        return log.warn("Could not discover XEP-0156 connection methods");
     }
     const bosh_links = sizzle(`Link[rel="urn:xmpp:alt-connections:xbosh"]`, xrd);
     const ws_links = sizzle(`Link[rel="urn:xmpp:alt-connections:websocket"]`, xrd);
@@ -629,9 +596,8 @@ async function onDomainDiscovered (response) {
     _converse.websocket_url = ws_methods.pop();
     _converse.bosh_service_url = bosh_methods.pop();
     if (bosh_methods.length === 0 && ws_methods.length === 0) {
-        _converse.log(
-            "onDomainDiscovered: neither BOSH nor WebSocket connection methods have been specified with XEP-0156.",
-            Strophe.LogLevel.WARN
+        log.warn(
+            "onDomainDiscovered: neither BOSH nor WebSocket connection methods have been specified with XEP-0156."
         );
     }
 }
@@ -651,13 +617,14 @@ async function discoverConnectionMethods (domain) {
     try {
         response = await fetch(url, options);
     } catch (e) {
-        _converse.log(`Failed to discover alternative connection methods at ${url}`, Strophe.LogLevel.ERROR);
-        return _converse.log(e, Strophe.LogLevel.ERROR);
+        log.error(`Failed to discover alternative connection methods at ${url}`);
+        log.error(e);
+        return;
     }
     if (response.status >= 200 && response.status < 400) {
         await onDomainDiscovered(response);
     } else {
-        _converse.log("Could not discover XEP-0156 connection methods", Strophe.LogLevel.WARN);
+        log.warn("Could not discover XEP-0156 connection methods");
     }
 }
 
@@ -794,12 +761,10 @@ function enableCarbons () {
       .c('enable', {xmlns: Strophe.NS.CARBONS});
     _converse.connection.addHandler((iq) => {
         if (iq.querySelectorAll('error').length > 0) {
-            _converse.log(
-                'An error occurred while trying to enable message carbons.',
-                Strophe.LogLevel.WARN);
+            log.warn('An error occurred while trying to enable message carbons.');
         } else {
             _converse.session.save({'carbons_enabled': true});
-            _converse.log('Message carbons have been enabled.');
+            log.debug('Message carbons have been enabled.');
         }
     }, null, "iq", null, "enablecarbons");
     _converse.connection.send(carbons_iq);
@@ -850,16 +815,16 @@ async function onConnected (reconnecting) {
 
 function setUpXMLLogging () {
     Strophe.log = function (level, msg) {
-        _converse.log(msg, level);
+        log.log(msg, level);
     };
     _converse.connection.xmlInput = function (body) {
         if (_converse.debug) {
-            _converse.log(body.outerHTML, Strophe.LogLevel.DEBUG, 'color: darkgoldenrod');
+            log.log(body.outerHTML, Strophe.LogLevel.DEBUG, 'color: darkgoldenrod');
         }
     };
     _converse.connection.xmlOutput = function (body) {
         if (_converse.debug) {
-            _converse.log(body.outerHTML, Strophe.LogLevel.DEBUG, 'color: darkcyan');
+            log.log(body.outerHTML, Strophe.LogLevel.DEBUG, 'color: darkcyan');
         }
     };
 }
@@ -893,7 +858,7 @@ async function finishInitialization () {
  * @private
  */
 function finishDisconnection () {
-    _converse.log('DISCONNECTED');
+    log.debug('DISCONNECTED');
     delete _converse.connection.reconnecting;
     _converse.connection.reset();
     tearDown();
@@ -941,8 +906,8 @@ async function getLoginCredentials () {
         try {
             credentials = await fetchLoginCredentials(wait); // eslint-disable-line no-await-in-loop
         } catch (e) {
-            _converse.log('Could not fetch login credentials', Strophe.LogLevel.ERROR);
-            _converse.log(e, Strophe.LogLevel.ERROR);
+            log.error('Could not fetch login credentials');
+            log.error(e);
         }
         // If unsuccessful, we wait 2 seconds between subsequent attempts to
         // fetch the credentials.
@@ -1036,7 +1001,7 @@ _converse.initialize = async function (settings, callback) {
             _converse.locale = i18n.getLocale(settings.i18n, _converse.locales);
             await i18n.fetchTranslations(_converse);
         } catch (e) {
-            _converse.log(e.message, Strophe.LogLevel.FATAL);
+            log.fatal(e.message);
         }
     }
 
@@ -1122,17 +1087,17 @@ _converse.initialize = async function (settings, callback) {
      * @memberOf _converse
      */
     this.onConnectStatusChanged = function (status, message) {
-        _converse.log(`Status changed to: ${_converse.CONNECTION_STATUS[status]}`);
+        log.debug(`Status changed to: ${_converse.CONNECTION_STATUS[status]}`);
         if (status === Strophe.Status.CONNECTED || status === Strophe.Status.ATTACHED) {
             _converse.setConnectionStatus(status);
             // By default we always want to send out an initial presence stanza.
             _converse.send_initial_presence = true;
             _converse.setDisconnectionCause();
             if (_converse.connection.reconnecting) {
-                _converse.log(status === Strophe.Status.CONNECTED ? 'Reconnected' : 'Reattached');
+                log.debug(status === Strophe.Status.CONNECTED ? 'Reconnected' : 'Reattached');
                 onConnected(true);
             } else {
-                _converse.log(status === Strophe.Status.CONNECTED ? 'Connected' : 'Attached');
+                log.debug(status === Strophe.Status.CONNECTED ? 'Connected' : 'Attached');
                 if (_converse.connection.restored) {
                     // No need to send an initial presence stanza when
                     // we're restoring an existing session.
@@ -1701,8 +1666,8 @@ _converse.api = {
      */
     send (stanza) {
         if (!_converse.api.connection.connected()) {
-            _converse.log("Not sending stanza because we're not connected!", Strophe.LogLevel.WARN);
-            _converse.log(Strophe.serialize(stanza), Strophe.LogLevel.WARN);
+            log.warn("Not sending stanza because we're not connected!");
+            log.warn(Strophe.serialize(stanza));
             return;
         }
         if (_.isString(stanza)) {
@@ -1844,6 +1809,7 @@ Object.assign(window.converse, {
         'Promise': Promise,
         'Strophe': Strophe,
         '_': _,
+        'log': log,
         'dayjs': dayjs,
         'sizzle': sizzle,
         'utils': u

+ 8 - 7
src/headless/converse-disco.js

@@ -9,6 +9,7 @@
  * Converse plugin which add support for XEP-0030: Service Discovery
  */
 import converse from "./converse-core";
+import log from "./log";
 import sizzle from "sizzle";
 
 const { Backbone, Strophe, $iq, utils, _ } = converse.env;
@@ -146,7 +147,7 @@ converse.plugins.add('converse-disco', {
                 try {
                     stanza = await _converse.api.disco.info(this.get('jid'), null);
                 } catch (iq) {
-                    _converse.log(iq, Strophe.LogLevel.ERROR);
+                    log.error(iq);
                     this.waitUntilFeaturesDiscovered.resolve(this);
                     return;
                 }
@@ -289,7 +290,7 @@ converse.plugins.add('converse-disco', {
                         _converse.api.trigger('streamFeaturesAdded');
                     },
                     error (m, e) {
-                        _converse.log(e, Strophe.LogLevel.ERROR);
+                        log.error(e);
                     }
                 });
             }
@@ -413,7 +414,7 @@ converse.plugins.add('converse-disco', {
                         if (_converse.stream_features === undefined && !_converse.api.connection.connected()) {
                             // Happens during tests when disco lookups happen asynchronously after teardown.
                             const msg = `Tried to get feature ${name} ${xmlns} but _converse.stream_features has been torn down`;
-                            _converse.log(msg, Strophe.LogLevel.WARN);
+                            log.warn(msg);
                             return;
                         }
                         return _converse.stream_features.findWhere({'name': name, 'xmlns': xmlns});
@@ -573,7 +574,7 @@ converse.plugins.add('converse-disco', {
                         if (_converse.disco_entities === undefined && !_converse.api.connection.connected()) {
                             // Happens during tests when disco lookups happen asynchronously after teardown.
                             const msg = `Tried to look up entity ${jid} but _converse.disco_entities has been torn down`;
-                            _converse.log(msg, Strophe.LogLevel.WARN);
+                            log.warn(msg);
                             return;
                         }
                         const entity = _converse.disco_entities.get(jid);
@@ -635,7 +636,7 @@ converse.plugins.add('converse-disco', {
                         if (_converse.disco_entities === undefined && !_converse.api.connection.connected()) {
                             // Happens during tests when disco lookups happen asynchronously after teardown.
                             const msg = `Tried to get feature ${feature} for ${jid} but _converse.disco_entities has been torn down`;
-                            _converse.log(msg, Strophe.LogLevel.WARN);
+                            log.warn(msg);
                             return;
                         }
                         entity = await entity.waitUntilFeaturesDiscovered;
@@ -771,14 +772,14 @@ converse.plugins.add('converse-disco', {
                  *             // The entity DOES NOT have this identity
                  *         }
                  *     }
-                 * ).catch(e => _converse.log(e, Strophe.LogLevel.FATAL));
+                 * ).catch(e => log.error(e));
                  */
                 async getIdentity (category, type, jid) {
                     const e = await _converse.api.disco.entities.get(jid, true);
                     if (e === undefined && !_converse.api.connection.connected()) {
                         // Happens during tests when disco lookups happen asynchronously after teardown.
                         const msg = `Tried to look up category ${category} for ${jid} but _converse.disco_entities has been torn down`;
-                        _converse.log(msg, Strophe.LogLevel.WARN);
+                        log.warn(msg);
                         return;
                     }
                     return e.getIdentity(category, type);

+ 11 - 14
src/headless/converse-mam.js

@@ -13,6 +13,7 @@ import "./converse-disco";
 import "./converse-rsm";
 import { intersection, pick } from 'lodash'
 import converse from "./converse-core";
+import log from "./log";
 import sizzle from "sizzle";
 
 const { Strophe, $iq, dayjs } = converse.env;
@@ -131,7 +132,7 @@ converse.plugins.add('converse-mam', {
                     try {
                         await message_handler(message);
                     } catch (e) {
-                        _converse.log(e, Strophe.LogLevel.ERROR);
+                        log.error(e);
                     }
                 }
 
@@ -188,14 +189,10 @@ converse.plugins.add('converse-mam', {
 
         _converse.onMAMError = function (iq) {
             if (iq.querySelectorAll('feature-not-implemented').length) {
-                _converse.log(
-                    "Message Archive Management (XEP-0313) not supported by this server",
-                    Strophe.LogLevel.WARN);
+                log.warn("Message Archive Management (XEP-0313) not supported by this server");
             } else {
-                _converse.log(
-                    "An error occured while trying to set archiving preferences.",
-                    Strophe.LogLevel.ERROR);
-                _converse.log(iq);
+                log.error("An error occured while trying to set archiving preferences.");
+                log.error(iq);
             }
         };
 
@@ -471,7 +468,7 @@ converse.plugins.add('converse-mam', {
                     const jid = attrs.to || _converse.bare_jid;
                     const supported = await _converse.api.disco.supports(Strophe.NS.MAM, jid);
                     if (!supported) {
-                        _converse.log(`Did not fetch MAM archive for ${jid} because it doesn't support ${Strophe.NS.MAM}`);
+                        log.warn(`Did not fetch MAM archive for ${jid} because it doesn't support ${Strophe.NS.MAM}`);
                         return {'messages': []};
                     }
 
@@ -513,11 +510,11 @@ converse.plugins.add('converse-mam', {
                         const from = stanza.getAttribute('from') || _converse.bare_jid;
                         if (options.groupchat) {
                             if (from !== options['with']) {
-                                _converse.log(`Ignoring alleged groupchat MAM message from ${stanza.getAttribute('from')}`, Strophe.LogLevel.WARN);
+                                log.warn(`Ignoring alleged groupchat MAM message from ${stanza.getAttribute('from')}`);
                                 return true;
                             }
                         } else if (from !== _converse.bare_jid) {
-                            _converse.log(`Ignoring alleged MAM message from ${stanza.getAttribute('from')}`, Strophe.LogLevel.WARN);
+                            log.warn(`Ignoring alleged MAM message from ${stanza.getAttribute('from')}`);
                             return true;
                         }
                         messages.push(stanza);
@@ -528,13 +525,13 @@ converse.plugins.add('converse-mam', {
                     const iq_result = await _converse.api.sendIQ(stanza, _converse.message_archiving_timeout, false)
                     if (iq_result === null) {
                         const err_msg = "Timeout while trying to fetch archived messages.";
-                        _converse.log(err_msg, Strophe.LogLevel.ERROR);
+                        log.error(err_msg);
                         error = new _converse.TimeoutError(err_msg);
                         return { messages, error };
 
                     } else if (u.isErrorStanza(iq_result)) {
-                        _converse.log("Error stanza received while trying to fetch archived messages", Strophe.LogLevel.ERROR);
-                        _converse.log(iq_result, Strophe.LogLevel.ERROR);
+                        log.error("Error stanza received while trying to fetch archived messages");
+                        log.error(iq_result);
                         return { messages };
                     }
                     _converse.connection.deleteHandler(message_handler);

+ 24 - 50
src/headless/converse-muc.js

@@ -14,6 +14,7 @@ import "./converse-emoji";
 import "./utils/muc";
 import { clone, get, intersection, invoke, isElement, isObject, isString, uniq, zipObject } from "lodash";
 import converse from "./converse-core";
+import log from "./log";
 import u from "./utils/form";
 
 const MUC_ROLE_WEIGHTS = {
@@ -199,10 +200,7 @@ converse.plugins.add('converse-muc', {
 
         async function openRoom (jid) {
             if (!u.isValidMUCJID(jid)) {
-                return _converse.log(
-                    `Invalid JID "${jid}" provided in URL fragment`,
-                    Strophe.LogLevel.WARN
-                );
+                return log.warn(`invalid jid "${jid}" provided in url fragment`);
             }
             await _converse.api.waitUntil('roomsAutoJoined');
             if (_converse.allow_bookmarks) {
@@ -266,10 +264,7 @@ converse.plugins.add('converse-muc', {
                 delete this.occupant;
                 const chatbox = get(this, 'collection.chatbox');
                 if (!chatbox) {
-                    return _converse.log(
-                        `Could not get collection.chatbox for message: ${JSON.stringify(this.toJSON())}`,
-                        Strophe.LogLevel.ERROR
-                    );
+                    return log.error(`Could not get collection.chatbox for message: ${JSON.stringify(this.toJSON())}`);
                 }
                 this.listenTo(chatbox.occupants, 'add', this.onOccupantAdded);
             },
@@ -280,10 +275,7 @@ converse.plugins.add('converse-muc', {
                     this.listenTo(this.occupant, 'destroy', this.onOccupantRemoved);
                     const chatbox = get(this, 'collection.chatbox');
                     if (!chatbox) {
-                        return _converse.log(
-                            `Could not get collection.chatbox for message: ${JSON.stringify(this.toJSON())}`,
-                            Strophe.LogLevel.ERROR
-                        );
+                        return log.error(`Could not get collection.chatbox for message: ${JSON.stringify(this.toJSON())}`);
                     }
                     this.stopListening(chatbox.occupants, 'add', this.onOccupantAdded);
                 }
@@ -293,10 +285,7 @@ converse.plugins.add('converse-muc', {
                 if (this.get('type') !== 'groupchat') { return; }
                 const chatbox = get(this, 'collection.chatbox');
                 if (!chatbox) {
-                    return _converse.log(
-                        `Could not get collection.chatbox for message: ${JSON.stringify(this.toJSON())}`,
-                        Strophe.LogLevel.ERROR
-                    );
+                    return log.error(`Could not get collection.chatbox for message: ${JSON.stringify(this.toJSON())}`);
                 }
                 const nick = Strophe.getResourceFromJid(this.get('from'));
                 this.occupant = chatbox.occupants.findWhere({'nick': nick});
@@ -330,10 +319,7 @@ converse.plugins.add('converse-muc', {
                         if (jid) {
                             vcard = _converse.vcards.findWhere({'jid': jid}) || _converse.vcards.create({'jid': jid});
                         } else {
-                            _converse.log(
-                                `Could not assign VCard for message because no JID found! msgid: ${this.get('msgid')}`,
-                                Strophe.LogLevel.ERROR
-                            );
+                            log.error(`Could not assign VCard for message because no JID found! msgid: ${this.get('msgid')}`);
                             return;
                         }
                     }
@@ -429,10 +415,7 @@ converse.plugins.add('converse-muc', {
 
             async enterRoom () {
                 const conn_status = this.get('connection_status');
-                _converse.log(
-                    `${this.get('jid')} initialized with connection_status ${conn_status}`,
-                    Strophe.LogLevel.DEBUG
-                );
+                log.debug(`${this.get('jid')} initialized with connection_status ${conn_status}`);
                 if (conn_status !==  converse.ROOMSTATUS.ENTERED) {
                     // We're not restoring a room from cache, so let's clear the potentially stale cache.
                     this.removeNonMembers();
@@ -526,7 +509,7 @@ converse.plugins.add('converse-muc', {
                             // MAM messages are handled in converse-mam.
                             // We shouldn't get MAM messages here because
                             // they shouldn't have a `type` attribute.
-                            _converse.log(`Received a MAM message with type "chat".`, Strophe.LogLevel.WARN);
+                            log.warn(`received a mam message with type "chat".`);
                             return true;
                         }
                         this.onMessage(stanza);
@@ -651,7 +634,7 @@ converse.plugins.add('converse-muc', {
                         return this.features.destroy({success, 'error': (m, e) => reject(e)})
                     });
                 } catch (e) {
-                    _converse.log(e, Strophe.LogLevel.ERROR);
+                    log.error(e);
                 }
                 return _converse.ChatBox.prototype.close.call(this);
             },
@@ -851,7 +834,7 @@ converse.plugins.add('converse-muc', {
                     identity = await _converse.api.disco.getIdentity('conference', 'text', this.get('jid'));
                 } catch (e) {
                     // Getting the identity probably failed because this room doesn't exist yet.
-                    return _converse.log(e, Strophe.LogLevel.ERROR);
+                    return log.error(e);
                 }
                 const fields = await _converse.api.disco.getFields(this.get('jid'));
                 this.save({
@@ -1126,15 +1109,15 @@ converse.plugins.add('converse-muc', {
                 if (result === null) {
                     const err_msg = `Error: timeout while fetching ${affiliation} list for MUC ${this.get('jid')}`;
                     const err = new Error(err_msg);
-                    _converse.log(err_msg, Strophe.LogLevel.WARN);
-                    _converse.log(result, Strophe.LogLevel.WARN);
+                    log.warn(err_msg);
+                    log.warn(result);
                     return err;
                 }
                 if (u.isErrorStanza(result)) {
                     const err_msg = `Error: not allowed to fetch ${affiliation} list for MUC ${this.get('jid')}`;
                     const err = new Error(err_msg);
-                    _converse.log(err_msg, Strophe.LogLevel.WARN);
-                    _converse.log(result, Strophe.LogLevel.WARN);
+                    log.warn(err_msg);
+                    log.warn(result);
                     return err;
                 }
                 return u.parseMemberListIQ(result).filter(p => p);
@@ -1227,12 +1210,12 @@ converse.plugins.add('converse-muc', {
                     } else if (sizzle(`registration-required[xmlns="${Strophe.NS.STANZAS}"]`, e).length) {
                         err_msg = __("You're not allowed to register in this groupchat because it's members-only.");
                     }
-                    _converse.log(e, Strophe.LogLevel.ERROR);
+                    log.error(e);
                     return err_msg;
                 }
                 const required_fields = sizzle('field required', iq).map(f => f.parentElement);
                 if (required_fields.length > 1 && required_fields[0].getAttribute('var') !== 'muc#register_roomnick') {
-                    return _converse.log(`Can't register the user register in the groupchat ${jid} due to the required fields`);
+                    return log.error(`Can't register the user register in the groupchat ${jid} due to the required fields`);
                 }
                 try {
                     await _converse.api.sendIQ($iq({
@@ -1250,8 +1233,8 @@ converse.plugins.add('converse-muc', {
                     } else if (sizzle(`bad-request[xmlns="${Strophe.NS.STANZAS}"]`, e).length) {
                         err_msg = __("Can't register your nickname in this groupchat, invalid data form supplied.");
                     }
-                    _converse.log(err_msg);
-                    _converse.log(e, Strophe.LogLevel.ERROR);
+                    log.error(err_msg);
+                    log.error(e);
                     return err_msg;
                 }
             },
@@ -1517,18 +1500,14 @@ converse.plugins.add('converse-muc', {
                 const original_stanza = stanza;
                 const bare_forward = sizzle(`message > forwarded[xmlns="${Strophe.NS.FORWARD}"]`, stanza).length;
                 if (bare_forward) {
-                    return _converse.log(
-                        'onMessage: Ignoring unencapsulated forwarded groupchat message',
-                        Strophe.LogLevel.WARN
-                    );
+                    return log.warn('onMessage: Ignoring unencapsulated forwarded groupchat message');
                 }
                 const is_carbon = u.isCarbonMessage(stanza);
                 if (is_carbon) {
                     // XEP-280: groupchat messages SHOULD NOT be carbon copied, so we're discarding it.
-                    return _converse.log(
+                    return log.warn(
                         'onMessage: Ignoring XEP-0280 "groupchat" message carbon, '+
-                        'according to the XEP groupchat messages SHOULD NOT be carbon copied',
-                        Strophe.LogLevel.WARN
+                        'according to the XEP groupchat messages SHOULD NOT be carbon copied'
                     );
                 }
                 const is_mam = u.isMAMMessage(stanza);
@@ -1537,10 +1516,7 @@ converse.plugins.add('converse-muc', {
                         const selector = `[xmlns="${Strophe.NS.MAM}"] > forwarded[xmlns="${Strophe.NS.FORWARD}"] > message`;
                         stanza = sizzle(selector, stanza).pop();
                     } else {
-                        return _converse.log(
-                            `onMessage: Ignoring alleged MAM groupchat message from ${stanza.getAttribute('from')}`,
-                            Strophe.LogLevel.WARN
-                        );
+                        return log.warn(`onMessage: Ignoring alleged MAM groupchat message from ${stanza.getAttribute('from')}`);
                     }
                 }
 
@@ -2097,9 +2073,7 @@ converse.plugins.add('converse-muc', {
                 } else if (isObject(groupchat)) {
                     _converse.api.rooms.open(groupchat.jid, clone(groupchat));
                 } else {
-                    _converse.log(
-                        'Invalid groupchat criteria specified for "auto_join_rooms"',
-                        Strophe.LogLevel.ERROR);
+                    log.error('Invalid groupchat criteria specified for "auto_join_rooms"');
                 }
             });
             /**
@@ -2256,7 +2230,7 @@ converse.plugins.add('converse-muc', {
                     await _converse.api.waitUntil('chatBoxesFetched');
                     if (jids === undefined) {
                         const err_msg = 'rooms.open: You need to provide at least one JID';
-                        _converse.log(err_msg, Strophe.LogLevel.ERROR);
+                        log.error(err_msg);
                         throw(new TypeError(err_msg));
                     } else if (isString(jids)) {
                         const room = await _converse.api.rooms.create(jids, attrs);

+ 5 - 3
src/headless/converse-ping.js

@@ -10,6 +10,8 @@
  * as specified in XEP-0199 XMPP Ping.
  */
 import converse from "./converse-core";
+import log from "./log";
+
 const { Strophe, $iq } = converse.env;
 const u = converse.env.utils;
 
@@ -104,13 +106,13 @@ converse.plugins.add('converse-ping', {
 
                     const result = await _converse.api.sendIQ(iq, 10000, false);
                     if (result === null) {
-                        _converse.log(`Timeout while pinging ${jid}`, Strophe.LogLevel.WARN);
+                        log.warn(`Timeout while pinging ${jid}`);
                         if (jid === Strophe.getDomainFromJid(_converse.bare_jid)) {
                             _converse.api.connection.reconnect();
                         }
                     } else if (u.isErrorStanza(result)) {
-                        _converse.log(`Error while pinging ${jid}`, Strophe.LogLevel.ERROR);
-                        _converse.log(result, Strophe.LogLevel.ERROR);
+                        log.error(`Error while pinging ${jid}`);
+                        log.error(result);
                     }
                     return true;
                 }

+ 4 - 6
src/headless/converse-pubsub.js

@@ -8,6 +8,7 @@
  */
 import "./converse-disco";
 import converse from "./converse-core";
+import log from "./log";
 
 const { Strophe, $iq } = converse.env;
 
@@ -68,8 +69,8 @@ converse.plugins.add('converse-pubsub', {
 
                             Object.keys(options).forEach(k => stanza.c('field', {'var': k}).c('value').t(options[k]).up().up());
                         } else {
-                            _converse.log(`_converse.api.publish: ${jid} does not support #publish-options, `+
-                                          `so we didn't set them even though they were provided.`)
+                            log.warn(`_converse.api.publish: ${jid} does not support #publish-options, `+
+                                     `so we didn't set them even though they were provided.`)
                         }
                     }
                     try {
@@ -83,10 +84,7 @@ converse.plugins.add('converse-pubsub', {
                             // met. We re-publish but without publish-options.
                             const el = stanza.nodeTree;
                             el.querySelector('publish-options').outerHTML = '';
-                            _converse.log(
-                                `PubSub: Republishing without publish options. ${el.outerHTML}`,
-                                Strophe.LogLevel.WARN
-                            );
+                            log.warn(`PubSub: Republishing without publish options. ${el.outerHTML}`);
                             _converse.api.sendIQ(el);
                         } else {
                             throw iq;

+ 12 - 11
src/headless/converse-roster.js

@@ -8,6 +8,7 @@
  */
 import "@converse/headless/converse-status";
 import converse from "@converse/headless/converse-core";
+import log from "./log";
 
 const { Backbone, Strophe, $iq, $pres, dayjs, sizzle, _ } = converse.env;
 const u = converse.env.utils;
@@ -108,7 +109,7 @@ converse.plugins.add('converse-roster', {
                 await _converse.roster.fetchRosterContacts();
                 _converse.api.trigger('rosterContactsFetched');
             } catch (reason) {
-                _converse.log(reason, Strophe.LogLevel.ERROR);
+                log.error(reason);
             } finally {
                 _converse.sendInitialPresence();
             }
@@ -424,7 +425,7 @@ converse.plugins.add('converse-roster', {
                     });
                 });
                 if (u.isErrorObject(result)) {
-                    _converse.log(result, Strophe.LogLevel.ERROR);
+                    log.error(result);
                     // Force a full roster refresh
                     _converse.session.set('roster_cached', false)
                     this.data.save('version', undefined);
@@ -516,7 +517,7 @@ converse.plugins.add('converse-roster', {
                 try {
                     await this.sendContactAddIQ(jid, name, groups);
                 } catch (e) {
-                    _converse.log(e, Strophe.LogLevel.ERROR);
+                    log.error(e);
                     alert(__('Sorry, there was an error while trying to add %1$s as a contact.', name || jid));
                     return e;
                 }
@@ -568,9 +569,8 @@ converse.plugins.add('converse-roster', {
                     // attribute (i.e., implicitly from the bare JID of the user's
                     // account) or it has a 'from' attribute whose value matches the
                     // user's bare JID <user@domainpart>.
-                    _converse.log(
-                        `Ignoring roster illegitimate roster push message from ${iq.getAttribute('from')}`,
-                        Strophe.LogLevel.WARN
+                    log.warn(
+                        `Ignoring roster illegitimate roster push message from ${iq.getAttribute('from')}`
                     );
                     return;
                 }
@@ -581,12 +581,12 @@ converse.plugins.add('converse-roster', {
 
                 const items = sizzle(`item`, query);
                 if (items.length > 1) {
-                    _converse.log(iq, Strophe.LogLevel.ERROR);
+                    log.error(iq);
                     throw new Error('Roster push query may not contain more than one "item" element.');
                 }
                 if (items.length === 0) {
-                    _converse.log(iq, Strophe.LogLevel.WARN);
-                    _converse.log('Received a roster push stanza without an "item" element.', Strophe.LogLevel.WARN);
+                    log.warn(iq);
+                    log.warn('Received a roster push stanza without an "item" element.');
                     return;
                 }
                 this.updateContact(items.pop());
@@ -628,8 +628,9 @@ converse.plugins.add('converse-roster', {
                     }
                 } else if (!u.isServiceUnavailableError(iq)) {
                     // Some unknown error happened, so we will try to fetch again if the page reloads.
-                    _converse.log(iq, Strophe.LogLevel.ERROR);
-                    return _converse.log("Error while trying to fetch roster from the server", Strophe.LogLevel.ERROR);
+                    log.error(iq);
+                    log.error("Error while trying to fetch roster from the server");
+                    return;
                 }
                 _converse.session.save('roster_cached', true);
                 /**

+ 9 - 8
src/headless/converse-smacks.js

@@ -10,8 +10,9 @@
  * Converse.js plugin which adds support for XEP-0198: Stream Management
  */
 import converse from "./converse-core";
+import log from "./log";
 
-const { Strophe, } = converse.env;
+const { Strophe } = converse.env;
 const u = converse.env.utils;
 
 
@@ -50,7 +51,7 @@ converse.plugins.add('converse-smacks', {
             if (delta < 0) {
                 const err_msg = `New reported stanza count lower than previous. `+
                     `New: ${handled} - Previous: ${last_known_handled}`
-                _converse.log(err_msg, Strophe.LogLevel.ERROR);
+                log.error(err_msg);
             }
             const unacked_stanzas = _converse.session.get('unacked_stanzas');
             if (delta > unacked_stanzas.length) {
@@ -59,7 +60,7 @@ converse.plugins.add('converse-smacks', {
                     `Reported Acknowledged Count: ${delta} -`+
                     `Unacknowledged Stanza Count: ${unacked_stanzas.length} -`+
                     `New: ${handled} - Previous: ${last_known_handled}`
-                _converse.log(err_msg, Strophe.LogLevel.ERROR);
+                log.error(err_msg);
             }
             _converse.session.save({
                 'num_stanzas_handled_by_server': handled,
@@ -125,11 +126,11 @@ converse.plugins.add('converse-smacks', {
                 //
                 // After resource binding, sendEnableStanza will be called
                 // based on the afterResourceBinding event.
-                _converse.log('Could not resume previous SMACKS session, session id not found. '+
-                              'A new session will be established.', Strophe.LogLevel.WARN);
+                log.warn('Could not resume previous SMACKS session, session id not found. '+
+                         'A new session will be established.');
             } else {
-                _converse.log('Failed to enable stream management', Strophe.LogLevel.ERROR);
-                _converse.log(el.outerHTML, Strophe.LogLevel.ERROR);
+                log.error('Failed to enable stream management');
+                log.error(el.outerHTML);
             }
             resetSessionData();
             /**
@@ -224,7 +225,7 @@ converse.plugins.add('converse-smacks', {
 
         function onStanzaSent (stanza) {
             if (!_converse.session) {
-                _converse.log('No _converse.session!', Strophe.LogLevel.WARN);
+                log.warn('No _converse.session!');
                 return;
             }
             if (!_converse.session.get('smacks_enabled')) {

+ 87 - 0
src/headless/log.js

@@ -0,0 +1,87 @@
+import * as strophe from 'strophe.js/src/core';
+import { get, isElement } from 'lodash';
+
+const Strophe = strophe.default.Strophe;
+
+const logger = Object.assign({
+    'debug': get(console, 'log') ? console.log.bind(console) : function noop () {},
+    'error': get(console, 'log') ? console.log.bind(console) : function noop () {},
+    'info': get(console, 'log') ? console.log.bind(console) : function noop () {},
+    'warn': get(console, 'log') ? console.log.bind(console) : function noop () {}
+}, console);
+
+
+/**
+ * The log namespace
+ * @namespace log
+ */
+const log = {
+
+    /**
+     * Initialize the logger by setting the loglevel
+     * @method log#initialize
+     * @param { string } message - The message to be logged
+     * @param { integer } level - The loglevel which allows for filtering of log messages
+     */
+    initialize (loglevel) {
+        this.loglevel = loglevel;
+    },
+
+    /**
+     * Logs messages to the browser's developer console.
+     * Available loglevels are 0 for 'debug', 1 for 'info', 2 for 'warn',
+     * 3 for 'error' and 4 for 'fatal'.
+     * When using the 'error' or 'warn' loglevels, a full stacktrace will be
+     * logged as well.
+     * @method log#log
+     * @param { string } message - The message to be logged
+     * @param { integer } level - The loglevel which allows for filtering of log messages
+     */
+    log (message, level, style='') {
+        if (level === Strophe.LogLevel.ERROR || level === Strophe.LogLevel.FATAL) {
+            style = style || 'color: maroon';
+        } else if (level === Strophe.LogLevel.DEBUG) {
+            style = style || 'color: green';
+        }
+
+        if (message instanceof Error) {
+            message = message.stack;
+        } else if (isElement(message)) {
+            message = message.outerHTML;
+        }
+        const prefix = style ? '%c' : '';
+        if (level === Strophe.LogLevel.ERROR) {
+            logger.error(`${prefix} ERROR: ${message}`, style);
+        } else if (level === Strophe.LogLevel.WARN) {
+            logger.warn(`${prefix} ${(new Date()).toISOString()} WARNING: ${message}`, style);
+        } else if (level === Strophe.LogLevel.FATAL) {
+            logger.error(`${prefix} FATAL: ${message}`, style);
+        } else if (this.loglevel === Strophe.LogLevel.DEBUG && level === Strophe.LogLevel.DEBUG) {
+            logger.debug(`${prefix} ${(new Date()).toISOString()} DEBUG: ${message}`, style);
+        } else if (this.loglevel === Strophe.LogLevel.INFO) {
+            logger.info(`${prefix} ${(new Date()).toISOString()} INFO: ${message}`, style);
+        }
+    },
+
+    debug (message) {
+        this.log(message, Strophe.LogLevel.DEBUG);
+    },
+
+    error (message) {
+        this.log(message, Strophe.LogLevel.ERROR);
+    },
+
+    info (message) {
+        this.log(message, Strophe.LogLevel.INFO);
+    },
+
+    warn (message) {
+        this.log(message, Strophe.LogLevel.WARN);
+    },
+
+    fatal (message) {
+        this.log(message, Strophe.LogLevel.FATAL);
+    }
+}
+
+export default log;

+ 2 - 7
src/headless/utils/core.js

@@ -9,6 +9,7 @@
 import * as strophe from 'strophe.js/src/core';
 import Backbone from "backbone";
 import _ from "../lodash.noconflict";
+import log from "@converse/headless/log";
 import sizzle from "sizzle";
 
 const Strophe = strophe.default.Strophe;
@@ -19,12 +20,6 @@ const Strophe = strophe.default.Strophe;
  */
 const u = {};
 
-u.logger = Object.assign({
-    'debug': _.get(console, 'log') ? console.log.bind(console) : function noop () {},
-    'error': _.get(console, 'log') ? console.log.bind(console) : function noop () {},
-    'info': _.get(console, 'log') ? console.log.bind(console) : function noop () {},
-    'warn': _.get(console, 'log') ? console.log.bind(console) : function noop () {}
-}, console);
 
 u.isTagEqual = function (stanza, name) {
     if (stanza.nodeTree) {
@@ -643,7 +638,7 @@ u.waitUntil = function (func, max_wait=300, check_delay=3) {
     const max_wait_timeout = setTimeout(() => {
         clearTimers(max_wait_timeout, interval);
         const err_msg = 'Wait until promise timed out';
-        u.logger.error(err_msg);
+        log.error(err_msg);
         promise.reject(new Error(err_msg));
     }, max_wait);