4
\$\begingroup\$

I needed to add a logger into my flow based on FluentIterable from the Guava library. I have a lot of functions and in some cases I would like to add logging when in other cases (for the same function) I would not.

Of course the natural solution for this problem is to use the decorator pattern, so I created something like this:

/**
 * Decorator function that add logging into function passed as parameter.
 *
 * @param <I> input type
 * @param <O> output type.
 */
public class DecoratorLoggerInfoFunc<I, O> implements Function<I, O> {

    /**
     * Original function
     */
    private final Function<I, O> function;

    /**
     * Current logger.
     */
    private final Logger logger;

    /**
     * "Builder" that takes function and return function decorated by logger.
     *
     * @param function function to decorate.
     * @param <I>      input type
     * @param <O>      output type
     * @return function with logger.
     */
    public static <I, O> Function<I, O> of(Function<I, O> function) {
        checkNotNull(function, "Function to decorate cannot be null");
        return new DecoratorLoggerInfoFunc<I, O>(function);
    }

    /**
     * Initialize logger!
     *
     * @param function original function.
     */
    private DecoratorLoggerInfoFunc(Function<I, O> function) {
        this.function = function;
        this.logger = LoggerFactory.getLogger(function.getClass());
    }

    /**
     * Flow:
     * <ul>
     * <li>Call original function</li>
     * <li>Log input and output</li>
     * <li>Return output of original function</li>
     * </ul>
     *
     * @param input
     * @return
     */
    @Override
    public O apply(I input) {
        O output = function.apply(input);
        logger.info("Input is {} Output is {}", input, output);
        return output;
    }
}

Is this code a good way to solve my problem? Is there a better way to do this?

\$\endgroup\$
5
\$\begingroup\$

The concept seems fine to me. The problem is that you will still need to decide whether to decorate, or not decorate the function, and that is done outside the function. That makes it a once-per-create decision, which is OK, but you may still end up with a lot of conditions in your code:

Function f = logFunction() ? new DecoratorLoggerInfoFunc(f) : f;

The actual apply method is not very robust though. I would include logging of any potential exceptions that are not declared to be thrown on the function itself...:

public O apply(I input) {
    try {
        O output = function.apply(input);
        logger.info("Input is {} Output is {}", input, output);
        return output;
    } catch (RuntimeException e) {
        logger.warn("Exception running with input {}", input, e);
        throw e;
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.