diff --git a/gdb/NEWS b/gdb/NEWS
index 534e2e7f364..6d35722cc23 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -88,6 +88,11 @@ show remote thread-options-packet
 
   ** GDB now supports the "cancel" request.
 
+  ** New command "set debug dap-log-level" controls DAP logging.
+
+  ** The "set debug dap-log-file" command is now documented.  This
+     command was available in GDB 14 but not documented.
+
 * New remote packets
 
 New stop reason: clone
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 6e4adf512ee..fd60e41091a 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -39611,6 +39611,24 @@ Evaluations like this can be interrupted using the DAP @code{cancel}
 request.  (In fact, @code{cancel} should work for any request, but it
 is unlikely to be useful for most of them.)
 
+@value{GDBN} provides a couple of logging settings that can be used in
+DAP mode.  These can be set on the command line using the @code{-iex}
+option (@pxref{File Options}).
+
+@table @code
+@item set debug dap-log-file @r{[}@var{filename}@r{]}
+Enable DAP logging.  Logs are written to the specified file.  If no
+file is given, logging is stopped.
+
+@item set debug dap-log-level @var{level}
+Set the DAP logging level.  The default is @samp{1}, which logs the
+DAP protocol, whatever debug messages the developers thought were
+useful, and unexpected exceptions.  Level @samp{2} can be used to log
+all exceptions, including ones that are considered to be expected.
+For example, a failure to parse an expression would be considered a
+normal exception and not normally be logged.
+@end table
+
 @node JIT Interface
 @chapter JIT Compilation Interface
 @cindex just-in-time compilation
diff --git a/gdb/python/lib/gdb/dap/breakpoint.py b/gdb/python/lib/gdb/dap/breakpoint.py
index c67bb471daf..cef3df910f0 100644
--- a/gdb/python/lib/gdb/dap/breakpoint.py
+++ b/gdb/python/lib/gdb/dap/breakpoint.py
@@ -24,7 +24,7 @@ from typing import Optional, Sequence
 
 from .server import request, capability, send_event
 from .sources import make_source
-from .startup import in_gdb_thread, log_stack, parse_and_eval, DAPException
+from .startup import in_gdb_thread, log_stack, parse_and_eval, LOG_FULL, DAPException
 from .typecheck import type_check
 
 
@@ -176,7 +176,9 @@ def _set_breakpoints_callback(kind, specs, creator):
             result.append(_breakpoint_descriptor(bp))
         # Exceptions other than gdb.error are possible here.
         except Exception as e:
-            log_stack()
+            # Don't normally want to see this, as it interferes with
+            # the test suite.
+            log_stack(LOG_FULL)
             # Maybe the breakpoint was made but setting an attribute
             # failed.  We still want this to fail.
             if bp is not None:
diff --git a/gdb/python/lib/gdb/dap/server.py b/gdb/python/lib/gdb/dap/server.py
index 44dffb1b809..e682c0faf84 100644
--- a/gdb/python/lib/gdb/dap/server.py
+++ b/gdb/python/lib/gdb/dap/server.py
@@ -23,6 +23,7 @@ import threading
 from .io import start_json_writer, read_json
 from .startup import (
     exec_and_log,
+    DAPException,
     DAPQueue,
     in_dap_thread,
     in_gdb_thread,
@@ -31,6 +32,7 @@ from .startup import (
     start_thread,
     log,
     log_stack,
+    LOG_FULL,
 )
 from .typecheck import type_check
 
@@ -139,12 +141,20 @@ class Server:
                 result["body"] = body
             result["success"] = True
         except NotStoppedException:
+            # This is an expected exception, and the result is clearly
+            # visible in the log, so do not log it.
             result["success"] = False
             result["message"] = "notStopped"
         except KeyboardInterrupt:
             # This can only happen when a request has been canceled.
             result["success"] = False
             result["message"] = "cancelled"
+        except DAPException as e:
+            # Don't normally want to see this, as it interferes with
+            # the test suite.
+            log_stack(LOG_FULL)
+            result["success"] = False
+            result["message"] = str(e)
         except BaseException as e:
             log_stack()
             result["success"] = False
diff --git a/gdb/python/lib/gdb/dap/startup.py b/gdb/python/lib/gdb/dap/startup.py
index 64a46597bf4..548fedf4ac9 100644
--- a/gdb/python/lib/gdb/dap/startup.py
+++ b/gdb/python/lib/gdb/dap/startup.py
@@ -101,6 +101,27 @@ def in_dap_thread(func):
     return ensure_dap_thread
 
 
+# Logging levels.
+LOG_DEFAULT = 1
+LOG_FULL = 2
+
+
+class LogLevel(gdb.Parameter):
+    """DAP logging level."""
+
+    set_doc = "Set the DAP logging level."
+    show_doc = "Show the DAP logging level."
+
+    def __init__(self):
+        super().__init__(
+            "debug dap-log-level", gdb.COMMAND_MAINTENANCE, gdb.PARAM_ZUINTEGER
+        )
+        self.value = LOG_DEFAULT
+
+
+_log_level = LogLevel()
+
+
 class LoggingParam(gdb.Parameter):
     """Whether DAP logging is enabled."""
 
@@ -128,16 +149,16 @@ class LoggingParam(gdb.Parameter):
 dap_log = LoggingParam()
 
 
-def log(something):
+def log(something, level=LOG_DEFAULT):
     """Log SOMETHING to the log file, if logging is enabled."""
-    if dap_log.log_file is not None:
+    if dap_log.log_file is not None and level <= _log_level.value:
         print(something, file=dap_log.log_file)
         dap_log.log_file.flush()
 
 
-def log_stack():
+def log_stack(level=LOG_DEFAULT):
     """Log a stack trace to the log file, if logging is enabled."""
-    if dap_log.log_file is not None:
+    if dap_log.log_file is not None and level <= _log_level.value:
         traceback.print_exc(file=dap_log.log_file)
 
 
