[slf4j-dev] [JIRA] Updates for SLF4J-592: Programmatically specify fallback provider for unit tests.

slf4j developers list slf4j-dev at qos.ch
Wed Jun 21 17:22:00 CEST 2023


SLF4J / SLF4J-592 [Open]
Programmatically specify fallback provider for unit tests.

==============================

Here's what changed in this issue in the last few minutes.

This issue has been created
This issue is now assigned to you.


View or comment on issue using this link
https://jira.qos.ch/browse/SLF4J-592

==============================
 Issue created
------------------------------

Garret Wilson created this issue on 21/Jun/23 17:10

Summary:              Programmatically specify fallback provider for unit tests.
Issue Type:           New Feature
Affects Versions:     2.0.7
Assignee:             SLF4J developers list
Components:           Core API
Created:              21/Jun/23 17:10
Priority:             Minor
Reporter:             Garret Wilson
Description:
  I originally described this problem in [Maven exclude/remove test dependency defined in parent POM|https://stackoverflow.com/q/73747106].
  
  In {{LoggerFactory}} SLF4J automatically falls back to a do-nothing provider if no logging implementations are on the classpath.
  
  {code:java}
  static final NOP_FallbackServiceProvider NOP_FALLBACK_SERVICE_PROVIDER = new NOP_FallbackServiceProvider();
  …
              List<SLF4JServiceProvider> providersList = findServiceProviders();
              reportMultipleBindingAmbiguity(providersList);
              if (providersList != null && !providersList.isEmpty()) {
                  PROVIDER = providersList.get(0);
                  // SLF4JServiceProvider.initialize() is intended to be called here and nowhere else.
                  PROVIDER.initialize();
                  INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
                  reportActualBinding(providersList);
              } else {
                  INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
                  Util.report("No SLF4J providers were found.");
                  Util.report("Defaulting to no-operation (NOP) logger implementation");
                  Util.report("See " + NO_PROVIDERS_URL + " for further details.");
  …
          if (INITIALIZATION_STATE == UNINITIALIZED) {
              synchronized (LoggerFactory.class) {
                  if (INITIALIZATION_STATE == UNINITIALIZED) {
                      INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                      performInitialization();
                  }
              }
          }
          switch (INITIALIZATION_STATE) {
          case SUCCESSFUL_INITIALIZATION:
              return PROVIDER;
          case NOP_FALLBACK_INITIALIZATION:
              return NOP_FALLBACK_SERVICE_PROVIDER;
  {code}
  
  In a lot of libraries, I want to run unit tests and see any logging output. Thus in my libraries I would need to have a logging implementation. But a library should not be specifying a logging implementation—a library may use the SLF4J API, but leave it to some later application to specify the actual SLF4J provider.
  
  Currently in my [Maven "root" parent POM|https://github.com/globalmentor/globalmentor-root/blob/main/pom.xml] I try to sidestep this by declaring the Logback simple implementation, but only in {{test}} scope:
  
  {code:xml}
  <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <scope>test</scope>
  </dependency>
  {code}
  
  This works fine except in my own applications, which use the above-mentioned root POM as their parent. One way or another they necessarily specify an SLF4J provider. As there is no way to remove the {{slf4j-simple}} provider from test scope, when I run tests for the application I get the {{SLF4J: Class path contains multiple SLF4J providers}} warning.
  
  Basically the goal here is to somehow declare a provider that only takes effect if there is no other provider present. One approach would be to enhance {{SLF4JServiceProvider}} to have something like an {{isFallback()}} method that would indicate it should only be used if no other providers are present. But this is just a degenerate case of a system of priorities, so really this would not be much different than adding a {{getPriority()}} integer or some enum of priorities. I doubt you're keen for this approach at all.
  
  Perhaps a cleaner, more surgical approach would be to add a static {{LoggerFactory.setFallbackProvider()}} method. Instead of {{static final NOP_FallbackServiceProvider NOP_FALLBACK_SERVICE_PROVIDER = new NOP_FallbackServiceProvider()}} you would have a static SLF4JServiceProvider fallbackProvider = new NOP_FallbackServiceProvider()}} internal variables. (Even the constant should have been declared to the interface {{SLF4JServiceProvider}} in the first place, in my opinion—there's no reason {{LoggerFactory}} needs to know the specific type of its fallback provider.)
  
  The new method {{LoggerFactory.setFallbackProvider()}} could check that {{INITIALIZATION_STATE == UNINITIALIZED}}, throwing an {{IllegalStateException}} if not, ensuring that this method is called before a logger is requested. Then you simply change this (and rename the enum value):
  
  {code:java}
          case NOP_FALLBACK_INITIALIZATION:
              return NOP_FALLBACK_SERVICE_PROVIDER;
  {code}
  
  to this:
  
  {code:java}
          case NOP_FALLBACK_INITIALIZATION:
              return fallbackProvider;
  {code}
  
  This should be 100% backwards compatible. It would allow any application to specify what logging implementation to use if none were provided. In my case, I could simply create a superclass for all my unit tests, and do something like:
  
  {code:java}
  LoggerFactory.setFallbackProvider(new MySystemOutLoggerProviderForTests())
  {code}
  
  Maybe this discussion will give you an idea for an even better approach.
  
  In fact, now that I think of it, it would probably be better in a testing environment, not to provide a fallback provider, but to _force_ a provider, overriding any that is already on the main compile classpath:
  
  {code:java}
  LoggerFactory.forceProvider(new MySystemOutLoggerProviderForTests())
  {code}
  
  You would would understandably be reluctant to allow that drastic of a change without lots of consideration.
  
  In any case, there remains a need to specify a logging provider in the context of unit tests without interfering with the main logging configuration and vice-versa.
  
  See also [Is there any simple pattern of slf4j usage in unit tests?|https://stackoverflow.com/q/3820290] (somebody else's question—I didn't write it) from 12 years ago.


==============================
 This message was sent by Atlassian Jira (v9.6.0#960000-sha1:a3ee8af)



More information about the slf4j-dev mailing list