diff --git a/src/loggingex/context/__init__.py b/src/loggingex/context/__init__.py
index f722f47..23f0337 100644
--- a/src/loggingex/context/__init__.py
+++ b/src/loggingex/context/__init__.py
@@ -128,6 +128,21 @@ class ContextChange:
 
         self.context_restore_token = context_restore_token
 
+    def __str__(self) -> str:
+        parts = [
+            "!" if self.context_restore_token else None,
+            "-*" if self.context_fresh else None,
+        ]
+        parts.extend("-%s" % n for n in sorted(self.context_remove))
+        upd = self.context_update
+        val = self.context_update.__getitem__
+        parts.extend("+%s=%r" % (n, val(n)) for n in sorted(upd.keys()))
+
+        return " ".join(x for x in parts if x)
+
+    def __repr__(self):
+        return "<ContextChange: %s>" % str(self)
+
     @classmethod
     def validate_context_variable_name(
         cls, name: ContextVariableUnvalidatedName

diff --git a/tests/loggingex/context/test_context_change.py b/tests/loggingex/context/test_context_change.py
index 98ab363..91bf5e4 100644
--- a/tests/loggingex/context/test_context_change.py
+++ b/tests/loggingex/context/test_context_change.py
@@ -1,4 +1,6 @@
-from pytest import mark, raises
+from itertools import product, starmap
+
+from pytest import fixture, mark, raises
 
 from loggingex.context import (
     ContextChange,
@@ -249,3 +251,55 @@ class ContextChangeAsDecoratorTests(InitializedContextBase):
                 return fib(n - 2) + fib(n - 1)
 
         assert fib(3) == 3
+
+
+class SerializationTests:
+    VALUE_MAP = {
+        "locked": {False: None, True: "not a None"},
+        "fresh": {False: False, True: True},
+        "remove": {False: set(), True: ("b", "a")},
+        "update": {False: {}, True: {"y": 2.3, "x": 1, "z": "dummy"}},
+    }
+    STR_MAP = {
+        "locked": {False: None, True: "!"},
+        "fresh": {False: None, True: "-*"},
+        "remove": {False: None, True: "-a -b"},
+        "update": {False: None, True: "+x=1 +y=2.3 +z='dummy'"},
+    }
+
+    FIXTURES = product([False, True], repeat=4)
+    FIXTURE_IDS = starmap(
+        "{}__{}__{}__{}".format,
+        product(
+            ("unlocked", "locked"),
+            ("unfresh", "fresh"),
+            ("no_remove", "with_remove"),
+            ("no_update", "with_update"),
+        ),
+    )
+
+    @fixture(params=FIXTURES, ids=FIXTURE_IDS)
+    def context_change_and_expected_str(self, request):
+        locked, fresh, remove, update = request.param
+        change = ContextChange(
+            context_fresh=self.VALUE_MAP["fresh"][fresh],
+            context_remove=self.VALUE_MAP["remove"][remove],
+            context_update=self.VALUE_MAP["update"][update],
+            context_restore_token=self.VALUE_MAP["locked"][locked],
+        )
+        parts = [
+            self.STR_MAP["locked"][locked],
+            self.STR_MAP["fresh"][fresh],
+            self.STR_MAP["remove"][remove],
+            self.STR_MAP["update"][update],
+        ]
+        expected_str = " ".join(x for x in parts if x)
+        return change, expected_str
+
+    def test_str_context_change_test(self, context_change_and_expected_str):
+        change, expected = context_change_and_expected_str
+        assert str(change) == expected
+
+    def test_repr_context_change_test(self, context_change_and_expected_str):
+        change, expected = context_change_and_expected_str
+        assert repr(change) == "<ContextChange: %s>" % expected

Implement __str__ and __repr__ for ContextChange
Currently `ContextChange` looks ugly in the debugger or when printed.

Implement `__str__` and `__repr__` methods to make it pretty and, most importantly, useful.
tests/loggingex/context/test_context_change.py::SerializationTests::test_str_context_change_test[unlocked__unfresh__no_remove__no_update] tests/loggingex/context/test_context_change.py::SerializationTests::test_str_context_change_test[unlocked__unfresh__no_remove__with_update] tests/loggingex/context/test_context_change.py::SerializationTests::test_str_context_change_test[unlocked__unfresh__with_remove__no_update] tests/loggingex/context/test_context_change.py::SerializationTests::test_str_context_change_test[unlocked__unfresh__with_remove__with_update] tests/loggingex/context/test_context_change.py::SerializationTests::test_str_context_change_test[unlocked__fresh__no_remove__no_update] tests/loggingex/context/test_context_change.py::SerializationTests::test_str_context_change_test[unlocked__fresh__no_remove__with_update] tests/loggingex/context/test_context_change.py::SerializationTests::test_str_context_change_test[unlocked__fresh__with_remove__no_update] tests/loggingex/context/test_context_change.py::SerializationTests::test_str_context_change_test[unlocked__fresh__with_remove__with_update] tests/loggingex/context/test_context_change.py::SerializationTests::test_str_context_change_test[locked__unfresh__no_remove__no_update] tests/loggingex/context/test_context_change.py::SerializationTests::test_str_context_change_test[locked__unfresh__no_remove__with_update] tests/loggingex/context/test_context_change.py::SerializationTests::test_str_context_change_test[locked__unfresh__with_remove__no_update] tests/loggingex/context/test_context_change.py::SerializationTests::test_str_context_change_test[locked__unfresh__with_remove__with_update] tests/loggingex/context/test_context_change.py::SerializationTests::test_str_context_change_test[locked__fresh__no_remove__no_update] tests/loggingex/context/test_context_change.py::SerializationTests::test_str_context_change_test[locked__fresh__no_remove__with_update] tests/loggingex/context/test_context_change.py::SerializationTests::test_str_context_change_test[locked__fresh__with_remove__no_update] tests/loggingex/context/test_context_change.py::SerializationTests::test_str_context_change_test[locked__fresh__with_remove__with_update] tests/loggingex/context/test_context_change.py::SerializationTests::test_repr_context_change_test[unlocked__unfresh__no_remove__no_update] tests/loggingex/context/test_context_change.py::SerializationTests::test_repr_context_change_test[unlocked__unfresh__no_remove__with_update] tests/loggingex/context/test_context_change.py::SerializationTests::test_repr_context_change_test[unlocked__unfresh__with_remove__no_update] tests/loggingex/context/test_context_change.py::SerializationTests::test_repr_context_change_test[unlocked__unfresh__with_remove__with_update] tests/loggingex/context/test_context_change.py::SerializationTests::test_repr_context_change_test[unlocked__fresh__no_remove__no_update] tests/loggingex/context/test_context_change.py::SerializationTests::test_repr_context_change_test[unlocked__fresh__no_remove__with_update] tests/loggingex/context/test_context_change.py::SerializationTests::test_repr_context_change_test[unlocked__fresh__with_remove__no_update] tests/loggingex/context/test_context_change.py::SerializationTests::test_repr_context_change_test[unlocked__fresh__with_remove__with_update] tests/loggingex/context/test_context_change.py::SerializationTests::test_repr_context_change_test[locked__unfresh__no_remove__no_update] tests/loggingex/context/test_context_change.py::SerializationTests::test_repr_context_change_test[locked__unfresh__no_remove__with_update] tests/loggingex/context/test_context_change.py::SerializationTests::test_repr_context_change_test[locked__unfresh__with_remove__no_update] tests/loggingex/context/test_context_change.py::SerializationTests::test_repr_context_change_test[locked__unfresh__with_remove__with_update] tests/loggingex/context/test_context_change.py::SerializationTests::test_repr_context_change_test[locked__fresh__no_remove__no_update] tests/loggingex/context/test_context_change.py::SerializationTests::test_repr_context_change_test[locked__fresh__no_remove__with_update] tests/loggingex/context/test_context_change.py::SerializationTests::test_repr_context_change_test[locked__fresh__with_remove__no_update] tests/loggingex/context/test_context_change.py::SerializationTests::test_repr_context_change_test[locked__fresh__with_remove__with_update]
tests/loggingex/context/test_context_change.py::test_default_constructor_initializes_context_changes tests/loggingex/context/test_context_change.py::test_validate_context_variable_name_raises_when_not_a_string[None] tests/loggingex/context/test_context_change.py::test_validate_context_variable_name_raises_when_not_a_string[1337] tests/loggingex/context/test_context_change.py::test_validate_context_variable_name_raises_when_not_a_string[13.37] tests/loggingex/context/test_context_change.py::test_validate_context_variable_name_raises_when_not_a_string[bytes] tests/loggingex/context/test_context_change.py::test_validate_context_variable_name_raises_when_not_a_string[value4] tests/loggingex/context/test_context_change.py::test_validate_context_variable_name_raises_when_not_a_string[value5] tests/loggingex/context/test_context_change.py::test_validate_context_variable_name_raises_when_not_a_string[value6] tests/loggingex/context/test_context_change.py::test_validate_context_variable_name_raises_when_not_a_string[value7] tests/loggingex/context/test_context_change.py::test_validate_context_variable_name_raises_when_not_identifier[?] tests/loggingex/context/test_context_change.py::test_validate_context_variable_name_raises_when_not_identifier[!] tests/loggingex/context/test_context_change.py::test_validate_context_variable_name_raises_when_not_identifier[foo.bar] tests/loggingex/context/test_context_change.py::test_validate_context_variable_name_raises_when_not_identifier[foo tests/loggingex/context/test_context_change.py::test_validate_context_variable_name_raises_when_not_identifier[foo-bar] tests/loggingex/context/test_context_change.py::test_validate_context_variable_name_passes_when_name_is_valid[foo] tests/loggingex/context/test_context_change.py::test_validate_context_variable_name_passes_when_name_is_valid[foo_bar] tests/loggingex/context/test_context_change.py::test_validate_context_variable_name_passes_when_name_is_valid[fooBar] tests/loggingex/context/test_context_change.py::test_validate_context_variable_name_passes_when_name_is_valid[_foo] tests/loggingex/context/test_context_change.py::test_validate_context_variable_name_passes_when_name_is_valid[bar_] tests/loggingex/context/test_context_change.py::test_started_returns_false_when_token_is_not_assigned tests/loggingex/context/test_context_change.py::test_started_returns_true_when_token_is_assigned tests/loggingex/context/test_context_change.py::test_can_change_returns_true_when_token_is_not_assigned tests/loggingex/context/test_context_change.py::test_can_change_returns_true_when_token_is_not_assigned_with_raise tests/loggingex/context/test_context_change.py::test_can_change_raises_when_token_is_assigned_with_raise tests/loggingex/context/test_context_change.py::test_fresh_changes_context_fresh[True] tests/loggingex/context/test_context_change.py::test_fresh_changes_context_fresh[False] tests/loggingex/context/test_context_change.py::test_fresh_raises_when_token_is_assigned tests/loggingex/context/test_context_change.py::test_remove_updates_context_remove_set[None-update0-result0] tests/loggingex/context/test_context_change.py::test_remove_updates_context_remove_set[init1-update1-result1] tests/loggingex/context/test_context_change.py::test_remove_updates_context_remove_set[init2-update2-result2] tests/loggingex/context/test_context_change.py::test_remove_raises_when_token_is_assigned tests/loggingex/context/test_context_change.py::test_remove_raises_when_name_is_not_valid[None] tests/loggingex/context/test_context_change.py::test_remove_raises_when_name_is_not_valid[1] tests/loggingex/context/test_context_change.py::test_remove_raises_when_name_is_not_valid[foo-bar] tests/loggingex/context/test_context_change.py::test_remove_raises_when_name_is_not_valid[with tests/loggingex/context/test_context_change.py::test_update_extends_context_update_dict[None-update0-result0] tests/loggingex/context/test_context_change.py::test_update_extends_context_update_dict[init1-update1-result1] tests/loggingex/context/test_context_change.py::test_update_extends_context_update_dict[init2-update2-result2] tests/loggingex/context/test_context_change.py::test_update_raises_when_token_is_assigned tests/loggingex/context/test_context_change.py::test_update_raises_when_name_is_not_valid[foo-bar] tests/loggingex/context/test_context_change.py::test_update_raises_when_name_is_not_valid[with tests/loggingex/context/test_context_change.py::test_apply_returns_same_dict_when_no_changes tests/loggingex/context/test_context_change.py::test_apply_removes_variables tests/loggingex/context/test_context_change.py::test_apply_replaces_variables tests/loggingex/context/test_context_change.py::test_apply_starts_fresh_when_fresh tests/loggingex/context/test_context_change.py::test_apply_combines_removes_and_updates tests/loggingex/context/test_context_change.py::StartAndStopTests::test_start_applies_context_change_and_saves_token tests/loggingex/context/test_context_change.py::StartAndStopTests::test_start_raises_when_change_already_started tests/loggingex/context/test_context_change.py::StartAndStopTests::test_stop_restores_context_and_clears_token tests/loggingex/context/test_context_change.py::StartAndStopTests::test_stop_raises_when_change_was_not_started tests/loggingex/context/test_context_change.py::ContextChangeAsContextManagerTests::test_can_be_used_as_context_manager tests/loggingex/context/test_context_change.py::ContextChangeAsContextManagerTests::test_context_manager_does_not_swallow_exceptions tests/loggingex/context/test_context_change.py::ContextChangeAsDecoratorTests::test_can_be_used_as_decorator tests/loggingex/context/test_context_change.py::ContextChangeAsDecoratorTests::test_decorator_does_not_swallow_exceptions tests/loggingex/context/test_context_change.py::ContextChangeAsDecoratorTests::test_decorator_works_with_recursive_functions