diff --git a/docs/source/news.rst b/docs/source/news.rst
index d1f2871..886bb3d 100644
--- a/docs/source/news.rst
+++ b/docs/source/news.rst
@@ -13,9 +13,17 @@ Features:
 * Generating messages is much faster.
 * ``eliot.ValidationError``, as raised by e.g. ``capture_logging``, is now part of the public API. Fixed issue #146.
 * ``eliot.twisted.DeferredContext.addCallbacks`` now supports omitting the errback, for compatibility with Twisted's ``Deferred``. Thanks to Jean-Paul Calderone for the fix. Fixed issue #366.
+* The testing infrastructure now has slightly more informative error messages. Thanks to Jean-Paul Calderone for the bug report. Fixes issue #373.
+* ``@validate_logging`` and ``@capture_logging`` now make it clearer what caused validation errors by printing the original traceback. Thanks to Jean-Paul Calderone for the bug report. Fixes issue #365.
 * Added lower-level testing infrastructure—``eliot.testing.swap_logger`` and ``eliot.testing.check_for_errors``—which is useful for cases when the ``@capture_logging`` decorator is insufficient. For example, test methods that are async, or return Twisted ``Deferred``. See the :doc:`testing documentation<generating/testing>` for details. Thanks to Jean-Paul Calderone for the feature request. Fixes #364.
 * The testing API now has slightly more informative error messages. Thanks to Jean-Paul Calderone for the bug report. Fixes issue #373.
-* ILogger.write is now explicitly thread-safe.  The MemoryLogger implementation of this method which was previously not thread-safe is now thread-safe.
+
+Bug fixes:
+
+* ``ILogger.write`` is now explicitly thread-safe. The ``MemoryLogger`` (as used
+  by tests) implementation of this method which was previously not thread-safe
+  is now thread-safe. Thanks to Jean-Paul Calderone for the patch. Fixes issue
+  #382.
 
 1.6.0
 ^^^^^
diff --git a/eliot/_output.py b/eliot/_output.py
index ab29884..df03421 100644
--- a/eliot/_output.py
+++ b/eliot/_output.py
@@ -5,6 +5,8 @@ Implementation of hooks and APIs for outputting log messages.
 from __future__ import unicode_literals, absolute_import
 
 import sys
+import traceback
+import inspect
 import json as pyjson
 from threading import Lock
 from functools import wraps
@@ -25,6 +27,7 @@ from ._message import (
 )
 from ._util import saferepr, safeunicode
 from .json import EliotJSONEncoder
+from ._validation import ValidationError
 
 
 class _DestinationsSendError(Exception):
@@ -297,11 +300,64 @@ class MemoryLogger(object):
         """
         Add the dictionary to list of messages.
         """
+        # Validate copy of the dictionary, to ensure what we store isn't
+        # mutated.
+        try:
+            self._validate_message(dictionary.copy(), serializer)
+        except Exception as e:
+            # Skip irrelevant frames that don't help pinpoint the problem:
+            from . import _output, _message, _action
+            skip_filenames = [
+                _output.__file__, _message.__file__, _action.__file__
+            ]
+            for frame in inspect.stack():
+                if frame[1] not in skip_filenames:
+                    break
+            self._failed_validations.append(
+                "{}: {}".format(
+                    e, "".join(traceback.format_stack(frame[0])))
+            )
         self.messages.append(dictionary)
         self.serializers.append(serializer)
         if serializer is TRACEBACK_MESSAGE._serializer:
             self.tracebackMessages.append(dictionary)
 
+    def _validate_message(self, dictionary, serializer):
+        """Validate an individual message.
+
+        As a side-effect, the message is replaced with its serialized contents.
+
+        @param dictionary: A message C{dict} to be validated.  Might be mutated
+            by the serializer!
+
+        @param serializer: C{None} or a serializer.
+
+        @raises TypeError: If a field name is not unicode, or the dictionary
+            fails to serialize to JSON.
+
+        @raises eliot.ValidationError: If serializer was given and validation
+            failed.
+        """
+        if serializer is not None:
+            serializer.validate(dictionary)
+        for key in dictionary:
+            if not isinstance(key, unicode):
+                if isinstance(key, bytes):
+                    key.decode("utf-8")
+                else:
+                    raise TypeError(
+                        dictionary, "%r is not unicode" % (key, )
+                    )
+        if serializer is not None:
+            serializer.serialize(dictionary)
+
+        try:
+            bytesjson.dumps(dictionary)
+            pyjson.dumps(dictionary)
+        except Exception as e:
+            raise TypeError("Message %s doesn't encode to JSON: %s" % (
+                dictionary, e))
+
     @exclusively
     def validate(self):
         """
@@ -310,6 +366,9 @@ class MemoryLogger(object):
         Does minimal validation of types, and for messages with corresponding
         serializers use those to do additional validation.
 
+        As a side-effect, the messages are replaced with their serialized
+        contents.
+
         @raises TypeError: If a field name is not unicode, or the dictionary
             fails to serialize to JSON.
 
@@ -317,25 +376,14 @@ class MemoryLogger(object):
             failed.
         """
         for dictionary, serializer in zip(self.messages, self.serializers):
