Repository: omni-us/jsonargparse

Issue:
Subclassed ArgumentParsers are not respected
## 🐛 Bug report

If `jsonargparse.ArgumentParser` is subclassed, it will fail to execute subclassed functionality for subclass arguments. 

This is because (in the case of the help string) in jsonargparse/actions.py  in `print_help`. It calls:

```python
        subparser = import_object('jsonargparse.ArgumentParser')()
        subparser.add_class_arguments(val_class, dest, **self.sub_add_kwargs)
```

which creates an empty instance of `jsonargparse.ArgumentParser` to continue argument processing, and not whatever subclass originally invoked the parsing callstack.

I'm actually not 100% sure what is happening in the non-help case, but if I monkey patch `jsonargparse.ArgumentParser` and `jsonargparse.core.ArgumentParser` that does seem to resolve the issue. However, I'd like to avoid monkey patching if possible, hence the bug report.

### To reproduce


The following is a MWE where I've disabled the monkey patch to make it work in the way I would expect:

```python
import jsonargparse
import inspect
from jsonargparse.parameter_resolvers import ParamData
from jsonargparse.signatures import get_signature_parameters
from jsonargparse.signatures import get_doc_short_description


class MyArgumentParser(jsonargparse.ArgumentParser):
    def _add_signature_arguments(
        self,
        function_or_class,
        method_name,
        nested_key,
        as_group: bool = True,
        as_positional: bool = False,
        skip=None,
        fail_untyped: bool = True,
        sub_configs: bool = False,
        instantiate: bool = True,
        linked_targets=None
    ) -> list[str]:
        ## Create group if requested ##
        doc_group = get_doc_short_description(function_or_class, method_name, self.logger)
        component = getattr(function_or_class, method_name) if method_name else function_or_class
        group = self._create_group_if_requested(component, nested_key, as_group, doc_group, instantiate=instantiate)

        params = get_signature_parameters(function_or_class, method_name, logger=self.logger)

        if hasattr(function_or_class, '__custom__'):
            # Hack to insert our method for explicit parameterization
            __custom__ = function_or_class.__custom__
            for key, info in __custom__.items():
                type = info.get('type', None)
                if type is None or not isinstance(type, type):
                    annotation = inspect._empty
                else:
                    annotation = type
                param = ParamData(
                    name=key,
                    annotation=annotation,
                    kind=inspect.Parameter.KEYWORD_ONLY,
                    default=info.get('value', None),
                    doc=info.get('help', None),
                    component=function_or_class.__init__,
                    parent=function_or_class,
                )
                params.append(param)

        ## Add parameter arguments ##
        added_args = []
        for param in params:
            self._add_signature_parameter(
                group,
                nested_key,
                param,
                added_args,
                skip,
                fail_untyped=fail_untyped,
                sub_configs=sub_configs,
                linked_targets=linked_targets,
                as_positional=as_positional,
            )
        # import ubelt as ub
        # print('added_args = {}'.format(ub.repr2(added_args, nl=1)))
        return added_args


class ClassA:
    def __init__(self, a=None):
        self.a = a


class ClassB(ClassA):
    __custom__ = {
        'b_name': {'value': 'abc', 'type': str, help: 'hello world'}
    }
    def __init__(self, b=3, **kwargs):
        self.b = b
        self.__dict__.update(kwargs)


class ClassC:
    __custom__ = {
        'c_name': {'type': str, help: 'hello world'}
    }
    def __init__(self, c=None, **kwargs):
        self.c = c
        self.__dict__.update(kwargs)


# Monkey patch jsonargparse so its subcommands use our extended functionality
MONKEY_PATCH = 0
if MONKEY_PATCH:
    jsonargparse.ArgumentParser = MyArgumentParser
    jsonargparse.core.ArgumentParser = MyArgumentParser


def main():
    parser = MyArgumentParser()
    parser.add_subclass_arguments(ClassA, nested_key='class_a', fail_untyped=False)
    parser.add_class_arguments(ClassC, nested_key='class_c', fail_untyped=False, sub_configs=True)
    config = parser.parse_args()
    instances = parser.instantiate_classes(config)
    if hasattr(instances, 'class_a'):
        print(f'{instances.class_a.__dict__=}')
    print(f'{instances.class_c.__dict__=}')


if __name__ == '__main__':
    """
    CommandLine:
        python jsonargparse_subclass_mwe.py --help
        python jsonargparse_subclass_mwe.py --class_a.help ClassB
        python jsonargparse_subclass_mwe.py --class_a=ClassB --class_a.init_args.b_name=foo
        python jsonargparse_subclass_mwe.py --class_c.c_name=bar
        python jsonargparse_subclass_mwe.py
    """
    main()
```

Without the monkey patch if I invoke:

```
        python jsonargparse_subclass_mwe.py --class_a.help ClassB
```

I get:

```
A cfg=Namespace(class_c=Namespace(c=None, c_name=None))
namespace=None
usage: jsonargparse_subclass_mwe.py [--class_a.init_args.b B]

<class '__main__.ClassB'>:
  --class_a.init_args.b B
                        (type: Any, default: 3)
```

which does not respect the customized arguments injected by my subclass.

Furthermore, running:  `python jsonargparse_subclass_mwe.py --class_a=ClassB --class_a.init_args.b_name=foo` doesn't even work:

```
jsonargparse_subclass_mwe.py: error: Parser key "class_a": Problem with given class_path "__main__.ClassB":
  - 'Configuration check failed :: No action for destination key "b_name" to check its value.'
```

### Expected behavior

If I include the monkey patch then invoking the above help command correctly results in the customized arguments being respected: 

```
usage: jsonargparse_subclass_mwe.py [--class_a.init_args.b B] [--class_a.init_args.b_name B_NAME]

<class '__main__.ClassB'>:
  --class_a.init_args.b B
                        (type: Any, default: 3)
  --class_a.init_args.b_name B_NAME
                        (type: Any, default: abc)
```

And if I invoke: `python jsonargparse_subclass_mwe.py --class_a=ClassB --class_a.init_args.b_name=foo` I get 

```
instances.class_a.__dict__={'b': 3, 'b_name': 'foo'}
instances.class_c.__dict__={'c': None, 'c_name': None}
``` 

as I would expect.

### Environment

jsonargparse.__version__ = 4.14.1


### Notes:

Fixing this issue naively would break LightningCLI as noted here: https://github.com/Lightning-AI/lightning/issues/15038


Discussion:
sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=omni-us_jsonargparse&pullRequest=225)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=225&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=225&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=225&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=225&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=225&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=225&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=225&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=225&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=225&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=225&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=225&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=225&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=225) No Coverage information  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=225&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=225&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=omni-us_jsonargparse&pullRequest=225)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=225&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=225&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=225&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=225&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=225&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=225&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=225&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=225&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=225&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=225&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=225&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=225&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=225) No Coverage information  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=225&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=225&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 0468ec3..561695c 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -17,7 +17,7 @@ v4.19.0 (2022-12-??)
 
 Added
 ^^^^^
-- ``CLI`` now exposes the ``fail_untyped`` parameter.
+- ``CLI`` now supports the ``fail_untyped`` and ``parser_class`` parameters.
 - ``bytes`` and ``bytearray`` registered on first use and decodes from standard
   Base64.
 - Support getting the import path of variables in modules, e.g.
@@ -32,6 +32,8 @@ Added
 - Path types now implement the ``os.PathLike`` protocol.
 - New path mode ``cc`` to not require the parent directory to exists but that it
   can be created.
+- The parent parser class is now used to create internal parsers `#171
+  <https://github.com/omni-us/jsonargparse/issues/171>`__.
 
 Fixed
 ^^^^^
@@ -46,6 +48,7 @@ Fixed
 - Argument links with target a subclass mixed with other types not working `#208
   <https://github.com/omni-us/jsonargparse/issues/208>`__.
 - Failures when using a sequence type and the default is a tuple.
+- Parent parser logger not being forwarded to subcommand and internal parsers.
 
 Changed
 ^^^^^^^
@@ -55,6 +58,9 @@ Changed
 - The ``signatures`` extras now installs the ``typeshed-client`` package.
 - ``validators`` package is no longer a dependency.
 - Path types are no longer a subclass of ``str``.
