diff --git a/docs/source/news.rst b/docs/source/news.rst
index bb2ba9b..b0409df 100644
--- a/docs/source/news.rst
+++ b/docs/source/news.rst
@@ -13,6 +13,7 @@ Features:
 * NumPy integers, floats, bools and arrays are now automatically serialized to JSON, via a new default JSON encoder (``eliot.json.EliotJSONEncoder``).
 * Dask support: replace ``dask.compute()`` with ``eliot.dask.compute_with_trace()`` to automatically preserve Eliot context for ``Bag`` and ``Delayed`` Dask computations. See :ref:`dask_usage` for details.
 * New decorator, ``@eliot.log_call``, which automatically creates an action that starts when function is called and ends when it returns. See :ref:`log_call decorator`.
+* A parser library for parsing serialized Eliot JSON messages into a tree of Python objects. See :ref:`parsing_logs` for details.
 
 Testing features:
 
diff --git a/docs/source/reading/reading.rst b/docs/source/reading/reading.rst
index 1312135..89a0ab9 100644
--- a/docs/source/reading/reading.rst
+++ b/docs/source/reading/reading.rst
@@ -41,3 +41,24 @@ For example, here's how you'd extract a particular field with `jq`_:
 
 .. _eliot-tree: https://github.com/jonathanj/eliottree
 .. _jq: https://stedolan.github.io/jq/
+
+
+.. _parsing_logs:
+
+Parsing Logs
+------------
+
+Eliot also includes a parser for parsing logs into Python objects:
+
+.. code-block:: python
+
+   import json
+   from eliot.parse import Parser
+
+   def load_messages(logfile_path):
+      for line in open(logfile_path):
+          yield json.loads(line)
+       
+   def parse(logfile_path):
+       for task in Parser.parse_stream(load_messages(logfile_path)):
+           print("Root action type is", task.root().action_type)
diff --git a/eliot/__init__.py b/eliot/__init__.py
index 2d998fc..d21ecbb 100644
--- a/eliot/__init__.py
+++ b/eliot/__init__.py
@@ -47,6 +47,18 @@ add_destinations = Logger._destinations.add
 remove_destination = removeDestination
 add_global_fields = addGlobalFields
 
