diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3c99703f7eae51e8cc1dbf2cbbece5a3e43148e8..5e518d9cc2fe0f4c658a43dbe06efaa190106618 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,6 @@
 # v2.4.29
 
-* _TBA_
+* added `CustomException`
 
 # v2.4.28
 
diff --git a/docs/exceptions.rst b/docs/exceptions.rst
new file mode 100644
index 0000000000000000000000000000000000000000..e0acbf5cd130e12c10f86f5f0fbcf92ea71d41f2
--- /dev/null
+++ b/docs/exceptions.rst
@@ -0,0 +1,17 @@
+Exceptions
+==========
+
+CustomException
+---------------
+
+This is the class you can base your exceptions off. It provides
+a reasonable __repr__ and __str__, eg.:
+
+::
+
+    class MyException(CustomException):
+        ...
+
+    assert str(MyException) == 'MyException()'
+
+__repr__ will additionally prefix the exception class name with entire module path to it.
diff --git a/docs/index.rst b/docs/index.rst
index 7fa8dfbe450bdb0d13a093fcee0cc3914f32125b..24bc9dae55667c140f2b05ec9dc706d837776b92 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -24,6 +24,7 @@ Visit the project's page at GitHub_!
            import
            files
            time
+           exceptions
 
 
 Indices and tables
diff --git a/satella/__init__.py b/satella/__init__.py
index c67ecddd24b2e49a65d9f2aa8e5a45a2a229cc26..56cdd0bfd1ab6862afffa31b912f7d042fd1b858 100644
--- a/satella/__init__.py
+++ b/satella/__init__.py
@@ -1 +1 @@
-__version__ = '2.4.29a1'
+__version__ = '2.4.29'
diff --git a/satella/exceptions.py b/satella/exceptions.py
index 55a4539c4a965da9a2b8ca2b19a32a556c3f41e2..4dfeb4525ea4a104715ff739e74640503548a3cc 100644
--- a/satella/exceptions.py
+++ b/satella/exceptions.py
@@ -1,23 +1,31 @@
-__all__ = ['BaseSatellaException', 'ResourceLockingError', 'ResourceNotLocked', 'ResourceLocked',
+import warnings
+
+__all__ = ['BaseSatellaError', 'ResourceLockingError', 'ResourceNotLocked', 'ResourceLocked',
            'ConfigurationValidationError', 'ConfigurationError', 'ConfigurationSchemaError',
-           'PreconditionError', 'MetricAlreadyExists']
+           'PreconditionError', 'MetricAlreadyExists', 'BaseSatellaException', 'CustomException']
 
 
-class BaseSatellaException(Exception):
-    """"Base class for all Satella exceptions"""
-    def __init__(self, msg, *args, **kwargs):
-        super().__init__(*(msg, *args))
+class CustomException(Exception):
+    """"
+    Base class for your custom exceptions. It will:
+
+    1. Accept any number of arguments
+    2. Provide faithful __repr__ and a reasonable __str__
+
+    It passed all arguments that your exception received via super()
+    """
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args)
         self.kwargs = kwargs
-        self.msg = msg
 
-    def __str__(self):
-        a = '%s(%s' % (self.__class__.__qualname__, self.args)
+    def __str__(self) -> str:
+        a = '%s(%s' % (self.__class__.__qualname__.split('.')[-1], ', '.join(map(repr, self.args)))
         if self.kwargs:
-            a += ', '+(', '.join(map(lambda k, v: '%s=%s' % (k, repr(v)), self.kwargs.items())))
+            a += ', ' + ', '.join(map(lambda k, v: '%s=%s' % (k, repr(v)), self.kwargs.items()))
         a += ')'
         return a
 
-    def __repr__(self):
+    def __repr__(self) -> str:
         a = '%s%s(%s' % ((self.__class__.__module__ + '.')
                          if self.__class__.__module__ != 'builtins' else '',
                          self.__class__.__qualname__,
@@ -25,11 +33,21 @@ class BaseSatellaException(Exception):
         if self.kwargs:
             a += ', ' + (', '.join(map(lambda kv: '%s=%s' % (kv[0], repr(kv[1])),
                                        self.kwargs.items())))
-            a += ')'
+        a += ')'
         return a
 
 
-class ResourceLockingError(BaseSatellaException):
+class BaseSatellaError(CustomException):
+    """"Base class for all Satella exceptions"""
+
+
+class BaseSatellaException(BaseSatellaError):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        warnings.warn('Use BaseSatellaError instead', DeprecationWarning)
+
+
+class ResourceLockingError(BaseSatellaError):
     """Base class for resource locking issues"""
 
 
@@ -41,13 +59,13 @@ class ResourceNotLocked(ResourceLockingError):
     """Locking given resource is needed in order to access it"""
 
 
-class PreconditionError(BaseSatellaException, ValueError):
+class PreconditionError(BaseSatellaError, ValueError):
     """
     A precondition was not met for the argument
     """
 
 
-class ConfigurationError(BaseSatellaException):
+class ConfigurationError(BaseSatellaError):
     """A generic error during configuration"""
 
 
@@ -66,7 +84,7 @@ class ConfigurationValidationError(ConfigurationSchemaError):
         self.value = value
 
 
-class MetricAlreadyExists(BaseSatellaException):
+class MetricAlreadyExists(BaseSatellaError):
     """Metric with given name already exists, but with a different type"""
 
     def __init__(self, msg, name, requested_type, existing_type):
diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py
index 6fb9e94cf6d84dab711c5d005d0fa01b07a14169..f7cf4f3e5813a15fc08a3483333c5f15753d74f2 100644
--- a/tests/test_exceptions.py
+++ b/tests/test_exceptions.py
@@ -1,33 +1,22 @@
 import unittest
 
-from satella.exceptions import BaseSatellaException
+
+from satella.exceptions import BaseSatellaError, CustomException
 
 
 class TestExceptions(unittest.TestCase):
 
     def test_exception_kwargs(self):
-        e = BaseSatellaException('hello world', label='value')
+        e = BaseSatellaError('hello world', label='value')
         self.assertIn("label='value'", repr(e))
 
-    def test_exception(self):
-        try:
-            raise BaseSatellaException('message', 'arg1', 'arg2')
-        except BaseSatellaException as e:
-            self.assertIn('arg1', str(e))
-            self.assertIn('arg2', str(e))
-            self.assertIn('BaseSatellaException', str(e))
-        else:
-            self.fail()
-
     def test_except_inherited(self):
-        class InheritedException(BaseSatellaException):
+        class InheritedError(CustomException):
             pass
 
         try:
-            raise InheritedException('message', 'arg1', 'arg2')
-        except BaseSatellaException as e:
-            self.assertIn('arg1', str(e))
-            self.assertIn('arg2', str(e))
-            self.assertIn('InheritedException', str(e))
+            raise InheritedError('message', 'arg1', 'arg2')
+        except CustomException as e:
+            self.assertEqual(str(e), "InheritedError('message', 'arg1', 'arg2')")
         else:
             self.fail()