+- Parsing steps logging now at debug level.
+- Discarding ``init_args`` warning changed to log at debug level.
+- Removed replacing list instead of append warning.
 
 
 v4.18.0 (2022-11-29)
diff --git a/jsonargparse/_common.py b/jsonargparse/_common.py
new file mode 100644
index 0000000..7a8bfa6
--- /dev/null
+++ b/jsonargparse/_common.py
@@ -0,0 +1,35 @@
+from contextlib import contextmanager
+from contextvars import ContextVar
+from typing import Optional, Union
+
+from .namespace import Namespace
+from .type_checking import ArgumentParser
+
+parent_parser: ContextVar['ArgumentParser'] = ContextVar('parent_parser')
+parser_capture: ContextVar[bool] = ContextVar('parser_capture', default=False)
+defaults_cache: ContextVar[Optional[Namespace]] = ContextVar('defaults_cache', default=None)
+lenient_check: ContextVar[Union[bool, str]] = ContextVar('lenient_check', default=False)
+load_value_mode: ContextVar[Optional[str]] = ContextVar('load_value_mode', default=None)
+
+
+parser_context_vars = dict(
+    parent_parser=parent_parser,
+    parser_capture=parser_capture,
+    defaults_cache=defaults_cache,
+    lenient_check=lenient_check,
+    load_value_mode=load_value_mode,
+)
+
+
+@contextmanager
+def parser_context(**kwargs):
+    context_var_tokens = []
+    for name, value in kwargs.items():
+        context_var = parser_context_vars[name]
+        token = context_var.set(value)
+        context_var_tokens.append((context_var, token))
+    try:
+        yield
+    finally:
+        for context_var, token in context_var_tokens:
+            context_var.reset(token)
diff --git a/jsonargparse/actions.py b/jsonargparse/actions.py
index 29083ff..ee64ac1 100644
--- a/jsonargparse/actions.py
+++ b/jsonargparse/actions.py
@@ -4,17 +4,21 @@ import os
 import re
 import sys
 import warnings
-from argparse import SUPPRESS, Action, _HelpAction, _SubParsersAction
+from argparse import SUPPRESS
+from argparse import Action as ArgparseAction
+from argparse import _HelpAction, _SubParsersAction
 from contextlib import contextmanager
 from contextvars import ContextVar
 from typing import Any, Dict, List, Optional, Tuple, Type, Union
 
+from ._common import parser_context
 from .loaders_dumpers import get_loader_exceptions, load_value
 from .namespace import Namespace, split_key, split_key_root
 from .optionals import FilesCompleterMethod, get_config_read_mode
 from .type_checking import ArgumentParser
 from .typing import path_type
 from .util import (
+    LoggerProperty,
     NoneType,
     ParserError,
     Path,
@@ -25,7 +29,6 @@ from .util import (
     indent_text,
     is_subclass,
     iter_to_set_str,
-    lenient_check_context,
     parse_value_or_config,
 )
 
@@ -37,6 +40,10 @@ __all__ = [
 ]
 
 
+class Action(LoggerProperty, ArgparseAction):
+    """Base for jsonargparse Action classes."""
+
+
 def _is_branch_key(parser, key: str) -> bool:
     root_key = split_key_root(key)[0]
     for action in filter_default_actions(parser._actions):
@@ -51,8 +58,8 @@ def _is_branch_key(parser, key: str) -> bool:
 def _find_action_and_subcommand(
     parser: 'ArgumentParser',
     dest: str,
-    exclude: Optional[Union[Type[Action], Tuple[Type[Action], ...]]] = None,
-) -> Tuple[Optional[Action], Optional[str]]:
+    exclude: Optional[Union[Type[ArgparseAction], Tuple[Type[ArgparseAction], ...]]] = None,
+) -> Tuple[Optional[ArgparseAction], Optional[str]]:
     """Finds an action in a parser given its destination key.
 
     Args:
@@ -88,16 +95,16 @@ def _find_action_and_subcommand(
 def _find_action(
     parser: 'ArgumentParser',
     dest: str,
-    exclude: Optional[Union[Type[Action], Tuple[Type[Action], ...]]] = None,
-) -> Optional[Action]:
+    exclude: Optional[Union[Type[ArgparseAction], Tuple[Type[ArgparseAction], ...]]] = None,
+) -> Optional[ArgparseAction]:
     return _find_action_and_subcommand(parser, dest, exclude=exclude)[0]
 
 
 def _find_parent_action_and_subcommand(
     parser: 'ArgumentParser',
     key: str,
-    exclude: Optional[Union[Type[Action], Tuple[Type[Action], ...]]] = None,
-) -> Tuple[Optional[Action], Optional[str]]:
+    exclude: Optional[Union[Type[ArgparseAction], Tuple[Type[ArgparseAction], ...]]] = None,
+) -> Tuple[Optional[ArgparseAction], Optional[str]]:
     action, subcommand = _find_action_and_subcommand(parser, key, exclude=exclude)
     if action is None and '.' in key:
         parts = split_key(key)
@@ -111,12 +118,12 @@ def _find_parent_action_and_subcommand(
 def _find_parent_action(
     parser: 'ArgumentParser',
     key: str,
-    exclude: Optional[Union[Type[Action], Tuple[Type[Action], ...]]] = None,
-) -> Optional[Action]:
+    exclude: Optional[Union[Type[ArgparseAction], Tuple[Type[ArgparseAction], ...]]] = None,
+) -> Optional[ArgparseAction]:
     return _find_parent_action_and_subcommand(parser, key, exclude=exclude)[0]
 
 
-def _is_action_value_list(action: Action) -> bool:
+def _is_action_value_list(action: ArgparseAction) -> bool:
     """Checks whether an action produces a list value.
 
     Args:
@@ -266,7 +273,7 @@ class _ActionPrintConfig(Action):
             subparser = parser.print_config.pop('subparser')
             if key is not None:
                 cfg = cfg[key]
-            with lenient_check_context():
+            with parser_context(lenient_check=True):
                 sys.stdout.write(subparser.dump(cfg, **parser.print_config))
             delattr(parser, 'print_config')
             parser.exit()
@@ -570,7 +577,8 @@ def parent_parsers_context(key, parser):
 class _ActionSubCommands(_SubParsersAction):
     """Extension of argparse._SubParsersAction to modify subcommands functionality."""
 
-    _env_prefix: Union[bool, str] = True
+    parent_parser: 'ArgumentParser'
+    env_prefix: str
 
 
     def add_parser(self, name, **kwargs):
@@ -589,11 +597,12 @@ class _ActionSubCommands(_SubParsersAction):
             raise ValueError('Multiple levels of subcommands must be added in level order.')
 
         parser.prog = f'{self._prog_prefix} [options] {name}'
-        parser.env_prefix = f'{self._env_prefix}_{name}_'
+        parser.env_prefix = f'{self.env_prefix}{name}_'
         parser.default_env = self.parent_parser.default_env
         parser.parent_parser = self.parent_parser
         parser.parser_mode = self.parent_parser.parser_mode
         parser.error_handler = self.parent_parser.error_handler
+        parser.logger = self.parent_parser.logger
         parser.subcommand = name
 
         # create a pseudo-action to hold the choice help