-            if serializer is not None:
-                serializer.validate(dictionary)
-            for key in dictionary:
-                if not isinstance(key, unicode):
-                    if isinstance(key, bytes):
-                        key.decode("utf-8")
-                    else:
-                        raise TypeError(
-                            dictionary, "%r is not unicode" % (key, )
-                        )
-            if serializer is not None:
-                serializer.serialize(dictionary)
-
             try:
-                bytesjson.dumps(dictionary)
-                pyjson.dumps(dictionary)
-            except Exception as e:
-                raise TypeError("Message %s doesn't encode to JSON: %s" % (
-                    dictionary, e))
+                self._validate_message(dictionary, serializer)
+            except (TypeError, ValidationError) as e:
+                # We already figured out which messages failed validation
+                # earlier. This just lets us figure out which exception type to
+                # raise.
+                raise e.__class__("\n\n".join(self._failed_validations))
+
 
     @exclusively
     def serialize(self):
@@ -367,7 +415,7 @@ class MemoryLogger(object):
         self.messages = []
         self.serializers = []
         self.tracebackMessages = []
-
+        self._failed_validations = []
 
 class FileDestination(PClass):
     """

diff --git a/eliot/tests/test_output.py b/eliot/tests/test_output.py
index 55ea76e..ff91938 100644
--- a/eliot/tests/test_output.py
+++ b/eliot/tests/test_output.py
@@ -80,7 +80,7 @@ class MemoryLoggerTests(TestCase):
     def test_serializer(self):
         """
         L{MemoryLogger.validate} calls the given serializer's C{validate()}
-        method with the message.
+        method with the message, as does L{MemoryLogger.write}.
         """
 
         class FakeValidator(list):
@@ -94,9 +94,9 @@ class MemoryLoggerTests(TestCase):
         logger = MemoryLogger()
         message = {"message_type": "mymessage", "X": 1}
         logger.write(message, validator)
-        self.assertEqual(validator, [])
-        logger.validate()
         self.assertEqual(validator, [message])
+        logger.validate()
+        self.assertEqual(validator, [message, message])
 
     def test_failedValidation(self):
         """
diff --git a/eliot/tests/test_testing.py b/eliot/tests/test_testing.py
index 3d60450..6619864 100644
--- a/eliot/tests/test_testing.py
+++ b/eliot/tests/test_testing.py
@@ -491,9 +491,12 @@ class ValidateLoggingTestsMixin(object):
                     "message_type": "wrongmessage"}, MESSAGE._serializer)
 
         test = MyTest()
