package com.netflix.governator.test; import java.io.PrintStream; import com.google.common.base.Strings; import com.google.inject.spi.ProvisionListener; /** * Use TracingProvisionListener to debug issues with Guice Injector creation by tracing * the object initialization path. Various hooks are provided for the different stages * of object instantiation: before, after and on error. * * To enable add the following binding in any guice module * <code> * bindListener(Matchers.any(), TracingProvisionListener.createDefault()); * </code> * */ public class TracingProvisionListener implements ProvisionListener { private int indent = 0; private final String prefix; private final int indentAmount; private final BindingFormatter beforeFormatter; private final BindingFormatter afterFormatter; private final PrintStream stream; private ErrorFormatter errorFormatter; private static final BindingFormatter EMPTY_BINDING_FORMATTER = new BindingFormatter() { @Override public <T> String format(ProvisionInvocation<T> provision) { return ""; } }; private static final BindingFormatter SIMPLE_BINDING_FORMATTER = new BindingFormatter() { @Override public <T> String format(ProvisionInvocation<T> provision) { return provision.getBinding().getKey().toString(); } }; private static final ErrorFormatter SIMPLE_ERROR_FORMATTER = new ErrorFormatter() { @Override public <T> String format(ProvisionInvocation<T> provision, Throwable t) { return String.format("Error creating '%s'. %s", provision.getBinding().getKey().toString(), t.getMessage()); } }; /** * Builder for customizing the tracer output. */ public static class Builder { private String prefix = ""; private int indentAmount = 2; private BindingFormatter beforeFormatter = SIMPLE_BINDING_FORMATTER; private BindingFormatter afterFormatter = EMPTY_BINDING_FORMATTER; private ErrorFormatter errorFormatter = SIMPLE_ERROR_FORMATTER; private PrintStream stream = System.out; /** * Provide a custom formatter for messages written before a type is provisioned * Returning null or empty stream will result in no message being written * * @param formatter */ public Builder formatBeforeWith(BindingFormatter formatter) { this.beforeFormatter = formatter; return this; } /** * Provide a custom formatter for messages written after a type is provisioned. * Returning null or empty stream will result in no message being written * * @param formatter */ public Builder formatAfterWith(BindingFormatter formatter) { this.afterFormatter = formatter; return this; } /** * Provide a custom formatter for messages written when type provision throws * an exception * * @param formatter */ public Builder formatErrorsWith(ErrorFormatter formatter) { this.errorFormatter = formatter; return this; } /** * Indentation increment for each nested provisioning * @param amount - Number of spaces to indent */ public Builder indentBy(int amount) { this.indentAmount = amount; return this; } /** * Customized the PrintStream to which messages are written. By default * messages are written to System.err * * @param stream */ public Builder writeTo(PrintStream stream) { this.stream = stream; return this; } /** * String to prefix each row (prior to indentation). Default is "" * * @param prefix */ public Builder prefixLinesWith(String prefix) { this.prefix = prefix; return this; } public TracingProvisionListener build() { return new TracingProvisionListener(this); } } public static interface BindingFormatter { <T> String format(ProvisionInvocation<T> provision); } public static interface ErrorFormatter { <T> String format(ProvisionInvocation<T> provision, Throwable error); } public static TracingProvisionListener createDefault() { return new TracingProvisionListener(newBuilder()); } public static Builder newBuilder() { return new Builder(); } private TracingProvisionListener(Builder builder) { this.indentAmount = builder.indentAmount; this.beforeFormatter = builder.beforeFormatter; this.afterFormatter = builder.afterFormatter; this.errorFormatter = builder.errorFormatter; this.prefix = builder.prefix; this.stream = builder.stream; } @Override public <T> void onProvision(ProvisionInvocation<T> provision) { writeString(beforeFormatter.format(provision)); indent += indentAmount; try { provision.provision(); writeString(afterFormatter.format(provision)); } catch (Throwable t) { writeString(errorFormatter.format(provision, t)); throw t; } finally { indent -= indentAmount; } } private void writeString(String str) { if (str != null && !str.isEmpty()) { stream.println(prefix + Strings.repeat(" ", indent) + str); } } }