diff --git a/jsonargparse/cli.py b/jsonargparse/cli.py
index 76a9673..92fbeda 100644
--- a/jsonargparse/cli.py
+++ b/jsonargparse/cli.py
@@ -19,6 +19,7 @@ def CLI(
     set_defaults: Optional[Dict[str, Any]] = None,
     as_positional: bool = True,
     fail_untyped: bool = True,
+    parser_class: Type[ArgumentParser] = ArgumentParser,
     **kwargs
 ):
     """Simple creation of command line interfaces.
@@ -36,6 +37,7 @@ def CLI(
         set_defaults: Dictionary of values to override components defaults.
         as_positional: Whether to add required parameters as positional arguments.
         fail_untyped: Whether to raise exception if a required parameter does not have a type.
+        parser_class: The ArgumentParser class to use.
         **kwargs: Used to instantiate :class:`.ArgumentParser`.
 
     Returns:
@@ -59,7 +61,7 @@ def CLI(
     elif not isinstance(components, list):
         components = [components]
 
-    parser = ArgumentParser(default_meta=False, **kwargs)
+    parser = parser_class(default_meta=False, **kwargs)
     parser.add_argument('--config', action=ActionConfigFile, help=config_help)
 
     if len(components) == 1:
@@ -78,7 +80,7 @@ def CLI(
     comp_dict = {c.__name__: c for c in components}
     for name, component in comp_dict.items():
         description = get_help_str(component, parser.logger)
-        subparser = ArgumentParser(description=description)
+        subparser = parser_class(description=description)
         subparser.add_argument('--config', action=ActionConfigFile, help=config_help)
         subcommands.add_subcommand(name, subparser, help=get_help_str(component, parser.logger))
         added_args = _add_component_to_parser(component, subparser, as_positional, fail_untyped, config_help)
@@ -115,7 +117,7 @@ def _add_component_to_parser(component, parser, as_positional, fail_untyped, con
         subcommands = parser.add_subcommands(required=True)
         for key in [k for k, v in inspect.getmembers(component) if callable(v) and k[0] != '_']:
             description = get_help_str(getattr(component, key), parser.logger)
-            subparser = ArgumentParser(description=description)
+            subparser = type(parser)(description=description)
             subparser.add_argument('--config', action=ActionConfigFile, help=config_help)
             added_subargs = subparser.add_method_arguments(component, key, as_group=False, **kwargs)
             added_args += [f'{key}.{a}' for a in added_subargs]
diff --git a/jsonargparse/core.py b/jsonargparse/core.py
index 5901e00..877e807 100644
--- a/jsonargparse/core.py
+++ b/jsonargparse/core.py
@@ -21,7 +21,9 @@ from typing import (
     Union,
 )
 
+from ._common import lenient_check, parser_context
 from .actions import (
+    Action,
     ActionConfigFile,
     ActionParser,
     _ActionConfigLoad,
@@ -37,7 +39,7 @@ from .actions import (
     parent_parsers,
     previous_config,
 )
-from .formatters import DefaultHelpFormatter, empty_help, formatter_context, get_env_var
+from .formatters import DefaultHelpFormatter, empty_help, get_env_var
 from .jsonnet import ActionJsonnet, ActionJsonnetExtVars
 from .jsonschema import ActionJsonSchema
 from .link_arguments import ActionLink, ArgumentLinking
@@ -46,7 +48,6 @@ from .loaders_dumpers import (
     dump_using_format,
     get_loader_exceptions,
     load_value,
-    load_value_context,
     loaders,
     set_omegaconf_loader,
     yaml_load,
@@ -79,8 +80,6 @@ from .util import (
     get_private_kwargs,
     identity,
     is_subclass,
-    lenient_check,
-    lenient_check_context,
     return_parser_if_captured,
     usage_and_exit_error_handler,
 )
@@ -134,9 +133,10 @@ class ActionsContainer(SignatureArguments, argparse._ActionsContainer):
                     kwargs=kwargs,
                     enable_path=enable_path,
                     container=super(),
-                    logger=self.logger,
+                    logger=self._logger,
                 )
         action = super().add_argument(*args, **kwargs)
+        action.logger = self._logger  # type: ignore
         if isinstance(action, ActionConfigFile) and getattr(self, '_print_config', None) is not None:
             self.add_argument(self._print_config, action=_ActionPrintConfig)  # type: ignore
         if is_meta_key(action.dest):
@@ -169,7 +169,7 @@ class ActionsContainer(SignatureArguments, argparse._ActionsContainer):
         parser = self.parser if hasattr(self, 'parser') else self
         if name is not None and name in parser.groups:
             raise ValueError(f'Group with name {name} already exists.')
-        group = _ArgumentGroup(parser, *args, logger=parser.logger, **kwargs)
+        group = _ArgumentGroup(parser, *args, logger=parser._logger, **kwargs)
         group.parser = parser
         parser._action_groups.append(group)
         if name is not None:
@@ -214,7 +214,7 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
         are supported. Additionally it accepts:
 
         Args:
-            env_prefix: Prefix for environment variables.
+            env_prefix: Prefix for environment variables. ``True`` to derive from ``prog``.
             error_handler: Handler for parsing errors, if None raises :class:`.ParserError` exception.
             formatter_class: Class for printing help messages.
             logger: Configures the logger, see :class:`.LoggerProperty`.
@@ -252,18 +252,10 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
         if caller not in {'jsonargparse', 'argcomplete'}:
             raise NotImplementedError('parse_known_args not implemented to dissuade its use, since typos in configs would go unnoticed.')
 
-        if args is None:
-            args = sys.argv[1:]
-        else:
-            args = list(args)
-            if not all(isinstance(a, str) for a in args):
-                self.error(f'All arguments are expected to be strings: {args}')
-        self.args = args
-
         namespace = argcomplete_namespace(caller, self, namespace)
 
         try:
-            with patch_namespace(), lenient_check_context(caller), ActionTypeHint.subclass_arg_context(self), load_value_context(self.parser_mode):
+            with patch_namespace(), parser_context(parent_parser=self, lenient_check=True), ActionTypeHint.subclass_arg_context(self):
                 namespace, args = self._parse_known_args(args, namespace)
         except (argparse.ArgumentError, ParserError) as ex:
             self.error(str(ex), ex)
@@ -290,7 +282,6 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
         skip_required: bool = False,
         skip_subcommands: bool = False,
         fail_no_subcommand: bool = True,
-        log_message: Optional[str] = None,
     ) -> Namespace:
         """Common parsing code used by other parse methods.
 
@@ -303,7 +294,6 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
             skip_required: Whether to skip check of required arguments.
             skip_subcommands: Whether to skip subcommand processing.
             fail_no_subcommand: Whether to fail if no subcommand given.
-            log_message: Message to log at INFO level after parsing.
 
         Returns:
             A config object with all parsed values.
@@ -315,12 +305,12 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
             _ActionSubCommands.handle_subcommands(self, cfg, env=env, defaults=defaults, fail_no_subcommand=fail_no_subcommand)
 
         if defaults:
-            with lenient_check_context():
+            with parser_context(lenient_check=True):
                 ActionTypeHint.add_sub_defaults(self, cfg)
 
         _ActionPrintConfig.print_config_if_requested(self, cfg)
 
-        with load_value_context(self.parser_mode):
+        with parser_context(parent_parser=self):
             ActionLink.apply_parsing_links(self, cfg)
 
             if not skip_check and not lenient_check.get():
@@ -329,9 +319,6 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
         if not (with_meta or (with_meta is None and self._default_meta)):
             cfg = strip_meta(cfg)
 
-        if log_message is not None:
-            self._logger.info(log_message)
-
         return cfg
 
 
@@ -348,7 +335,7 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
         if env or (env is None and self._default_env):
             if environ is None:
                 environ = os.environ
-            with load_value_context(self.parser_mode):
+            with parser_context(load_value_mode=self.parser_mode):
                 cfg_env = self._load_env_vars(env=environ, defaults=defaults)
             cfg = self.merge_config(cfg_env, cfg)
 
@@ -386,6 +373,14 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
         return_parser_if_captured(self)
         argcomplete_autocomplete(self)
 
+        if args is None:
+            args = sys.argv[1:]
+        else:
+            args = list(args)
+            if not all(isinstance(a, str) for a in args):
+                self.error(f'All arguments are expected to be strings: {args}')
+        self.args = args
+
         try:
             cfg = self._parse_defaults_and_environ(defaults, env)
             if namespace:
@@ -402,12 +397,12 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
                 defaults=defaults,
                 with_meta=with_meta,
                 skip_check=skip_check,
-                log_message='Parsed command line arguments.',
             )
 
         except (TypeError, KeyError) as ex:
             self.error(str(ex), ex)
 
+        self._logger.debug('Parsed command line arguments: %s', args)
         return parsed_cfg
 
 
@@ -441,8 +436,8 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
             if cfg_base:
                 cfg = self.merge_config(cfg_base, cfg)
 
-            cfg_obj = self._apply_actions(cfg_obj, prev_cfg=cfg)
-            cfg = self.merge_config(cfg_obj, cfg)
+            cfg_apply = self._apply_actions(cfg_obj, prev_cfg=cfg)
+            cfg = self.merge_config(cfg_apply, cfg)
 
             parsed_cfg = self._parse_common(
                 cfg=cfg,
@@ -451,12 +446,12 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
                 with_meta=with_meta,
                 skip_check=skip_check,
                 skip_required=skip_required,
-                log_message='Parsed object.',
             )
 
         except (TypeError, KeyError) as ex:
             self.error(str(ex), ex)
 
