diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 2f6a082..a2568d8 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -5,6 +5,10 @@ Dozer Changelog
 ----------------
 
 - Add support for Python 3.8.
+- Add UI input for existing "floor" query string parameter
+  (https://github.com/mgedmin/dozer/issues/2)
+- Add UI input to filter type charts by a regular expression
+- Display traceback on 500 Internal Server Error
 
 
 0.7 (April 23, 2019)
diff --git a/dozer/leak.py b/dozer/leak.py
index f7eae1e..c8ad741 100644
--- a/dozer/leak.py
+++ b/dozer/leak.py
@@ -1,9 +1,11 @@
 import cgi
 import gc
 import os
+import re
 import sys
 import threading
 import time
+import traceback
 import warnings
 from io import BytesIO
 from types import FrameType, ModuleType
@@ -113,7 +115,23 @@ class Dozer(object):
             or req.path_info == self.path):
             req.script_name += self.path
             req.path_info = req.path_info[len(self.path):]
-            return self.dowse(req)(environ, start_response)
+            try:
+                return self.dowse(req)(environ, start_response)
+            except Exception as ex:
+                error_text = traceback.format_exc()
+
+                acceptable_offers = req.accept.acceptable_offers(
+                    offers=['text/html', 'application/json']
+                )
+                match = acceptable_offers[0][0] if acceptable_offers else None
+                if match != 'application/json':
+                    # Strangely, webob.exc.WSGIHTTPException.plain_body replaces newlines
+                    # to spaces for plain/text, but replaces "<br/>" tags to newlines.
+                    error_text = error_text.replace('\n', '<br/>')
+
+                return exc.HTTPInternalServerError(
+                    str(ex), body_template=error_text
+                )(environ, start_response)
         else:
             return self.app(environ, start_response)
 
@@ -174,13 +192,18 @@ class Dozer(object):
         self.running = False
 
     def index(self, req):
-        floor = req.GET.get('floor', 0)
+        floor = req.GET.get('floor', 0) or 0
+        filtertext = req.GET.get('filter', '')
+        filterre = re.compile(filtertext, re.IGNORECASE) if filtertext else None
         rows = []
         typenames = sorted(self.history)
         for typename in typenames:
             hist = self.history[typename]
             maxhist = max(hist)
-            if maxhist > int(floor):
+            if (
+                maxhist > int(floor)
+                and (not filterre or filterre.search(typename))
+            ):
                 row = ('<div class="typecount">%s<br />'
                        '<img class="chart" src="%s" /><br />'
                        'Min: %s Cur: %s Max: %s <a href="%s">TRACE</a></div>'
@@ -192,7 +215,13 @@ class Dozer(object):
                        )
                 rows.append(row)
         res = Response()
-        res.text = template(req, "graphs.html", output="\n".join(rows))
+        res.text = template(
+            req,
+            "graphs.html",
+            output="\n".join(rows),
+            floor=floor,
+            filter=escape(filtertext),
+        )
         return res
     index.exposed = True
 
diff --git a/dozer/media/graphs.html b/dozer/media/graphs.html
index 841ec8d..361a63e 100644
--- a/dozer/media/graphs.html
+++ b/dozer/media/graphs.html
@@ -30,6 +30,17 @@
 <body>
 <div id="header">
     <h1><a href="%(home)s">Dozer</a>: Types</h1>
+    <div>
+        <form>
+            <fieldset>
+                <label>Floor</label>
+                <input name="floor" value="%(floor)s"/>
+                <label>Filter</label>
+                <input name="filter" value="%(filter)s"/>
+                <button type="submit">Submit</button>
+            </fieldset>
+        </form>
+    </div>
 </div>
 
 <div id="output">
diff --git a/tox.ini b/tox.ini
index 6f5dcfc..795056a 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,6 @@
 [tox]
 envlist = py27, py35, py36, py37, py38
+minversion = 2.4
 
 [testenv]
 extras = test

Add UI for the `floor` option or document in README
I only happened to realise there was an option to show the top X objects (eg `/_dozer/index?floor=1000`) after seeing the equivalent feature in https://github.com/munhitsu/django-dowser

It would be great if there was UI for this, or alternatively a mention in the README :-)

diff --git a/dozer/tests/test_leak.py b/dozer/tests/test_leak.py
index ae47563..2027482 100644
--- a/dozer/tests/test_leak.py
+++ b/dozer/tests/test_leak.py
@@ -254,3 +254,32 @@ class TestEntireStack(unittest.TestCase):
         app = self.make_test_app()
         resp = app.get('/_dozer/dowse', status=403)
 