-        self.assertRaises(ValidationError, test.debug)
-        self.assertEqual(
-            list(test.logger.messages[0].keys()), ["message_type"])
+        with self.assertRaises(ValidationError) as context:
+            test.debug()
+        # Some reference to the reason:
+        self.assertIn("wrongmessage", str(context.exception))
+        # Some reference to which file caused the problem:
+        self.assertIn("test_testing.py", str(context.exception))
 
     def test_addCleanupTracebacks(self):
         """

Point to the call site where a log mesage that fails validation was emitted
The traceback associated with a validation failure is pretty much useless.  It points to test runner innards and Eliot innards.  It doesn't point to the call site where the log message was emitted.  It doesn't even say what the action or message type was.  It only reports the field that failed validation.

Debugging validation failures would be easier with an action or message type.  It would be _much_ easier with a filename and line number.

I imagine there are many good reasons not to collect this information during normal operation but I think it may be feasible to collect when running with the intent of validating.
eliot/tests/test_output.py::MemoryLoggerTests::test_serializer eliot/tests/test_testing.py::ValidateLoggingTests::test_addCleanupValidate eliot/tests/test_testing.py::CaptureLoggingTests::test_addCleanupValidate
eliot/tests/test_output.py::MemoryLoggerTests::test_JSON eliot/tests/test_output.py::MemoryLoggerTests::test_bytesMustBeUTF8 eliot/tests/test_output.py::MemoryLoggerTests::test_failedValidation eliot/tests/test_output.py::MemoryLoggerTests::test_flushTracebacksNoTestFailure eliot/tests/test_output.py::MemoryLoggerTests::test_flushTracebacksReturnsExceptions eliot/tests/test_output.py::MemoryLoggerTests::test_flushTracebacksUnflushedTestFailure eliot/tests/test_output.py::MemoryLoggerTests::test_flushTracebacksUnflushedUnreturned eliot/tests/test_output.py::MemoryLoggerTests::test_interface eliot/tests/test_output.py::MemoryLoggerTests::test_notStringFieldKeys eliot/tests/test_output.py::MemoryLoggerTests::test_reset eliot/tests/test_output.py::MemoryLoggerTests::test_serialize eliot/tests/test_output.py::MemoryLoggerTests::test_serializeCopies eliot/tests/test_output.py::MemoryLoggerTests::test_threadSafeWrite eliot/tests/test_output.py::MemoryLoggerTests::test_tracebacksCauseTestFailure eliot/tests/test_output.py::MemoryLoggerTests::test_write eliot/tests/test_output.py::DestinationsTests::test_addGlobalFields eliot/tests/test_output.py::DestinationsTests::test_addGlobalFieldsCumulative eliot/tests/test_output.py::DestinationsTests::test_buffering eliot/tests/test_output.py::DestinationsTests::test_buffering_second_batch eliot/tests/test_output.py::DestinationsTests::test_destinationExceptionContinue eliot/tests/test_output.py::DestinationsTests::test_destinationExceptionMultipleDestinations eliot/tests/test_output.py::DestinationsTests::test_global_fields_buffering eliot/tests/test_output.py::DestinationsTests::test_remove eliot/tests/test_output.py::DestinationsTests::test_removeNonExistent eliot/tests/test_output.py::DestinationsTests::test_send eliot/tests/test_output.py::LoggerTests::test_destinationExceptionCaught eliot/tests/test_output.py::LoggerTests::test_destinationExceptionCaughtTwice eliot/tests/test_output.py::LoggerTests::test_destinationMultipleExceptionsCaught eliot/tests/test_output.py::LoggerTests::test_global eliot/tests/test_output.py::LoggerTests::test_interface eliot/tests/test_output.py::LoggerTests::test_passedInDictionaryUnmodified eliot/tests/test_output.py::LoggerTests::test_safeUnicodeDictionary eliot/tests/test_output.py::LoggerTests::test_safeUnicodeDictionaryFallback eliot/tests/test_output.py::LoggerTests::test_safeUnicodeDictionaryFallbackFailure eliot/tests/test_output.py::LoggerTests::test_serializationErrorTraceback eliot/tests/test_output.py::LoggerTests::test_serializer eliot/tests/test_output.py::LoggerTests::test_write eliot/tests/test_output.py::PEP8Tests::test_flush_tracebacks eliot/tests/test_output.py::ToFileTests::test_bytes_values eliot/tests/test_output.py::ToFileTests::test_filedestination_custom_encoder eliot/tests/test_output.py::ToFileTests::test_filedestination_flushes eliot/tests/test_output.py::ToFileTests::test_filedestination_writes_json_bytes eliot/tests/test_output.py::ToFileTests::test_filedestination_writes_json_unicode eliot/tests/test_output.py::ToFileTests::test_to_file_adds_destination eliot/tests/test_output.py::ToFileTests::test_to_file_custom_encoder eliot/tests/test_testing.py::IsSuperSetTests::test_additionalIsSuperSet eliot/tests/test_testing.py::IsSuperSetTests::test_equal eliot/tests/test_testing.py::IsSuperSetTests::test_missingIsNotSuperSet eliot/tests/test_testing.py::LoggedActionTests::test_descendants eliot/tests/test_testing.py::LoggedActionTests::test_fromMessagesAddsChildActions eliot/tests/test_testing.py::LoggedActionTests::test_fromMessagesAddsChildMessages eliot/tests/test_testing.py::LoggedActionTests::test_fromMessagesCreatesLoggedAction eliot/tests/test_testing.py::LoggedActionTests::test_fromMessagesFinishNotFound eliot/tests/test_testing.py::LoggedActionTests::test_fromMessagesStartAndErrorFinish eliot/tests/test_testing.py::LoggedActionTests::test_fromMessagesStartAndSuccessfulFinish eliot/tests/test_testing.py::LoggedActionTests::test_fromMessagesStartNotFound eliot/tests/test_testing.py::LoggedActionTests::test_notSucceeded eliot/tests/test_testing.py::LoggedActionTests::test_ofType eliot/tests/test_testing.py::LoggedActionTests::test_ofTypeNotFound eliot/tests/test_testing.py::LoggedActionTests::test_succeeded eliot/tests/test_testing.py::LoggedActionTests::test_values eliot/tests/test_testing.py::LoggedMessageTest::test_ofType eliot/tests/test_testing.py::LoggedMessageTest::test_ofTypeNotFound eliot/tests/test_testing.py::LoggedMessageTest::test_values eliot/tests/test_testing.py::AssertContainsFields::test_additionalIsSuperSet eliot/tests/test_testing.py::AssertContainsFields::test_differentValues eliot/tests/test_testing.py::AssertContainsFields::test_equal eliot/tests/test_testing.py::AssertContainsFields::test_missingFields eliot/tests/test_testing.py::ValidateLoggingTests::test_addCleanupTracebacks eliot/tests/test_testing.py::ValidateLoggingTests::test_assertion eliot/tests/test_testing.py::ValidateLoggingTests::test_assertionAfterTest eliot/tests/test_testing.py::ValidateLoggingTests::test_assertionArguments eliot/tests/test_testing.py::ValidateLoggingTests::test_assertionBeforeTracebackCleanup eliot/tests/test_testing.py::ValidateLoggingTests::test_decoratedFunctionCalledWithMemoryLogger eliot/tests/test_testing.py::ValidateLoggingTests::test_name eliot/tests/test_testing.py::ValidateLoggingTests::test_newMemoryLogger eliot/tests/test_testing.py::ValidateLoggingTests::test_raises eliot/tests/test_testing.py::ValidateLoggingTests::test_returns eliot/tests/test_testing.py::CaptureLoggingTests::test_addCleanupTracebacks eliot/tests/test_testing.py::CaptureLoggingTests::test_assertion eliot/tests/test_testing.py::CaptureLoggingTests::test_assertionAfterTest eliot/tests/test_testing.py::CaptureLoggingTests::test_assertionArguments eliot/tests/test_testing.py::CaptureLoggingTests::test_assertionBeforeTracebackCleanup eliot/tests/test_testing.py::CaptureLoggingTests::test_decoratedFunctionCalledWithMemoryLogger eliot/tests/test_testing.py::CaptureLoggingTests::test_default_logger eliot/tests/test_testing.py::CaptureLoggingTests::test_global_cleanup eliot/tests/test_testing.py::CaptureLoggingTests::test_global_cleanup_exception eliot/tests/test_testing.py::CaptureLoggingTests::test_name eliot/tests/test_testing.py::CaptureLoggingTests::test_newMemoryLogger eliot/tests/test_testing.py::CaptureLoggingTests::test_raises eliot/tests/test_testing.py::CaptureLoggingTests::test_returns eliot/tests/test_testing.py::CaptureLoggingTests::test_validationNotRunForSkip eliot/tests/test_testing.py::AssertHasMessageTests::test_failIfNoMessagesOfType eliot/tests/test_testing.py::AssertHasMessageTests::test_failIfNotSubset eliot/tests/test_testing.py::AssertHasMessageTests::test_returnsIfMessagesOfType eliot/tests/test_testing.py::AssertHasMessageTests::test_returnsIfSubset eliot/tests/test_testing.py::AssertHasActionTests::test_failIfNoActionsOfType eliot/tests/test_testing.py::AssertHasActionTests::test_failIfNotEndSubset eliot/tests/test_testing.py::AssertHasActionTests::test_failIfNotStartSubset eliot/tests/test_testing.py::AssertHasActionTests::test_failIfWrongSuccessStatus eliot/tests/test_testing.py::AssertHasActionTests::test_returns eliot/tests/test_testing.py::AssertHasActionTests::test_returnsIfMessagesOfType eliot/tests/test_testing.py::PEP8Tests::test_LoggedAction_end_message eliot/tests/test_testing.py::PEP8Tests::test_LoggedAction_from_messages eliot/tests/test_testing.py::PEP8Tests::test_LoggedAction_of_type eliot/tests/test_testing.py::PEP8Tests::test_LoggedAction_start_message eliot/tests/test_testing.py::PEP8Tests::test_LoggedMessage_of_type eliot/tests/test_testing.py::PEP8Tests::test_validate_logging eliot/tests/test_testing.py::LowLevelTestingHooks::test_check_for_errors_unflushed_tracebacks eliot/tests/test_testing.py::LowLevelTestingHooks::test_check_for_errors_validation eliot/tests/test_testing.py::LowLevelTestingHooks::test_swap_logger