+        self._logger.debug('Parsed object: %s', cfg_obj)
         return parsed_cfg
 
 
@@ -525,12 +520,12 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
                 with_meta=with_meta,
                 skip_check=skip_check,
                 skip_subcommands=skip_subcommands,
-                log_message='Parsed environment variables.',
             )
 
         except (TypeError, KeyError) as ex:
             self.error(str(ex), ex)
 
+        self._logger.debug('Parsed environment variables')
         return parsed_cfg
 
 
@@ -571,8 +566,7 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
                 **kwargs,
             )
 
-        self._logger.info(f'Parsed {self.parser_mode} from path: {cfg_path}')
-
+        self._logger.debug('Parsed configuration from path: %s', cfg_path)
         return parsed_cfg
 
 
@@ -605,7 +599,7 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
         skip_check, fail_no_subcommand = get_private_kwargs(kwargs, _skip_check=False, _fail_no_subcommand=True)
 
         try:
-            with load_value_context(self.parser_mode):
+            with parser_context(load_value_mode=self.parser_mode):
                 cfg = self._load_config_parser_mode(cfg_str, cfg_path, ext_vars, previous_config.get())
 
             if defaults or env:
@@ -619,12 +613,12 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
                 with_meta=with_meta,
                 skip_check=skip_check,
                 fail_no_subcommand=fail_no_subcommand,
-                log_message=f'Parsed {self.parser_mode} string.',
             )
 
         except (TypeError, KeyError) as ex:
             self.error(str(ex), ex)
 
+        self._logger.debug('Parsed %s string: %s', self.parser_mode, cfg_str)
         return parsed_cfg
 
 
@@ -677,14 +671,14 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
         """
         if 'description' not in kwargs:
             kwargs['description'] = 'For more details of each subcommand, add it as an argument followed by --help.'
-        with formatter_context(self):
+        with parser_context(parent_parser=self):
             subcommands: _ActionSubCommands = super().add_subparsers(dest=dest, **kwargs)  # type: ignore
         if required:
             self.required_args.add(dest)
         subcommands._required = required  # type: ignore
         subcommands.required = False
-        subcommands.parent_parser = self  # type: ignore
-        subcommands._env_prefix = self.env_prefix
+        subcommands.parent_parser = self
+        subcommands.env_prefix = get_env_var(self)
         self._subcommands_action = subcommands
         return subcommands
 
@@ -721,7 +715,7 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
         cfg = strip_meta(cfg)
         ActionLink.strip_link_target_keys(self, cfg)
 
-        with load_value_context(self.parser_mode):
+        with parser_context(load_value_mode=self.parser_mode):
             if not skip_check:
                 self.check_config(cfg)
 
@@ -736,7 +730,7 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
                 self._dump_cleanup_actions(defaults, self._actions, {'skip_check': True, 'skip_none': skip_none})
                 self._dump_delete_default_entries(cfg_dict, defaults.as_dict())
 
-        with formatter_context(self):
+        with parser_context(parent_parser=self):
             return dump_using_format(self, cfg_dict, 'yaml_comments' if yaml_comments else format)
 
 
@@ -755,7 +749,8 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
             elif isinstance(action, ActionTypeHint):
                 value = cfg.get(action_dest)
                 if value is not None:
-                    value = action.serialize(value, dump_kwargs=dump_kwargs)
+                    with parser_context(parent_parser=self):
+                        value = action.serialize(value, dump_kwargs=dump_kwargs)
                     cfg.update(value, action_dest)
 
 
@@ -767,7 +762,8 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
                 class_object_val = None
                 if is_subclass_spec(val):
                     if val['class_path'] != default.get('class_path'):
-                        parser = ActionTypeHint.get_class_parser(val['class_path'])
+                        with parser_context(parent_parser=self):
+                            parser = ActionTypeHint.get_class_parser(val['class_path'])
                         default = {'init_args': parser.get_defaults().as_dict()}
                     class_object_val = val
                     val = val.get('init_args')
@@ -839,7 +835,7 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
             ActionLink.strip_link_target_keys(self, cfg)
 
             if not skip_check:
-                with load_value_context(self.parser_mode):
+                with parser_context(load_value_mode=self.parser_mode):
                     self.check_config(strip_meta(cfg), branch=branch)
 
             def save_paths(cfg):
@@ -868,7 +864,7 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
                             f.write(val.get_content())
                         cfg[key] = type(val)(str(val_path))
 
-            with change_to_path_dir(path_fc), formatter_context(self):
+            with change_to_path_dir(path_fc), parser_context(parent_parser=self):
                 save_paths(cfg)
             dump_kwargs['skip_check'] = True
             with open(path_fc(), 'w') as f:
@@ -968,11 +964,11 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
             ):
                 cfg[action.dest] = recreate_branches(action.default)
 
-        self._logger.info('Loaded default values from parser.')
+        self._logger.debug('Loaded default values from parser')
 
         default_config_files = self._get_default_config_files()
         for key, default_config_file in default_config_files:
-            with change_to_path_dir(default_config_file), load_value_context(self.parser_mode):
+            with change_to_path_dir(default_config_file), parser_context(parent_parser=self):
                 cfg_file = self._load_config_parser_mode(default_config_file.get_content())
                 if key is not None:
                     cfg_file = cfg_file.get(key)
@@ -996,7 +992,7 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
                 cfg['__default_config__'] = [meta, default_config_file]
             else:
                 cfg['__default_config__'] = default_config_file
-            self._logger.info(f'Parsed configuration from default path: {default_config_file}')
+            self._logger.debug('Parsed default configuration from path: %s', default_config_file)
 
         ActionTypeHint.add_sub_defaults(self, cfg)
 
@@ -1084,7 +1080,7 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
         try:
             if not skip_required and not lenient_check.get():
                 check_required(cfg, self)
-            with load_value_context(self.parser_mode):
+            with parser_context(load_value_mode=self.parser_mode):
                 check_values(cfg)
         except (TypeError, KeyError) as ex:
             prefix = 'Configuration check failed :: '
@@ -1133,10 +1129,10 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
                     pass
                 else:
                     if value is not None:
-                        with load_value_context(self.parser_mode):
+                        with parser_context(parent_parser=self):
                             parent[key] = component.instantiate_classes(value)
             else:
-                with load_value_context(self.parser_mode):
+                with parser_context(load_value_mode=self.parser_mode):
                     component.instantiate_class(component, cfg)
 
         ActionLink.apply_instantiation_links(self, cfg, order=order)
@@ -1203,13 +1199,13 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
                 note = f'tried getting defaults considering default_config_files but failed due to: {ex}'
             group = self._default_config_files_group
             group.description = f'{self._default_config_files}, Note: {note}'
-        with formatter_context(self, defaults):
+        with parser_context(parent_parser=self, defaults_cache=defaults):
             help_str = super().format_help()
         return help_str
 
 
     def print_usage(self, file=None):
-        with formatter_context(self):
+        with parser_context(parent_parser=self):
             return super().print_usage(file)
 
 
@@ -1265,7 +1261,7 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
             value = cfg[action_dest]
             if skip_fn and skip_fn(value):
                 continue
-            with lenient_check_context(), load_value_context(self.parser_mode):
+            with parser_context(parent_parser=self, lenient_check=True):
                 value = self._check_value_key(action, value, action_dest, prev_cfg)
             if isinstance(action, _ActionConfigLoad):
                 config_keys.add(action_dest)
@@ -1287,7 +1283,8 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
             A new object with the merged configuration.
         """
         cfg = cfg_to.clone()
-        ActionTypeHint.discard_init_args_on_class_path_change(self, cfg, cfg_from)
+        with parser_context(parent_parser=self):
+            ActionTypeHint.discard_init_args_on_class_path_change(self, cfg, cfg_from)
         cfg.update(cfg_from)
         ActionTypeHint.apply_appends(self, cfg)
         return cfg
@@ -1316,7 +1313,8 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
             if isinstance(value, str):
                 value = action.check_type(value, self)
         elif hasattr(action, '_check_type'):
-            value = action._check_type(value, cfg=cfg)
+            with parser_context(parent_parser=self):
+                value = action._check_type(value, cfg=cfg)
         elif action.type is not None:
             try:
                 if action.nargs in {None, '?'} or action.nargs == 0:
@@ -1428,7 +1426,7 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
 
 
     @default_meta.setter
-    def default_meta(self, default_meta:bool):
+    def default_meta(self, default_meta: bool):
         if isinstance(default_meta, bool):
             self._default_meta = default_meta
         else:
@@ -1445,25 +1443,23 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
         Raises:
             ValueError: If an invalid value is given.
         """
-        return self._env_prefix  # type: ignore
+        return self._env_prefix
 
 
     @env_prefix.setter
     def env_prefix(self, env_prefix: Union[bool, str]):
-        if env_prefix is False:
-            self._env_prefix = None
-        elif env_prefix in {True, None}:
-            if env_prefix is None:
-                from .deprecated import (
-                    deprecation_warning,
-                    env_prefix_property_none_message,
-                )
-                deprecation_warning(ArgumentParser, env_prefix_property_none_message)
-            self._env_prefix = os.path.splitext(self.prog)[0]
-        elif isinstance(env_prefix, str):
-            self._env_prefix = env_prefix
-        else:
-            raise ValueError('env_prefix has to be a string or None.')
+        if env_prefix is None:
+            from .deprecated import (
+                deprecation_warning,
+                env_prefix_property_none_message,
+            )
+            deprecation_warning(ArgumentParser, env_prefix_property_none_message)
+            env_prefix = False
+        elif env_prefix is True:
+            env_prefix = os.path.splitext(self.prog)[0]
+        elif not isinstance(env_prefix, (bool, str)):
+            raise ValueError('env_prefix must be a string or a boolean.')
+        self._env_prefix = env_prefix
 
 
     @property
diff --git a/jsonargparse/formatters.py b/jsonargparse/formatters.py
index 381a1e1..afae6c4 100644
--- a/jsonargparse/formatters.py
+++ b/jsonargparse/formatters.py
@@ -9,12 +9,11 @@ from argparse import (
     HelpFormatter,
     _HelpAction,
 )
-from contextlib import contextmanager
-from contextvars import ContextVar
 from io import StringIO
 from string import Template
 from typing import Optional, Union
 
+from ._common import defaults_cache, parent_parser
 from .actions import (
     ActionConfigFile,
     ActionYesNo,
@@ -35,21 +34,6 @@ __all__ = ['DefaultHelpFormatter']
 empty_help: str = '_EMPTY_HELP_'
 
 
-formatter_parser: ContextVar = ContextVar('formatter_parser')
-formatter_defaults: ContextVar = ContextVar('formatter_defaults')
-
-
-@contextmanager
-def formatter_context(parser: 'ArgumentParser', defaults: Optional[Namespace] = None):
-    prev_parser = formatter_parser.set(parser)
-    prev_defaults = formatter_defaults.set(defaults)
-    try:
-        yield
-    finally:
-        formatter_parser.reset(prev_parser)
-        formatter_defaults.reset(prev_defaults)
-
-
 class PercentTemplate(Template):
     delimiter = '%'
     pattern = r'''
@@ -99,7 +83,7 @@ class DefaultHelpFormatter(HelpFormatter):
 
     def _format_usage(self, *args, **kwargs):
         usage = super()._format_usage(*args, **kwargs)
-        parser = formatter_parser.get()
+        parser = parent_parser.get()
         for key in parser.required_args:
             try:
                 default = parser.get_default(key)
@@ -111,7 +95,7 @@ class DefaultHelpFormatter(HelpFormatter):
 
 
     def _format_action_invocation(self, action):
-        parser = formatter_parser.get()
+        parser = parent_parser.get()
         if action.option_strings == [] or action.default == SUPPRESS or not parser.default_env:
             return super()._format_action_invocation(action)
         extr = ''
@@ -136,7 +120,7 @@ class DefaultHelpFormatter(HelpFormatter):
         if params.get('default') == SUPPRESS:
             del params['default']
         elif 'default' in params:
-            defaults = formatter_defaults.get()
+            defaults = defaults_cache.get()
             if defaults is not None:
                 params['default'] = action.default = defaults.get(action.dest)
             if params['default'] is None:
@@ -179,7 +163,7 @@ class DefaultHelpFormatter(HelpFormatter):
                     subparsers.update(get_subparsers(subparser, prefix=full_key))
             return subparsers
 
-        parser = formatter_parser.get()
+        parser = parent_parser.get()
         parsers = get_subparsers(parser)
         parsers[None] = parser
 
@@ -268,12 +252,19 @@ class DefaultHelpFormatter(HelpFormatter):
         cfg.yaml_set_comment_before_after_key(key, before='\n'+text, indent=2*depth)
 
 
-def get_env_var(parser_or_formatter: Union['ArgumentParser', DefaultHelpFormatter], action: Action) -> str:
-    """Returns the environment variable for a given parser or formatter and action."""
+def get_env_var(
+    parser_or_formatter: Union['ArgumentParser', DefaultHelpFormatter],
+    action: Optional[Action] = None,
+) -> str:
+    """Returns the environment variable name for a given parser or formatter and action."""
     if isinstance(parser_or_formatter, DefaultHelpFormatter):
-        parser = formatter_parser.get()
+        parser = parent_parser.get()
     else:
         parser = parser_or_formatter
-    env_var = (parser.env_prefix+'_' if parser.env_prefix else '') + action.dest
+    env_var = ''
+    if isinstance(parser.env_prefix, str):
+        env_var = parser.env_prefix + '_'
+    if action:
+        env_var += action.dest
     env_var = env_var.replace('.', '__').upper()
     return env_var
diff --git a/jsonargparse/jsonnet.py b/jsonargparse/jsonnet.py
index a1b951e..5d4eefe 100644
--- a/jsonargparse/jsonnet.py
+++ b/jsonargparse/jsonnet.py
@@ -1,11 +1,11 @@
 """Actions to support jsonnet."""
 
-from argparse import Action
 from typing import Any, Dict, Optional, Tuple, Union
 
-from .actions import _is_action_value_list
+from ._common import parser_context
+from .actions import Action, _is_action_value_list
 from .jsonschema import ActionJsonSchema
-from .loaders_dumpers import get_loader_exceptions, load_value, load_value_context
+from .loaders_dumpers import get_loader_exceptions, load_value
 from .optionals import (
     get_config_read_mode,
     get_jsonschema_exceptions,
@@ -64,7 +64,7 @@ class ActionJsonnet(Action):
             if schema is not None:
                 jsonvalidator = import_jsonschema('ActionJsonnet')[1]
                 if isinstance(schema, str):
-                    with load_value_context('yaml'):
+                    with parser_context(load_value_mode='yaml'):
                         try:
                             schema = load_value(schema)
                         except get_loader_exceptions() as ex:
@@ -163,7 +163,7 @@ class ActionJsonnet(Action):
             fname = jsonnet(absolute=False) if isinstance(jsonnet, Path) else jsonnet
             snippet = fpath.get_content()
         try:
-            with load_value_context('yaml'):
+            with parser_context(load_value_mode='yaml'):
                 values = load_value(_jsonnet.evaluate_snippet(fname, snippet, ext_vars=ext_vars, ext_codes=ext_codes))
         except RuntimeError as ex:
             raise ParserError(f'Problems evaluating jsonnet "{fname}" :: {ex}') from ex
diff --git a/jsonargparse/jsonschema.py b/jsonargparse/jsonschema.py
index cb554f2..4e2dc3f 100644
--- a/jsonargparse/jsonschema.py
+++ b/jsonargparse/jsonschema.py
@@ -1,11 +1,11 @@
 """Action to support jsonschemas."""
 
 import os
-from argparse import Action
 from typing import Dict, Optional, Union
 
-from .actions import _is_action_value_list
-from .loaders_dumpers import get_loader_exceptions, load_value, load_value_context
+from ._common import parser_context
+from .actions import Action, _is_action_value_list
+from .loaders_dumpers import get_loader_exceptions, load_value
 from .namespace import strip_meta
 from .optionals import (
     argcomplete_warn_redraw_prompt,
@@ -40,7 +40,7 @@ class ActionJsonSchema(Action):
         """
         if schema is not None:
             if isinstance(schema, str):
-                with load_value_context('yaml'):
+                with parser_context(load_value_mode='yaml'):
                     try:
                         schema = load_value(schema)
                     except get_loader_exceptions() as ex:
diff --git a/jsonargparse/link_arguments.py b/jsonargparse/link_arguments.py
index 97030b3..72b1d2b 100644
--- a/jsonargparse/link_arguments.py
+++ b/jsonargparse/link_arguments.py
@@ -2,13 +2,15 @@
 
 import inspect
 import re
-from argparse import SUPPRESS, Action
+from argparse import SUPPRESS
+from argparse import Action as ArgparseAction
 from collections import defaultdict
 from contextlib import contextmanager
 from contextvars import ContextVar
 from typing import Any, Callable, List, Optional, Tuple, Type, Union
 
 from .actions import (
+    Action,
     ActionConfigFile,
     _ActionConfigLoad,
     _ActionSubCommands,
@@ -24,9 +26,9 @@ __all__ = ['ArgumentLinking']
 def find_parent_or_child_actions(
     parser: 'ArgumentParser',
     key: str,
-    exclude: Optional[Union[Type[Action], Tuple[Type[Action], ...]]] = None,
-) -> Optional[List[Action]]:
-    found: List[Action] = []
+    exclude: Optional[Union[Type[ArgparseAction], Tuple[Type[ArgparseAction], ...]]] = None,
+) -> Optional[List[ArgparseAction]]:
+    found: List[ArgparseAction] = []
     action = _find_parent_action(parser, key, exclude=exclude)
     if action is not None:
         found = [action]
@@ -42,8 +44,8 @@ def find_parent_or_child_actions(
 def find_subclass_action_or_class_group(
     parser: 'ArgumentParser',
     key: str,
-    exclude: Optional[Union[Type[Action], Tuple[Type[Action], ...]]] = None,
-) -> Optional[Union[Action, '_ArgumentGroup']]:
+    exclude: Optional[Union[Type[ArgparseAction], Tuple[Type[ArgparseAction], ...]]] = None,
+) -> Optional[Union[ArgparseAction, '_ArgumentGroup']]:
     from .typehints import ActionTypeHint
     action = _find_parent_action(parser, key, exclude=exclude)
     if ActionTypeHint.is_subclass_typehint(action):
diff --git a/jsonargparse/loaders_dumpers.py b/jsonargparse/loaders_dumpers.py
index cbf09ac..04bc3b1 100644
--- a/jsonargparse/loaders_dumpers.py
+++ b/jsonargparse/loaders_dumpers.py
@@ -2,12 +2,11 @@
 
 import inspect
 import re
-from contextlib import contextmanager
-from contextvars import ContextVar
 from typing import Any, Callable, Dict, Tuple, Type
 
 import yaml
 
+from ._common import load_value_mode, parent_parser
 from .optionals import import_jsonnet, omegaconf_support
 from .type_checking import ArgumentParser
 
@@ -17,19 +16,9 @@ __all__ = [
 ]
 
 
-load_value_mode: ContextVar = ContextVar('load_value_mode')
 regex_curly_comma = re.compile(' *[{},] *')
 
 
-@contextmanager
-def load_value_context(mode):
-    t = load_value_mode.set(mode)
-    try:
-        yield
-    finally:
-        load_value_mode.reset(t)
-
-
 class DefaultLoader(getattr(yaml, 'CSafeLoader', yaml.SafeLoader)):  # type: ignore
     pass
 
@@ -102,12 +91,19 @@ loader_exceptions: Dict[str, Tuple[Type[Exception], ...]] = {
 }
 
 
+def get_load_value_mode() -> str:
+    mode = load_value_mode.get()
+    if mode is None:
+        mode = parent_parser.get().parser_mode
+    return mode
+
+
 def get_loader_exceptions():
-    return loader_exceptions[load_value_mode.get()]
+    return loader_exceptions[get_load_value_mode()]
 
 
 def load_value(value: str, simple_types: bool = False, **kwargs):
-    loader = loaders[load_value_mode.get()]
+    loader = loaders[get_load_value_mode()]
     if kwargs:
         params = set(list(inspect.signature(loader).parameters)[1:])
         kwargs = {k: v for k, v in kwargs.items() if k in params}
diff --git a/jsonargparse/optionals.py b/jsonargparse/optionals.py
index 71bb33e..53fea96 100644
--- a/jsonargparse/optionals.py
+++ b/jsonargparse/optionals.py
@@ -248,9 +248,9 @@ class FilesCompleterMethod:
 
 def argcomplete_autocomplete(parser):
     if argcomplete_support:
-        argcomplete = import_argcomplete('parse_args')
-        from .loaders_dumpers import load_value_context
-        with load_value_context(parser.parser_mode):
+        argcomplete = import_argcomplete('argcomplete_autocomplete')
+        from ._common import parser_context
+        with parser_context(load_value_mode=parser.parser_mode):
             argcomplete.autocomplete(parser)
 
 
diff --git a/jsonargparse/typehints.py b/jsonargparse/typehints.py
index f322407..a8807b3 100644
--- a/jsonargparse/typehints.py
+++ b/jsonargparse/typehints.py
@@ -4,8 +4,6 @@ import inspect
 import os
 import re
 import sys
-import warnings
-from argparse import Action
 from collections import abc, defaultdict
 from contextlib import contextmanager, suppress
 from contextvars import ContextVar
@@ -33,16 +31,20 @@ from typing import (
     Union,
 )
 
+from ._common import parent_parser, parser_context
 from .actions import (
+    Action,
+    ActionConfigFile,
     _ActionHelpClassPath,
+    _ActionPrintConfig,
     _find_action,
     _find_parent_action,
     _is_action_value_list,
+    remove_actions,
 )
 from .loaders_dumpers import (
     get_loader_exceptions,
     load_value,
-    load_value_context,
     pyyaml_exceptions,
     yaml_load,
 )
