Repository: sphinx-doc/sphinx

Issue:
Show warnings with their "type" and "subtype"?
Similar to #8813, just for warnings instead of exceptions:

Currently, warning messages are shown like this:

```
WARNING: Some warning message
```

If a warning can be disabled, I think it would be helpful to display the warning `type` and `subtype`, maybe like:

```
WARNING (some_type.some_subtype): Some warning message
```


Discussion:
-0: IMO, it's better to resolve the root reason for the warning instead of suppressing. I feel it's noisy...
+1 

For [myst-parser](https://github.com/executablebooks/MyST-Parser) and [myst-nb](https://github.com/ExecutableBookProject/MyST-NB) I have had numerous people ask to silence a specific warning.
Although I encourage them to fix the warning, it is true that some warnings are more important than others. Also, as already mentioned, displaying the warning type helps to identify its origin, and thus how to fix it.

Displaying warning types has plenty of precedence in other packages, such as:

- https://mypy.readthedocs.io/en/stable/error_codes.html?highlight=show#silencing-errors-based-on-error-codes
- http://pylint.pycqa.org/en/latest/user_guide/message-control.html#block-disables

In myst-parser I now append the warning type to the end of the message: https://myst-parser.readthedocs.io/en/latest/using/howto.html#suppress-warnings

```
WARNING: Non-consecutive header level increase; 1 to 3 [myst.header]
```

> I feel it's noisy...

You could simply add a configuration option to control whether they are displayed, e.g. `show_warning_type`

Also to note, in https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-suppress_warnings it says "Now, this option should be considered experimental.".
I assume this is no longer the case, since this option has been around for a long time now?

I would consider creating a PR, but obviously would want to know first it was not likely to be rejected 😬 
@tk0miya 

> IMO, it's better to resolve the root reason for the warning instead of suppressing.

I agree, most of the times it's better to fix the root reason.

But sometimes it's not, and then it would be nice to have the additional information in order to silence the right warnings.
If not, people might just silence *all* warnings, which might be worse!

So I would add to your argument:

* most of the time, it's better to resolve the root reason for the warning instead of suppressing
* if not, it's better to suppress only certain warnings explicitly instead of suppressing all of them

> I feel it's noisy...

This would certainly add a little bit of noise, but I think it would be worth it.

For me, the main usage is in different CI tasks. I might run Sphinx in multiple CI tasks, but for certain builders (e.g. `linkcheck`) I might want to disable some warnings, which I want to keep enabled for the main `html` builder task.

Does this make sense?

@chrisjsewell 

> You could simply add a configuration option to control whether they are displayed, e.g. `show_warning_type`

This is certainly a possibility, but I feel that this would never actually be used (assuming it has the correct default value).

People who care about warning messages will keep them to a minimum (ideally 0) anyway. And people who don't care, won't care about the exact display of the warnings anyway.
> -0: IMO, it's better to resolve the root reason for the warning instead of suppressing. I feel it's noisy...

every C++ compiler gives an exact warning number, by which you can also disable the given warning type - if done right, the "noisiness" can be kept low and the output made really universal.
@tk0miya I agree that it is better to fix the underlying issue. But to present another situation where it could be useful: I'm developing an extension that parses code blocks and links the code with corresponding autodoc entries. I intend to issue warnings when those entries are not found, but the code example might still be valid. So a user might want to ignore the warning because there's nothing they can do until the entry is added (perhaps in a 3rd party library). They *can* look at the extension documentation, but it would be much faster to see the exact warning type in the message.

It is a bit noisy though 😄 and I think more often it's a valid warning that can be fixed. So if not default behavior, could this at least be available via configuration?



Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index 8ffe9365d67..04733657806 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -22,6 +22,9 @@ Deprecated
 Features added
 --------------
 
+* #12131: Added :confval:`show_warning_types` configuration option.
+  Patch by Chris Sewell.
+
 * #11701: HTML Search: Adopt the new `<search>`_ element.
   Patch by Bénédikt Tran.
 
diff --git a/doc/conf.py b/doc/conf.py
index 37f86232dce..2816935e6c0 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -23,6 +23,7 @@
 release = version
 show_authors = True
 nitpicky = True
+show_warning_types = True
 
 html_theme = 'sphinx13'
 html_theme_path = ['_themes']
diff --git a/doc/usage/configuration.rst b/doc/usage/configuration.rst
index c7b30d054af..b5a2b84934a 100644
--- a/doc/usage/configuration.rst
+++ b/doc/usage/configuration.rst
@@ -326,11 +326,19 @@ General configuration
 
    .. versionadded:: 0.5
 
+.. confval:: show_warning_types
+
+   If ``True``, the type of each warning is added as a suffix to the warning message,
+   e.g., ``WARNING: [...] [index]`` or ``WARNING: [...] [toc.circular]``.
+   The default is ``False``.
+
+   .. versionadded:: 7.3.0
+
 .. confval:: suppress_warnings
 
    A list of warning types to suppress arbitrary warning messages.
 
-   Sphinx supports following warning types:
+   Sphinx core supports following warning types:
 
    * ``app.add_node``
    * ``app.add_directive``
@@ -359,11 +367,11 @@ General configuration
    * ``toc.not_readable``
    * ``toc.secnum``
 
+   Then extensions can also define their own warning types.
+
    You can choose from these types.  You can also give only the first
    component to exclude all warnings attached to it.
 
-   Now, this option should be considered *experimental*.
-
    .. versionadded:: 1.4
 
    .. versionchanged:: 1.5
diff --git a/sphinx/config.py b/sphinx/config.py
index e90c7ebfcb8..dc8c1c11d72 100644
--- a/sphinx/config.py
+++ b/sphinx/config.py
@@ -227,6 +227,7 @@ class Config:
         'template_bridge': _Opt(None, 'html', frozenset((str,))),
         'keep_warnings': _Opt(False, 'env', ()),
         'suppress_warnings': _Opt([], 'env', ()),
+        'show_warning_types': _Opt(False, 'env', frozenset((bool,))),
         'modindex_common_prefix': _Opt([], 'html', ()),
         'rst_epilog': _Opt(None, 'env', frozenset((str,))),
         'rst_prolog': _Opt(None, 'env', frozenset((str,))),
diff --git a/sphinx/util/logging.py b/sphinx/util/logging.py
index baff073253c..a74369862ba 100644
--- a/sphinx/util/logging.py
+++ b/sphinx/util/logging.py
@@ -480,6 +480,7 @@ class SphinxLogRecordTranslator(logging.Filter):
 
     * Make a instance of SphinxLogRecord
     * docname to path if location given
+    * append warning type/subtype to message if :confval:`show_warning_types` is ``True``
     """
 
     LogRecordClass: type[logging.LogRecord]
@@ -522,6 +523,23 @@ class WarningLogRecordTranslator(SphinxLogRecordTranslator):
 
     LogRecordClass = SphinxWarningLogRecord
 
+    def filter(self, record: SphinxWarningLogRecord) -> bool:  # type: ignore[override]
+        ret = super().filter(record)
+
+        try:
+            show_warning_types = self.app.config.show_warning_types
+        except AttributeError:
+            # config is not initialized yet (ex. in conf.py)
+            show_warning_types = False
+        if show_warning_types:
+            if log_type := getattr(record, 'type', ''):
+                if log_subtype := getattr(record, 'subtype', ''):
+                    record.msg += f' [{log_type}.{log_subtype}]'
+                else:
+                    record.msg += f' [{log_type}]'
+
+        return ret
+
 
 def get_node_location(node: Node) -> str | None:
     source, line = get_source_line(node)


Test patch:
diff --git a/tests/test_util/test_util_logging.py b/tests/test_util/test_util_logging.py
index 4d506a8a862..8c621880313 100644
--- a/tests/test_util/test_util_logging.py
+++ b/tests/test_util/test_util_logging.py
@@ -10,7 +10,7 @@
 from sphinx.errors import SphinxWarning
 from sphinx.testing.util import strip_escseq
 from sphinx.util import logging, osutil
-from sphinx.util.console import colorize
+from sphinx.util.console import colorize, strip_colors
 from sphinx.util.logging import is_suppressed_warning, prefixed_warnings
 from sphinx.util.parallel import ParallelTasks
 
@@ -396,3 +396,20 @@ def test_get_node_location_abspath():
     location = logging.get_node_location(n)
 
     assert location == absolute_filename + ':'
+
+
+@pytest.mark.sphinx(confoverrides={'show_warning_types': True})
+def test_show_warning_types(app, status, warning):
+    logging.setup(app, status, warning)
+    logger = logging.getLogger(__name__)
+    logger.warning('message2')
+    logger.warning('message3', type='test')
+    logger.warning('message4', type='test', subtype='logging')
+
+    warnings = strip_colors(warning.getvalue()).splitlines()
+
+    assert warnings == [
+        'WARNING: message2',
+        'WARNING: message3 [test]',
+        'WARNING: message4 [test.logging]',
+    ]