Search code examples
javaguicejclouds

Creating a custom logger using JClouds with Guice dependency injection


I am just stuck and need some pointers. I have followed instruction on creating my own logging implementation, logging factory, and guice module; but just aren't sure what I am missing...

My custom logger implemented w/ Factory. This extends from jClouds BaseLogger; not shown is the actually logging functionality. Also created is the Factory, which I based off of their implementation of Log4JLogger Factory. In this I needed a static getLogger and used the slf4j's LoggerFactory and casted it to jClouds interface type Logger. I added the '@Inject' annotation but I feel like this is what should be on the jClouds side, not mine. (Not sure). What am I missing though??

import org.jclouds.logging.BaseLogger;
import org.jclouds.logging.Logger;
import com.google.inject.Inject;

public class NullLoggerExt extends BaseLogger {
  private final org.jclouds.logging.Logger logger;

  public static class NullLoggerExtFactory implements LoggerFactory {
    public Logger getLogger(String category) {
      return new NullLoggerExt( (Logger) org.slf4j.LoggerFactory.getLogger(category) );
    }
  }

  @Inject   <--- is this what I need?
  public NullLoggerExt(org.jclouds.logging.Logger logger) {
    this.logger = logger;
  }
  .
  . (implemented abstract methods)
  .
}

My custom logger Module

import NullLoggerExt;

import org.jclouds.logging.Logger.LoggerFactory;
import org.jclouds.logging.config.LoggingModule;

public class NullLoggerModuleExt extends LoggingModule {

  @Override
  public LoggerFactory createLoggerFactory() {
    return new NullLoggerExt.NullLoggerExtFactory();
  }
}

My Guice module

import NullLoggerExt;
import com.google.inject.AbstractModule;
import org.jclouds.logging.Logger;

public class BindLoggerModule extends AbstractModule {

  @Override
  protected void configure() {
    bind(Logger.class).to(NullLoggerExt.class);
  }
}

This section will describe where I am using it.

public class CloudHandler
{
  //Actual logger
  private static final Iterable<Module> detailedTrace = 
            ImmutableSet.<Module> of( new SLF4JLoggingModule() );

  Injector injector = Guice.createInjector(new BindLoggerModule());
  NullLoggerModuleExt customLog = injector.getInstance(NullLoggerModuleExt.class);

  //My performance logger
  private final Iterable<Module> baseLog =  
            ImmutableSet.<Module> of( customLog );

  public int connect( ... ) {
    contextBuilder...

    //Problem area
    if (debug mode == true)
      contextBuilder.modules( detailedTrace );
    else
      contextBuilder.modules( baseLog );

    //Throws CreationException
    ctx = contextBuilder.buildView( BlobStoreContext.class );
    return Success or Failure;
  }
}

Solution

  • This approach is wrong, because you are trying to inject a Logger implementation that is bound to that exact same class, so you will get a recursive loop there.

    If you are just trying to modify how the SLF4j logging driver works, you could just extend its classes. However, if you are trying to "decorate" any jclouds logger with the null logger, and then delegate to the logging driver when needed, you could go for a more generic approach by using the following logging classes and module:

    static class DelegatingNullLogger extends BaseLogger {
        private final Logger logger;
    
        public static class DelegatingNullLoggerFactory implements LoggerFactory {
            private final LoggerFactory delegate;
    
            public DelegatingNullLoggerFactory(LoggerFactory delegate) {
                this.delegate = checkNotNull(delegate, "delegate");
            }
    
            @Override
            public Logger getLogger(String category) {
                return new DelegatingNullLogger(delegate.getLogger(category));
            }
        }
    
        public DelegatingNullLogger(Logger logger) {
            this.logger = logger;
        }
    
        // Implement methods
    }
    
    static class NullDelegatingLoggingModule extends LoggingModule {
        private final LoggerFactory loggerFactory;
    
        public NullDelegatingLoggingModule(LoggerFactory loggerFactory) {
            this.loggerFactory = checkNotNull(loggerFactory, "loggerFactory");
        }
    
        @Override
        public LoggerFactory createLoggerFactory() {
            return new DelegatingNullLogger.DelegatingNullLoggerFactory(loggerFactory);
        }
    }
    

    This code will first create a logger that will delegate to an underlying logger that can be Log4j, SLF4j or whatever when it comes to log messages, and the provided module, configures the delegate logger that will be passed.

    You can use this as follows, for example to have the null logger delegate to the SLF4j logging driver:

    LoggerFactory loggerFactory = new SLF4JLogger.SLF4JLoggerFactory();
    LoggingModule loggingModule = new NullDelegatingLoggingModule(loggerFactory);
    
    BlobStoreContext ctx = ContextBuilder.newBuilder("provider")
            .modules(ImmutableSet.of(loggingModule))
            (...)
            .buildView(BlobStoreContext.class);