@@ -66,7 +68,6 @@ from .util import (
     indent_text,
     is_subclass,
     iter_to_set_str,
-    lenient_check_context,
     object_path_serializer,
     parse_value_or_config,
     warning,
@@ -367,7 +368,7 @@ class ActionTypeHint(Action):
         for key in [k for k in cfg.keys() if k.endswith('+')]:
             action = _find_action(parser, key[:-1])
             if ActionTypeHint.supports_append(action):
-                with load_value_context(parser.parser_mode):
+                with parser_context(load_value_mode=parser.parser_mode):
                     val = action._check_type(cfg[key], append=True, cfg=cfg)
                 cfg[key[:-1]] = val
                 cfg.pop(key)
@@ -451,11 +452,6 @@ class ActionTypeHint(Action):
                     if ex:
                         raise ex
 
-                if not append and self._supports_append:
-                    prev_val = kwargs.get('prev_val')
-                    if isinstance(prev_val, list) and not_append_diff(prev_val, val) and get_typehint_origin(self._typehint) == Union:
-                        warnings.warn(f'Replacing list value "{prev_val}" with "{val}". To append to a list use "{self.dest}+".')
-
                 if path_meta is not None:
                     val['__path__'] = path_meta
                 if isinstance(val, (Namespace, dict)) and config_path is not None:
@@ -480,13 +476,14 @@ class ActionTypeHint(Action):
 
     @staticmethod
     def get_class_parser(val_class, sub_add_kwargs=None, skip_args=0):
-        from .core import ArgumentParser
         if isinstance(val_class, str):
             val_class = import_object(val_class)
         kwargs = dict(sub_add_kwargs) if sub_add_kwargs else {}
         if skip_args:
             kwargs.setdefault('skip', set()).add(skip_args)
-        parser = ArgumentParser(error_handler=None)
+        parser = parent_parser.get()
+        parser = type(parser)(error_handler=None, logger=parser.logger)
+        remove_actions(parser, (ActionConfigFile, _ActionPrintConfig))
         parser.add_class_arguments(val_class, **kwargs)
         return parser
 
@@ -929,7 +926,7 @@ def discard_init_args_on_class_path_change(parser_or_action, prev_val, value):
         for key, val in list(prev_val.init_args.__dict__.items()):
             action = _find_action(parser, key)
             if action:
-                with lenient_check_context(lenient=False), load_value_context(parser.parser_mode):
+                with parser_context(lenient_check=False, load_value_mode=parser.parser_mode):
                     try:
                         parser._check_value_key(action, val, key, Namespace())
                     except Exception:
@@ -937,7 +934,7 @@ def discard_init_args_on_class_path_change(parser_or_action, prev_val, value):
             if not action:
                 del_args[key] = prev_val.init_args.pop(key)
         if del_args:
-            warnings.warn(
+            parser_or_action.logger.debug(
                 f"Due to class_path change from {prev_val['class_path']!r} to {value['class_path']!r}, "
                 f"discarding init_args: {del_args}."
             )
@@ -1038,13 +1035,6 @@ def adapt_classes_any(val, serialize, instantiate_classes, sub_add_kwargs):
     return val
 
 
-def not_append_diff(val1, val2):
-    if isinstance(val1, list) and isinstance(val2, list):
-        val1 = [x.get('class_path') if is_subclass_spec(x) else x for x in val1]
-        val2 = [x.get('class_path') if is_subclass_spec(x) else x for x in val2]
-    return val1 != val2
-
-
 def sort_subtypes_for_append(subtypes):
     if subtypes and len(subtypes) > 1:
         subtypes = sorted(subtypes, key=lambda x: get_typehint_origin(x) not in sequence_origin_types)
diff --git a/jsonargparse/util.py b/jsonargparse/util.py
index 9a480c9..2dfdca1 100644
--- a/jsonargparse/util.py
+++ b/jsonargparse/util.py
@@ -29,6 +29,7 @@ from typing import (
     Union,
 )
 
+from ._common import parser_capture, parser_context
 from .loaders_dumpers import json_dump, load_value
 from .optionals import (
     fsspec_support,
@@ -97,18 +98,6 @@ class CaptureParserException(Exception):
         super().__init__('' if parser else 'No parse_args call to capture the parser.')
 
 
-parser_captured: ContextVar = ContextVar('parser_captured', default=False)
-
-
-@contextmanager
-def capture_parser_context():
-    t = parser_captured.set(True)
-    try:
-        yield
-    finally:
-        parser_captured.reset(t)
-
-
 def capture_parser(function: Callable, *args, **kwargs) -> ArgumentParser:
     """Returns the parser object used within the execution of a function.
 
@@ -124,7 +113,7 @@ def capture_parser(function: Callable, *args, **kwargs) -> ArgumentParser:
         CaptureParserException: If the function does not call parse_args.
     """
     try:
-        with capture_parser_context():
+        with parser_context(parser_capture=True):
             function(*args, **kwargs)
     except CaptureParserException as ex:
         return ex.parser  # type: ignore
@@ -132,7 +121,7 @@ def capture_parser(function: Callable, *args, **kwargs) -> ArgumentParser:
 
 
 def return_parser_if_captured(parser: ArgumentParser):
-    if parser_captured.get():
+    if parser_capture.get():
         raise CaptureParserException(parser)
 
 
@@ -292,18 +281,6 @@ def get_typehint_origin(typehint):
     return getattr(typehint, '__origin__', None)
 
 
-lenient_check: ContextVar = ContextVar('lenient_check', default=False)
-
-
-@contextmanager
-def lenient_check_context(caller=None, lenient=True):
-    t = lenient_check.set(False if caller == 'argcomplete' else lenient)
-    try:
-        yield
-    finally:
-        lenient_check.reset(t)
-
-
 current_path_dir: ContextVar[Optional[str]] = ContextVar('current_path_dir', default=None)
 
 


Test patch:
diff --git a/jsonargparse_tests/test_argcomplete.py b/jsonargparse_tests/test_argcomplete.py
index 0bf94db..b45a51e 100755
--- a/jsonargparse_tests/test_argcomplete.py
+++ b/jsonargparse_tests/test_argcomplete.py
@@ -10,7 +10,7 @@ from io import StringIO
 from typing import List, Optional
 
 from jsonargparse import ActionConfigFile, ActionJsonSchema, ActionYesNo, ArgumentParser
-from jsonargparse.loaders_dumpers import load_value_context
+from jsonargparse._common import parser_context
 from jsonargparse.optionals import (
     argcomplete_support,
     import_argcomplete,
@@ -44,7 +44,7 @@ class ArgcompleteTests(TempDirTestCase):
         os.environ['COMP_TYPE'] = str(ord('?'))   # ='63'  str(ord('\t'))='9'
         self.parser = ArgumentParser(error_handler=lambda x: x.exit(2))
         stack = ExitStack()
-        stack.enter_context(load_value_context('yaml'))
+        stack.enter_context(parser_context(load_value_mode='yaml'))
         self.addCleanup(stack.close)
 
 
diff --git a/jsonargparse_tests/test_loaders_dumpers.py b/jsonargparse_tests/test_loaders_dumpers.py
index 2fc2331..7dd9cd5 100755
--- a/jsonargparse_tests/test_loaders_dumpers.py
+++ b/jsonargparse_tests/test_loaders_dumpers.py
@@ -9,12 +9,8 @@ from typing import List
 import yaml
 
 from jsonargparse import ActionConfigFile, ArgumentParser, set_dumper, set_loader
-from jsonargparse.loaders_dumpers import (
-    load_value,
-    load_value_context,
-    loaders,
-    yaml_dump,
-)
+from jsonargparse._common import parser_context
+from jsonargparse.loaders_dumpers import load_value, loaders, yaml_dump
 
 
 class LoadersTests(unittest.TestCase):
@@ -118,7 +114,7 @@ class LoadersTests(unittest.TestCase):
 
 
     def test_load_value_dash(self):
-        with load_value_context('yaml'):
+        with parser_context(load_value_mode='yaml'):
             self.assertEqual('-', load_value('-'))
             self.assertEqual(' -  ', load_value(' -  '))
 
diff --git a/jsonargparse_tests/test_signatures.py b/jsonargparse_tests/test_signatures.py
index 179f9b1..e9d9100 100755
--- a/jsonargparse_tests/test_signatures.py
+++ b/jsonargparse_tests/test_signatures.py
@@ -484,7 +484,7 @@ class SignaturesTests(unittest.TestCase):
 
 
     def test_add_subclass_discard_init_args(self):
-        parser = ArgumentParser(error_handler=None)
+        parser = ArgumentParser(error_handler=None, logger={'level': 'DEBUG'})
         parser.add_subclass_arguments(Calendar, 'cal')
 
         class CalA(Calendar):
@@ -495,7 +495,7 @@ class SignaturesTests(unittest.TestCase):
             def __init__(self, pb: str = 'b', pc: int = 4, **kwds):
                 super().__init__(**kwds)
 
-        with mock_module(CalA, CalB) as module, warnings.catch_warnings(record=True) as w:
+        with mock_module(CalA, CalB) as module, self.assertLogs(logger=parser.logger, level='DEBUG') as log:
             cfg = parser.parse_args([
                 f'--cal.class_path={module}.CalA',
                 '--cal.init_args.pa=A',
@@ -506,7 +506,7 @@ class SignaturesTests(unittest.TestCase):
             ])
             self.assertEqual(cfg.cal.class_path, f'{module}.CalB')
             self.assertEqual(cfg.cal.init_args, Namespace(pb='B', pc=4, firstweekday=3))
-            self.assertIn("discarding init_args: {'pa': 'A', 'pc': 'X'}", str(w[0].message))
+            self.assertTrue(any("discarding init_args: {'pa': 'A', 'pc': 'X'}" in o for o in log.output))
 
 
     def test_add_subclass_nested_discard_init_args(self):
@@ -525,8 +525,8 @@ class SignaturesTests(unittest.TestCase):
             def __init__(self, c: ChildBase):
                 pass
 
-        with mock_module(Parent, ChildBase, A, B) as module, warnings.catch_warnings(record=True) as w:
-            parser = ArgumentParser(error_handler=None)
+        parser = ArgumentParser(error_handler=None, logger={'level': 'DEBUG'})
+        with mock_module(Parent, ChildBase, A, B) as module, self.assertLogs(logger=parser.logger, level='DEBUG') as log:
             parser.add_subclass_arguments(Parent, 'p')
             cfg = parser.parse_args([
                 '--p=Parent',
@@ -538,7 +538,7 @@ class SignaturesTests(unittest.TestCase):
             self.assertEqual(cfg.p.class_path, f'{module}.Parent')
             self.assertEqual(cfg.p.init_args.c.class_path, f'{module}.B')
             self.assertEqual(cfg.p.init_args.c.init_args, Namespace(b=2))
-            self.assertIn("discarding init_args: {'a': 1}", str(w[0].message))
+        self.assertTrue(any("discarding init_args: {'a': 1}" in o for o in log.output))
 
 
     def test_class_path_override_with_mixed_type(self):
@@ -550,11 +550,11 @@ class SignaturesTests(unittest.TestCase):
             def __init__(self, cal: Union[Calendar, bool] = lazy_instance(MyCalendar, param=1)):
                 self.cal = cal
 
-        with mock_module(MyCalendar), warnings.catch_warnings(record=True) as w:
-            parser = ArgumentParser(error_handler=None)
+        parser = ArgumentParser(error_handler=None, logger={'level': 'DEBUG'})
+        with mock_module(MyCalendar), self.assertLogs(logger=parser.logger, level='DEBUG') as log:
             parser.add_class_arguments(Main, 'main')
             parser.parse_args(['--main.cal=Calendar'])
-            self.assertIn("discarding init_args: {'param': 1}", str(w[0].message))
+        self.assertTrue(any("discarding init_args: {'param': 1}" in o for o in log.output))
 
 
     def test_add_subclass_init_args_without_class_path(self):
@@ -1381,8 +1381,8 @@ class SignaturesConfigTests(TempDirTestCase):
             }
 
             for subtest in ['class', 'subclass']:
-                with self.subTest(subtest), warnings.catch_warnings(record=True) as w:
-                    parser = ArgumentParser(error_handler=None)
+                with self.subTest(subtest):
+                    parser = ArgumentParser(error_handler=None, logger={'level': 'DEBUG'})
                     parser.add_argument('--config', action=ActionConfigFile)
 
                     if subtest == 'class':
@@ -1401,11 +1401,12 @@ class SignaturesConfigTests(TempDirTestCase):
                     config_path = Path('config.yaml')
                     config_path.write_text(yaml.safe_dump(config))
 
-                    cfg = parser.parse_args([f'--config={config_path}'])
+                    with self.assertLogs(logger=parser.logger, level='DEBUG') as log:
+                        cfg = parser.parse_args([f'--config={config_path}'])
                     init = parser.instantiate_classes(cfg)
                     self.assertIsInstance(init.main, Main)
                     self.assertIsInstance(init.main.sub, Sub2)
-                    self.assertIn("discarding init_args: {'s1': 'x'}", str(w[0].message))
+                    self.assertTrue(any("discarding init_args: {'s1': 'x'}" in o for o in log.output))
 
 
 @dataclasses.dataclass(frozen=True)
diff --git a/jsonargparse_tests/test_typehints.py b/jsonargparse_tests/test_typehints.py
index b8edcd5..c4d07f9 100755
--- a/jsonargparse_tests/test_typehints.py
+++ b/jsonargparse_tests/test_typehints.py
@@ -9,7 +9,6 @@ import sys
 import time
 import unittest
 import uuid
-import warnings
 from calendar import Calendar, HTMLCalendar, TextCalendar
 from contextlib import redirect_stderr, redirect_stdout
 from copy import deepcopy
@@ -341,10 +340,8 @@ class TypeHintsTests(unittest.TestCase):
         self.assertEqual([1, 2, 3], parser.parse_args(['--val=1', '--val+=2', '--val+=3']).val)
         self.assertEqual([1, 2, 3], parser.parse_args(['--val=[1,2]', '--val+=3']).val)
         self.assertEqual([1], parser.parse_args(['--val=0.1', '--val+=1']).val)
+        self.assertEqual(3, parser.parse_args(['--val=[1,2]', '--val=3']).val)
         self.assertRaises(ParserError, lambda: parser.parse_args(['--val=a', '--val+=1']))
-        with warnings.catch_warnings(record=True) as w:
-            self.assertEqual(3, parser.parse_args(['--val=[1,2]', '--val=3']).val)
-            self.assertIn('Replacing list value "[1, 2]" with "3"', str(w[0].message))
 
 
     def test_list_append_default_empty(self):
@@ -1574,7 +1571,8 @@ class TypeHintsTmpdirTests(TempDirTestCase):
         self.assertIn('class_path: calendar.Calendar\n', dump)
         self.assertIn('firstweekday: 3\n', dump)
 
-        cfg = parser.parse_args([])
+        with self.assertLogs(logger=parser.logger, level='DEBUG'):
+            cfg = parser.parse_args([])
         self.assertEqual(cfg.data.cal.as_dict(), config)
         cfg = parser.parse_args(['--data.cal.class_path=calendar.Calendar'], defaults=False)
         self.assertEqual(cfg.data.cal, Namespace(class_path='calendar.Calendar'))
@@ -1594,14 +1592,14 @@ class TypeHintsTmpdirTests(TempDirTestCase):
                 pass
 
         with mock_module(Base, Subclass1, Subclass2) as module:
-            parser = ArgumentParser()
+            parser = ArgumentParser(logger={'level': 'DEBUG'})
             parser.add_argument('--cfg', action=ActionConfigFile)
             parser.add_argument('--s', type=Base, default=lazy_instance(Subclass1, s1='v1'))
 
             config = {'s': {'class_path': 'Subclass2', 'init_args': {'s2': 'v2'}}}
-            with warnings.catch_warnings(record=True) as w:
+            with self.assertLogs(logger=parser.logger, level='DEBUG') as log:
                 cfg = parser.parse_args([f'--cfg={config}'])
-                self.assertIn("discarding init_args: {'s1': 'v1'}", str(w[0].message))
+            self.assertTrue(any("discarding init_args: {'s1': 'v1'}" in o for o in log.output))
             self.assertEqual(cfg.s.class_path, f'{module}.Subclass2')
             self.assertEqual(cfg.s.init_args, Namespace(s2='v2'))
 
@@ -1621,15 +1619,20 @@ class TypeHintsTmpdirTests(TempDirTestCase):
             with open(config_path, 'w') as f:
                 json.dump({'cal': config}, f)
 
-            parser = ArgumentParser(error_handler=None, default_config_files=[config_path])
+            parser = ArgumentParser(
+                error_handler=None,
+                logger={'level': 'DEBUG'},
+                default_config_files=[config_path],
+            )
             parser.add_argument('--cal', type=Optional[Calendar])
 
-            cfg = parser.instantiate_classes(parser.get_defaults())
+            with self.assertLogs(logger=parser.logger, level='DEBUG'):
+                cfg = parser.instantiate_classes(parser.get_defaults())
             self.assertIsInstance(cfg['cal'], MyCalendar)
 
-            with warnings.catch_warnings(record=True) as w:
+            with self.assertLogs(logger=parser.logger, level='DEBUG') as log:
                 cfg = parser.parse_args(['--cal={"class_path": "calendar.Calendar", "init_args": {"firstweekday": 3}}'])
-                self.assertIn("discarding init_args: {'param': '1'}", str(w[0].message))
+            self.assertTrue(any("discarding init_args: {'param': '1'}" in o for o in log.output))
             self.assertEqual(cfg.cal.init_args, Namespace(firstweekday=3))
             self.assertEqual(type(parser.instantiate_classes(cfg)['cal']), Calendar)
 
@@ -1687,7 +1690,7 @@ class TypeHintsTmpdirTests(TempDirTestCase):
             def __init__(self, a: int = 4, c: int = 5):
                 pass
 
-        parser = ArgumentParser(error_handler=None)
+        parser = ArgumentParser(error_handler=None, logger={'level': 'DEBUG'})
         parser_subcommands = parser.add_subcommands()
         subparser = ArgumentParser()
         subparser.add_argument('--arch', type=Arch)
@@ -1699,9 +1702,9 @@ class TypeHintsTmpdirTests(TempDirTestCase):
             subparser.set_defaults(arch=default)
             parser_subcommands.add_subcommand('fit', subparser)
 
-            with warnings.catch_warnings(record=True) as w:
+            with self.assertLogs(logger=parser.logger, level='DEBUG') as log:
                 cfg = parser.parse_args(['fit', f'--arch={json.dumps(value)}'])
-                self.assertIn("discarding init_args: {'b': 3}", str(w[0].message))
+            self.assertTrue(any("discarding init_args: {'b': 3}" in o for o in log.output))
             self.assertEqual(cfg.fit.arch.as_dict(), value)