diff --git a/remoteappmanager/handlers/home_handler.py b/remoteappmanager/handlers/home_handler.py
index c1648de..5b50efc 100644
--- a/remoteappmanager/handlers/home_handler.py
+++ b/remoteappmanager/handlers/home_handler.py
@@ -1,6 +1,5 @@
 import socket
 import os
-import uuid
 from datetime import timedelta
 
 import errno
@@ -45,12 +44,11 @@ class HomeHandler(BaseHandler):
 
         try:
             yield handler(options)
-        except Exception as e:
+        except Exception as exc:
             # Create a random reference number for support
-            ref = str(uuid.uuid1())
-            self.log.exception("Failed with POST action: {0}. {1} "
-                               "Ref: {2}".format(
-                                   action, str(e), ref))
+            ref = self.log.issue(
+                "Failed with POST action {}".format(action),
+                exc)
 
             images_info = yield self._get_images_info()
 
@@ -63,7 +61,7 @@ class HomeHandler(BaseHandler):
                         images_info=images_info,
                         error_message=message.format(
                             action=action,
-                            error_type=type(e).__name__,
+                            error_type=type(exc).__name__,
                             ref=ref))
 
     # Subhandling after post
diff --git a/remoteappmanager/logging/logging_mixin.py b/remoteappmanager/logging/logging_mixin.py
index 6339a43..fddc6f6 100644
--- a/remoteappmanager/logging/logging_mixin.py
+++ b/remoteappmanager/logging/logging_mixin.py
@@ -1,3 +1,6 @@
+import uuid
+import types
+
 from traitlets import (
     HasTraits,
     Instance,
@@ -5,6 +8,25 @@ from traitlets import (
 )
 
 
+def issue(self, message, exc=None):
+    """Accepts a message that will be logged with an additional reference
+    code for easy log lookup.
+
+    The identifier will be returned for inclusion in user-visible
+    error messages.
+    """
+    ref = str(uuid.uuid1())
+
+    if exc is None:
+        err_message = "{}. Ref: {}".format(message, ref)
+    else:
+        err_message = "{} : {}. Ref: {}".format(
+            message, str(exc), ref)
+
+    self.error(err_message)
+    return ref
+
+
 class LoggingMixin(HasTraits):
     """A HasTrait class that provides logging. Used as a mixin.
     """
@@ -13,5 +35,9 @@ class LoggingMixin(HasTraits):
 
     @default('log')
     def _log_default(self):
-        from traitlets import log
-        return log.get_logger()
+        from tornado.log import app_log
+
+        # monkey patch the logger to provide an additional method that handles
+        # issues
+        app_log.issue = types.MethodType(issue, app_log)
+        return app_log

Formalise error handling with the logger and the refnumber for the issue
Currently when an error takes place in a number of places we generate a `refnumber` that can be displayed in the user when an error takes place. This `refnumber` is at the same time logged next to the related error. This will help to pinpoint the place in the logs where a specific error (reported by a user) took place and simplify debugging.

To achieve this I would suggest one of the following:

- Wrap the python `Logger`  logging functions so that they generate a refnumber, pass it to the logger and then return it to the caller.
- Provide a generic `log(self, level, msg, *args, **kwargs)` function in the `BaseHandler` that will generate the refnumber pass it to a python logger and return it.

I think that the second implementation is simpler and easier to test. 


diff --git a/tests/logging/__init__.py b/tests/logging/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/logging/test_logging_mixin.py b/tests/logging/test_logging_mixin.py
new file mode 100644
index 0000000..833562e
--- /dev/null
+++ b/tests/logging/test_logging_mixin.py
@@ -0,0 +1,19 @@
+import unittest
+from unittest import mock
+
+from remoteappmanager.logging.logging_mixin import LoggingMixin
+
+
+class Logged(LoggingMixin):
+    pass
+
+
+class TestLoggingMixin(unittest.TestCase):
+    def test_issue(self):
+        l = Logged()
+        with mock.patch("tornado.log.app_log.error") as mock_error:
+            ref = l.log.issue("hello")
+            self.assertIn("hello. Ref: "+str(ref), mock_error.call_args[0][0])
+            ref = l.log.issue("hello", Exception("Boom!"))
+            self.assertIn("hello : Boom!. Ref: "+str(ref),
+                          mock_error.call_args[0][0])

pip install -e .[dev]
parse_log_pytest
requirements.txt
flake8 sphinx coverage pytest
apt-get update apt-get install -y gcc
3.9
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.16
alembic==1.15.2
annotated-types==0.7.0
arrow==1.3.0
async-generator==1.10
attrs==25.3.0
babel==2.17.0
certifi==2025.1.31
certipy==0.2.2
cffi==1.17.1
charset-normalizer==3.4.1
click==8.1.8
coverage==7.8.0
cryptography==44.0.2
docker-py==1.10.6
docker-pycreds==0.4.0
docutils==0.21.2
escapism==1.0.1
exceptiongroup==1.2.2
flake8==7.2.0
fqdn==1.5.1
greenlet==3.1.1
idna==3.10
imagesize==1.4.1
importlib_metadata==8.6.1
iniconfig==2.1.0
isoduration==20.11.0
Jinja2==3.1.6
jsonpointer==3.0.0
jsonschema==4.23.0
jsonschema-specifications==2024.10.1
jupyter-events==0.12.0
jupyter_client==8.6.3
jupyter_core==5.7.2
jupyterhub==5.2.1
Mako==1.3.9
MarkupSafe==3.0.2
mccabe==0.7.0
oauthlib==3.2.2
packaging==24.2
pamela==1.2.0
platformdirs==4.3.7
pluggy==1.5.0
prometheus_client==0.21.1
pycodestyle==2.13.0
pycparser==2.22
pydantic==2.11.1
pydantic_core==2.33.0
pyflakes==3.3.1
Pygments==2.19.1
pytest==8.3.5
python-dateutil==2.9.0.post0
python-json-logger==3.3.0
PyYAML==6.0.2
pyzmq==26.3.0
referencing==0.36.2
-e git+https://github.com/simphony/simphony-remote.git@05225db5d853d5b00bc1135763affe0f9935ea99#egg=remoteappmanager
requests==2.32.3
rfc3339-validator==0.1.4
rfc3986-validator==0.1.1
rpds-py==0.24.0
six==1.17.0
snowballstemmer==2.2.0
Sphinx==7.4.7
sphinxcontrib-applehelp==2.0.0
sphinxcontrib-devhelp==2.0.0
sphinxcontrib-htmlhelp==2.1.0
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==2.0.0
sphinxcontrib-serializinghtml==2.0.0
SQLAlchemy==2.0.40
tabulate==0.9.0
tomli==2.2.1
tornado==6.4.2
traitlets==5.14.3
types-python-dateutil==2.9.0.20241206
typing-inspection==0.4.0
typing_extensions==4.13.0
uri-template==1.3.0
urllib3==2.3.0
webcolors==24.11.1
websocket-client==1.8.0
zipp==3.21.0

name: simphony-remote
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.16
      - alembic==1.15.2
      - annotated-types==0.7.0
      - arrow==1.3.0
      - async-generator==1.10
      - attrs==25.3.0
      - babel==2.17.0
      - certifi==2025.1.31
      - certipy==0.2.2
      - cffi==1.17.1
      - charset-normalizer==3.4.1
      - click==8.1.8
      - coverage==7.8.0
      - cryptography==44.0.2
      - docker-py==1.10.6
      - docker-pycreds==0.4.0
      - docutils==0.21.2
      - escapism==1.0.1
      - exceptiongroup==1.2.2
      - flake8==7.2.0
      - fqdn==1.5.1
      - greenlet==3.1.1
      - idna==3.10
      - imagesize==1.4.1
      - importlib-metadata==8.6.1
      - iniconfig==2.1.0
      - isoduration==20.11.0
      - jinja2==3.1.6
      - jsonpointer==3.0.0
      - jsonschema==4.23.0
      - jsonschema-specifications==2024.10.1
      - jupyter-client==8.6.3
      - jupyter-core==5.7.2
      - jupyter-events==0.12.0
      - jupyterhub==5.2.1
      - mako==1.3.9
      - markupsafe==3.0.2
      - mccabe==0.7.0
      - oauthlib==3.2.2
      - packaging==24.2
      - pamela==1.2.0
      - platformdirs==4.3.7
      - pluggy==1.5.0
      - prometheus-client==0.21.1
      - pycodestyle==2.13.0
      - pycparser==2.22
      - pydantic==2.11.1
      - pydantic-core==2.33.0
      - pyflakes==3.3.1
      - pygments==2.19.1
      - pytest==8.3.5
      - python-dateutil==2.9.0.post0
      - python-json-logger==3.3.0
      - pyyaml==6.0.2
      - pyzmq==26.3.0
      - referencing==0.36.2
      - requests==2.32.3
      - rfc3339-validator==0.1.4
      - rfc3986-validator==0.1.1
      - rpds-py==0.24.0
      - six==1.17.0
      - snowballstemmer==2.2.0
      - sphinx==7.4.7
      - sphinxcontrib-applehelp==2.0.0
      - sphinxcontrib-devhelp==2.0.0
      - sphinxcontrib-htmlhelp==2.1.0
      - sphinxcontrib-jsmath==1.0.1
      - sphinxcontrib-qthelp==2.0.0
      - sphinxcontrib-serializinghtml==2.0.0
      - sqlalchemy==2.0.40
      - tabulate==0.9.0
      - tomli==2.2.1
      - tornado==6.4.2
      - traitlets==5.14.3
      - types-python-dateutil==2.9.0.20241206
      - typing-extensions==4.13.0
      - typing-inspection==0.4.0
      - uri-template==1.3.0
      - urllib3==2.3.0
      - webcolors==24.11.1
      - websocket-client==1.8.0
      - zipp==3.21.0
prefix: /opt/conda/envs/simphony-remote

tests/logging/test_logging_mixin.py::TestLoggingMixin::test_issue
remoteappmanager/logging/logging_mixin.py remoteappmanager/handlers/home_handler.py
remoteappmanager/logging/logging_mixin.py remoteappmanager/handlers/home_handler.py