+# Backwards compatibility for old versions of eliot-tree, which rely on
+# eliot._parse:
+def _parse_compat():
+    # Force eliot.parse to be imported in way that works with old Python:
+    from .parse import Parser
+    del Parser
+    import sys
+    sys.modules["eliot._parse"] = sys.modules["eliot.parse"]
+    return sys.modules["eliot.parse"]
+_parse = _parse_compat()
+del _parse_compat
+
 
 __all__ = [
     "Message",
@@ -83,7 +95,11 @@ __all__ = [
     "to_file",
     "log_call",
     "__version__",
+
+    # Backwards compat for eliot-tree:
+    "_parse",
 ]
 
+
 __version__ = get_versions()['version']
 del get_versions
diff --git a/eliot/_action.py b/eliot/_action.py
index b15c050..9549ca2 100644
--- a/eliot/_action.py
+++ b/eliot/_action.py
@@ -23,7 +23,7 @@ from pyrsistent import (
     pvector_field,
     pvector, )
 from boltons.funcutils import wraps
-from six import text_type as unicode, integer_types
+from six import text_type as unicode, integer_types, PY3
 
 from ._message import (
     Message,
@@ -896,12 +896,29 @@ def log_call(
                        include_result=include_result)
 
     if action_type is None:
-        action_type = wrapped_function.__name__
+        if PY3:
+            action_type = "{}.{}".format(wrapped_function.__module__,
+                                         wrapped_function.__qualname__)
+        else:
+            action_type = wrapped_function.__name__
+
+    if PY3 and include_args is not None:
+        from inspect import signature
+        sig = signature(wrapped_function)
+        if set(include_args) - set(sig.parameters):
+            raise ValueError(
+                ("include_args ({}) lists arguments not in the "
+                 "wrapped function").format(include_args)
+            )
 
     @wraps(wrapped_function)
     def logging_wrapper(*args, **kwargs):
         callargs = getcallargs(wrapped_function, *args, **kwargs)
 
+        # Remove self is it's included:
+        if "self" in callargs:
+            callargs.pop("self")
+
         # Filter arguments to log, if necessary:
         if include_args is not None:
             callargs = {k: callargs[k] for k in include_args}
diff --git a/eliot/_parse.py b/eliot/parse.py
similarity index 98%
rename from eliot/_parse.py
rename to eliot/parse.py
index 91474d0..bca580c 100644
--- a/eliot/_parse.py
+++ b/eliot/parse.py
@@ -181,3 +181,6 @@ class Parser(PClass):
                 yield task
         for task in parser.incomplete_tasks():
             yield task
+
+
+__all__ = ["Parser", "Task", "TaskLevel", "WrittenMessage", "WrittenAction"]

diff --git a/eliot/tests/corotests.py b/eliot/tests/corotests.py
index de844c8..78e3501 100644
--- a/eliot/tests/corotests.py
+++ b/eliot/tests/corotests.py
@@ -10,7 +10,7 @@ from threading import Thread
 from unittest import TestCase
 
 from ..testing import capture_logging
-from .._parse import Parser
+from ..parse import Parser
 from .. import start_action
 from .._action import _ExecutionContext, _context, use_asyncio_context
 from .._asyncio import AsyncioContext
diff --git a/eliot/tests/test_action.py b/eliot/tests/test_action.py
index 86eb5e1..dc1113b 100644
--- a/eliot/tests/test_action.py
+++ b/eliot/tests/test_action.py
@@ -5,7 +5,7 @@ Tests for L{eliot._action}.
 from __future__ import unicode_literals
 
 import pickle
-from unittest import TestCase
+from unittest import TestCase, skipIf
 from threading import Thread
 from warnings import catch_warnings, simplefilter
 
@@ -44,7 +44,7 @@ from .._message import (
 from .._output import MemoryLogger
 from .._validation import ActionType, Field, _ActionSerializers
 from ..testing import assertContainsFields, capture_logging
-from .._parse import Parser
+from ..parse import Parser
 from .. import (
     _action,
     add_destination,
@@ -1655,6 +1655,9 @@ class LogCallTests(TestCase):
 
     def assert_logged(self, logger, action_type, expected_params, expected_result):
         """Assert that an action of given structure was logged."""
+        if six.PY2:
+            # On Python 2 we don't include the module or class:
+            action_type = action_type.split(".")[-1]
         [tree] = Parser.parse_stream(logger.messages)
         root = tree.root()
         self.assertEqual(root.action_type, action_type)
@@ -1676,7 +1679,7 @@ class LogCallTests(TestCase):
             return 4
 
         myfunc(2, 3)
-        self.assert_logged(logger, u"myfunc",
+        self.assert_logged(logger, self.id() + ".<locals>.myfunc",
                            {u"x": 2, u"y": 3}, 4)
 
     @capture_logging(None)
@@ -1712,7 +1715,7 @@ class LogCallTests(TestCase):
             return 4
 
         myfunc(2, y=5)
-        self.assert_logged(logger, u"myfunc",
+        self.assert_logged(logger, self.id() + ".<locals>.myfunc",
                            {u"x": 2, u"y": 5}, 4)
 
     @capture_logging(None)
@@ -1723,7 +1726,7 @@ class LogCallTests(TestCase):
             return 6
 
         myfunc(2)
-        self.assert_logged(logger, u"myfunc",
+        self.assert_logged(logger, self.id() + ".<locals>.myfunc",
                            {u"x": 2, u"y": 1}, 6)
 
     @capture_logging(None)
@@ -1734,7 +1737,7 @@ class LogCallTests(TestCase):
             return 6
 
         myfunc(2, 3, 4, a=1, b=2)
-        self.assert_logged(logger, u"myfunc",
+        self.assert_logged(logger, self.id() + ".<locals>.myfunc",
                            {u"x": 2, u"y": (3, 4), u"z": {u"a": 1, u"b": 2}},
                            6)
 
@@ -1746,7 +1749,16 @@ class LogCallTests(TestCase):
             return 6
 
         myfunc(2, 3, 4)
-        self.assert_logged(logger, u"myfunc", {u"x": 2, u"z": 4}, 6)
+        self.assert_logged(logger, self.id() + ".<locals>.myfunc",
+                           {u"x": 2, u"z": 4}, 6)
+
+    @skipIf(six.PY2, "Didn't bother implementing safety check on Python 2")
+    def test_wrong_whitelist_args(self):
+        """If C{include_args} doesn't match function, raise an exception."""
+        with self.assertRaises(ValueError):
+            @log_call(include_args=["a", "x", "y"])
+            def f(x, y):
+                pass
 
     @capture_logging(None)
     def test_no_result(self, logger):
@@ -1771,3 +1783,16 @@ class LogCallTests(TestCase):
             for_pickling,
             pickle.loads(pickle.dumps(for_pickling))
         )
+
+    @capture_logging(None)
+    def test_methods(self, logger):
+        """self is not logged."""
+        class C(object):
+            @log_call
+            def f(self, x):
+                pass
+
+        C().f(2)
+        self.assert_logged(logger, self.id() + u".<locals>.C.f", {u"x": 2}, None)
+
+
diff --git a/eliot/tests/test_parse.py b/eliot/tests/test_parse.py
index a24ad00..77410ee 100644
--- a/eliot/tests/test_parse.py
+++ b/eliot/tests/test_parse.py
@@ -16,7 +16,7 @@ from pyrsistent import PClass, field, pvector_field
 
 from .. import start_action, Message
 from ..testing import MemoryLogger
-from .._parse import Task, Parser
+from ..parse import Task, Parser
 from .._message import (
     WrittenMessage,
     MESSAGE_TYPE_FIELD,
@@ -295,3 +295,15 @@ class ParserTests(TestCase):
                 if m is not None]))
         assertCountEqual(
             self, all_tasks, [parse_to_task(msgs) for msgs in all_messages])
+
+
+class BackwardsCompatibility(TestCase):
+    """Tests for backwards compatibility."""
+
+    def test_imports(self):
+        """Old ways of importing still work."""
+        import eliot._parse
+        from eliot import _parse
+        import eliot.parse
+        self.assertIs(eliot.parse, eliot._parse)
+        self.assertIs(_parse, eliot.parse)

Make eliot._parse public so eliot-tree isn't depending on private API
`eliot-tree` depends on `eliot._parse`, which is private. Really, it's a stable API, and it should be useful to others as well.

So:

1. Make it public.
2. Add backwards compat `eliot._parse` for old eliot-tree installs.
3. Document it, at least a little.

CC @jonathanj 
eliot/tests/test_action.py::ExecutionContextTests::test_globalInstance eliot/tests/test_action.py::ExecutionContextTests::test_multipleCurrent eliot/tests/test_action.py::ExecutionContextTests::test_nothingPushed eliot/tests/test_action.py::ExecutionContextTests::test_popMultiple eliot/tests/test_action.py::ExecutionContextTests::test_popSingle eliot/tests/test_action.py::ExecutionContextTests::test_pushMultiple eliot/tests/test_action.py::ExecutionContextTests::test_pushSingle eliot/tests/test_action.py::ExecutionContextTests::test_threadSafety eliot/tests/test_action.py::ExecutionContextTests::test_threadStart eliot/tests/test_action.py::ActionTests::test_addSuccessFields eliot/tests/test_action.py::ActionTests::test_child eliot/tests/test_action.py::ActionTests::test_childLevel eliot/tests/test_action.py::ActionTests::test_childSerializers eliot/tests/test_action.py::ActionTests::test_failureFinishSerializer eliot/tests/test_action.py::ActionTests::test_finish eliot/tests/test_action.py::ActionTests::test_finishWithBadException eliot/tests/test_action.py::ActionTests::test_multipleFinishCalls eliot/tests/test_action.py::ActionTests::test_nextTaskLevel eliot/tests/test_action.py::ActionTests::test_run eliot/tests/test_action.py::ActionTests::test_runContext eliot/tests/test_action.py::ActionTests::test_runContextUnsetOnRaise eliot/tests/test_action.py::ActionTests::test_runContextUnsetOnReturn eliot/tests/test_action.py::ActionTests::test_start eliot/tests/test_action.py::ActionTests::test_startFieldsNotInFinish eliot/tests/test_action.py::ActionTests::test_startMessageSerialization eliot/tests/test_action.py::ActionTests::test_stringActionCompatibility eliot/tests/test_action.py::ActionTests::test_stringActionCompatibilityWarning eliot/tests/test_action.py::ActionTests::test_successfulFinishSerializer eliot/tests/test_action.py::ActionTests::test_task_uuid eliot/tests/test_action.py::ActionTests::test_withContextNoLogging eliot/tests/test_action.py::ActionTests::test_withContextReturnsaction eliot/tests/test_action.py::ActionTests::test_withContextSetsContext eliot/tests/test_action.py::ActionTests::test_withContextUnsetOnRaise eliot/tests/test_action.py::ActionTests::test_withContextUnsetOnReturn eliot/tests/test_action.py::ActionTests::test_withLogsExceptionMessage eliot/tests/test_action.py::ActionTests::test_withLogsSuccessfulFinishMessage eliot/tests/test_action.py::ActionTests::test_withReturnValue eliot/tests/test_action.py::ActionTests::test_withSetsContext eliot/tests/test_action.py::ActionTests::test_withUnsetOnRaise eliot/tests/test_action.py::ActionTests::test_withUnsetOnReturn eliot/tests/test_action.py::StartActionAndTaskTests::test_startActionNoLogger eliot/tests/test_action.py::StartActionAndTaskTests::test_startActionNoParent eliot/tests/test_action.py::StartActionAndTaskTests::test_startActionNoParentLogStart eliot/tests/test_action.py::StartActionAndTaskTests::test_startActionSerializers eliot/tests/test_action.py::StartActionAndTaskTests::test_startActionWithParent eliot/tests/test_action.py::StartActionAndTaskTests::test_startActionWithParentLogStart eliot/tests/test_action.py::StartActionAndTaskTests::test_startTaskLogsStart eliot/tests/test_action.py::StartActionAndTaskTests::test_startTaskNewAction eliot/tests/test_action.py::StartActionAndTaskTests::test_startTaskNewUUID eliot/tests/test_action.py::StartActionAndTaskTests::test_startTaskNoLogger eliot/tests/test_action.py::StartActionAndTaskTests::test_startTaskSerializers eliot/tests/test_action.py::StartActionAndTaskTests::test_start_action_default_action_type eliot/tests/test_action.py::StartActionAndTaskTests::test_start_task_default_action_type eliot/tests/test_action.py::PEP8Tests::test_add_success_fields eliot/tests/test_action.py::PEP8Tests::test_continue_task eliot/tests/test_action.py::PEP8Tests::test_serialize_task_id eliot/tests/test_action.py::SerializationTests::test_continueTaskNoLogger eliot/tests/test_action.py::SerializationTests::test_continueTaskRequiredTaskId eliot/tests/test_action.py::SerializationTests::test_continueTaskReturnsAction eliot/tests/test_action.py::SerializationTests::test_continueTaskStartsAction eliot/tests/test_action.py::SerializationTests::test_continueTaskUnicode eliot/tests/test_action.py::SerializationTests::test_serializeTaskId eliot/tests/test_action.py::TaskLevelTests::test_child_greater_than_parent eliot/tests/test_action.py::TaskLevelTests::test_fromString eliot/tests/test_action.py::TaskLevelTests::test_from_string eliot/tests/test_action.py::TaskLevelTests::test_next_sibling eliot/tests/test_action.py::TaskLevelTests::test_next_sibling_greater eliot/tests/test_action.py::TaskLevelTests::test_parent_of_child eliot/tests/test_action.py::TaskLevelTests::test_parent_of_root eliot/tests/test_action.py::TaskLevelTests::test_toString eliot/tests/test_action.py::TaskLevelTests::test_to_string eliot/tests/test_action.py::WrittenActionTests::test_action_type_mismatch eliot/tests/test_action.py::WrittenActionTests::test_children eliot/tests/test_action.py::WrittenActionTests::test_different_task_uuid eliot/tests/test_action.py::WrittenActionTests::test_duplicate_task_level eliot/tests/test_action.py::WrittenActionTests::test_end_has_no_status eliot/tests/test_action.py::WrittenActionTests::test_failed_end eliot/tests/test_action.py::WrittenActionTests::test_from_single_child_message eliot/tests/test_action.py::WrittenActionTests::test_from_single_end_message eliot/tests/test_action.py::WrittenActionTests::test_from_single_start_message eliot/tests/test_action.py::WrittenActionTests::test_invalid_start_message_missing_status eliot/tests/test_action.py::WrittenActionTests::test_invalid_start_message_wrong_status eliot/tests/test_action.py::WrittenActionTests::test_invalid_task_level_in_start_message eliot/tests/test_action.py::WrittenActionTests::test_successful_end eliot/tests/test_action.py::WrittenActionTests::test_wrong_task_level eliot/tests/test_action.py::WrittenActionTests::test_wrong_task_uuid eliot/tests/test_action.py::FailedActionExtractionTests::test_environmenterror eliot/tests/test_action.py::FailedActionExtractionTests::test_error_in_extracter eliot/tests/test_action.py::FailedActionExtractionTests::test_matching_class eliot/tests/test_action.py::FailedActionExtractionTests::test_regular_fields eliot/tests/test_action.py::FailedActionExtractionTests::test_subclass_falls_back_to_parent eliot/tests/test_action.py::FailedActionExtractionTests::test_subclass_matches_first eliot/tests/test_action.py::PreserveContextTests::test_callable_only_once eliot/tests/test_action.py::PreserveContextTests::test_no_context eliot/tests/test_action.py::PreserveContextTests::test_with_context_calls_underlying eliot/tests/test_action.py::PreserveContextTests::test_with_context_preserves_context eliot/tests/test_action.py::LogCallTests::test_action_type eliot/tests/test_action.py::LogCallTests::test_default_argument_given eliot/tests/test_action.py::LogCallTests::test_default_argument_missing eliot/tests/test_action.py::LogCallTests::test_exception eliot/tests/test_action.py::LogCallTests::test_methods eliot/tests/test_action.py::LogCallTests::test_no_args_return eliot/tests/test_action.py::LogCallTests::test_no_result eliot/tests/test_action.py::LogCallTests::test_pickleable eliot/tests/test_action.py::LogCallTests::test_star_args_kwargs eliot/tests/test_action.py::LogCallTests::test_whitelist_args eliot/tests/test_action.py::LogCallTests::test_wrong_whitelist_args eliot/tests/test_parse.py::TaskTests::test_is_complete eliot/tests/test_parse.py::TaskTests::test_missing_action eliot/tests/test_parse.py::TaskTests::test_parse_contents eliot/tests/test_parse.py::TaskTests::test_parse_from_random_order eliot/tests/test_parse.py::ParserTests::test_incomplete_tasks eliot/tests/test_parse.py::ParserTests::test_parse_into_tasks eliot/tests/test_parse.py::ParserTests::test_parse_stream eliot/tests/test_parse.py::BackwardsCompatibility::test_imports