+    def test_dozer_floor(self):
+        app = self.make_test_app()
+        app.app.history['mymodule.AnotherType'] = [10, 20, 30, 40, 50]
+        resp = app.get('/_dozer/?floor=4')
+        self.assertEqual(resp.status_int, 200)
+        self.assertIn('<input name="floor" value="4"/>', resp)
+        self.assertIn('mymodule.AnotherType', resp)
+        self.assertIn('mymodule.MyType', resp)
+
+        resp = app.get('/_dozer/?floor=10')
+        self.assertEqual(resp.status_int, 200)
+        self.assertIn('<input name="floor" value="10"/>', resp)
+        self.assertIn('mymodule.AnotherType', resp)
+        self.assertNotIn('mymodule.MyType', resp)
+
+    def test_dozer_filter(self):
+        app = self.make_test_app()
+        app.app.history['mymodule.AnotherType'] = [10, 20, 30, 40, 50]
+        resp = app.get('/_dozer/?filter=type')
+        self.assertEqual(resp.status_int, 200)
+        self.assertIn('<input name="filter" value="type"/>', resp)
+        self.assertIn('mymodule.AnotherType', resp)
+        self.assertIn('mymodule.MyType', resp)
+
+        resp = app.get('/_dozer/?filter=another')
+        self.assertEqual(resp.status_int, 200)
+        self.assertIn('<input name="filter" value="another"/>', resp)
+        self.assertIn('mymodule.AnotherType', resp)
+        self.assertNotIn('mymodule.MyType', resp)

pip install -e .[test]
parse_log_pytest
requirements.txt
apt-get update apt-get install -y gcc
3.7
requirements/base.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
beautifulsoup4==4.13.3
certifi @ file:///croot/certifi_1671487769961/work/certifi
-e git+https://github.com/mgedmin/dozer.git@cd1f2b060362c4d962e69473e23609192f066038#egg=Dozer
exceptiongroup==1.2.2
importlib-metadata==6.7.0
iniconfig==2.0.0
Mako==1.2.4
MarkupSafe==2.1.5
mock==5.2.0
packaging==24.0
Pillow==9.5.0
pluggy==1.2.0
pytest==7.4.4
soupsieve==2.4.1
tomli==2.0.1
typing_extensions==4.7.1
waitress==2.1.2
WebOb==1.8.9
WebTest==3.0.1
zipp==3.15.0

name: dozer
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
  - certifi=2022.12.7=py37h06a4308_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=1.1.1w=h7f8727e_0
  - pip=22.3.1=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - beautifulsoup4==4.13.3
      - exceptiongroup==1.2.2
      - importlib-metadata==6.7.0
      - iniconfig==2.0.0
      - mako==1.2.4
      - markupsafe==2.1.5
      - mock==5.2.0
      - packaging==24.0
      - pillow==9.5.0
      - pluggy==1.2.0
      - pytest==7.4.4
      - soupsieve==2.4.1
      - tomli==2.0.1
      - typing-extensions==4.7.1
      - waitress==2.1.2
      - webob==1.8.9
      - webtest==3.0.1
      - zipp==3.15.0
prefix: /opt/conda/envs/dozer

dozer/tests/test_leak.py::TestEntireStack::test_dozer_filter dozer/tests/test_leak.py::TestEntireStack::test_dozer_floor
dozer/tests/test_leak.py::TestDozer::test_maybe_warn_about_PIL dozer/tests/test_leak.py::TestDozer::test_path_normalization dozer/tests/test_leak.py::TestDozer::test_start dozer/tests/test_leak.py::TestDozer::test_start_thread dozer/tests/test_leak.py::TestDozer::test_tick dozer/tests/test_leak.py::TestDozer::test_tick_handles_disappearing_types dozer/tests/test_leak.py::TestDozer::test_tick_handles_types_with_broken_module dozer/tests/test_leak.py::TestDozer::test_trace_all_handles_types_with_broken_module dozer/tests/test_leak.py::TestDozer::test_trace_one_handles_types_with_broken_module dozer/tests/test_leak.py::TestDozer::test_tree_handles_types_with_broken_module dozer/tests/test_leak.py::TestDozer::test_url dozer/tests/test_leak.py::TestReferrerTree::test_gen_handles_types_with_broken_module dozer/tests/test_leak.py::TestReferrerTree::test_gen_skips_itself dozer/tests/test_leak.py::TestReferrerTree::test_get_repr_handles_types_with_broken_module dozer/tests/test_leak.py::TestEntireStack::test_application_pass_through dozer/tests/test_leak.py::TestEntireStack::test_dozer dozer/tests/test_leak.py::TestEntireStack::test_dozer_chart dozer/tests/test_leak.py::TestEntireStack::test_dozer_chart_no_PIL dozer/tests/test_leak.py::TestEntireStack::test_dozer_forbidden dozer/tests/test_leak.py::TestEntireStack::test_dozer_index dozer/tests/test_leak.py::TestEntireStack::test_dozer_media dozer/tests/test_leak.py::TestEntireStack::test_dozer_not_found dozer/tests/test_leak.py::TestEntireStack::test_dozer_trace_all dozer/tests/test_leak.py::TestEntireStack::test_dozer_trace_all_not_empty dozer/tests/test_leak.py::TestEntireStack::test_dozer_trace_one dozer/tests/test_leak.py::TestEntireStack::test_dozer_trace_one_not_empty dozer/tests/test_leak.py::TestEntireStack::test_dozer_tree dozer/tests/test_leak.py::TestEntireStack::test_dozer_tree_not_empty
CHANGELOG.rst dozer/leak.py tox.ini dozer/media/graphs.html
CHANGELOG.rst dozer/leak.py tox.ini dozer/media/graphs.html