Uploaded image for project: 'SLF4J'
  1. SLF4J
  2. SLF4J-592

Mechanism for indicating provider to use for unit tests.

    XMLWordPrintable

Details

    • Icon: New Feature New Feature
    • Resolution: Duplicate
    • Icon: Major Major
    • 2.0.9
    • 2.0.7
    • Core API
    • None

    Description

      I originally described this problem in Maven exclude/remove test dependency defined in parent POM.

      In LoggerFactory SLF4J automatically falls back to a do-nothing provider if no logging implementations are on the classpath.

      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;
      

      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 I try to sidestep this by declaring the SLF4J simple implementation, but only in test scope:

      <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>
        <scope>test</scope>
      </dependency>
      

      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):

              case NOP_FALLBACK_INITIALIZATION:
                  return NOP_FALLBACK_SERVICE_PROVIDER;
      

      to this:

              case NOP_FALLBACK_INITIALIZATION:
                  return fallbackProvider;
      

      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:

      LoggerFactory.setFallbackProvider(new MySystemOutLoggerProviderForTests())
      

      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:

      LoggerFactory.forceProvider(new MySystemOutLoggerProviderForTests())
      

      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? (somebody else's question—I didn't write it) from 12 years ago.

      Attachments

        Activity

          People

            ceki Ceki Gülcü
            garretwilson Garret Wilson
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

            Dates

              Created:
              Updated:
              Resolved: