Repository: quarkusio/quarkus
Issue: ArC - stack log messages about usage of private members

Currently, we log an `INFO` message for every private injection field/`@PreDestroy` callback/disposer/producer/etc. we find in the app code. This could be very annoying when porting an existing app. Therefore, we should only log one `INFO` message for the first occurence + `and N more found. Please enable debug logging to see more`.

Changed files:
independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanGenerator.java
independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/InterceptorGenerator.java
independent-projects/arc/processor/src/test/java/org/jboss/protean/arc/processor/SubclassGeneratorTest.java
independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanProcessor.java
extensions/arc/deployment/src/main/java/org/jboss/shamrock/arc/deployment/ArcAnnotationProcessor.java
independent-projects/arc/processor/src/test/java/org/jboss/protean/arc/processor/ClientProxyGeneratorTest.java
independent-projects/arc/processor/src/test/java/org/jboss/protean/arc/processor/BeanGeneratorTest.java
independent-projects/arc/processor/src/test/java/org/jboss/protean/arc/processor/InterceptorGeneratorTest.java
independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/ObserverGenerator.java

diff --git a/extensions/arc/deployment/src/main/java/org/jboss/shamrock/arc/deployment/ArcAnnotationProcessor.java b/extensions/arc/deployment/src/main/java/org/jboss/shamrock/arc/deployment/ArcAnnotationProcessor.java
index 8d31098da48..c246d651890 100644
--- a/extensions/arc/deployment/src/main/java/org/jboss/shamrock/arc/deployment/ArcAnnotationProcessor.java
+++ b/extensions/arc/deployment/src/main/java/org/jboss/shamrock/arc/deployment/ArcAnnotationProcessor.java
@@ -18,9 +18,7 @@
 
 import static org.jboss.shamrock.deployment.annotations.ExecutionTime.STATIC_INIT;
 
-import java.io.ByteArrayInputStream;
 import java.io.IOException;
-import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
@@ -33,11 +31,9 @@
 import javax.inject.Inject;
 
 import org.jboss.jandex.AnnotationTarget;
-import org.jboss.jandex.ClassInfo;
 import org.jboss.jandex.CompositeIndex;
 import org.jboss.jandex.DotName;
 import org.jboss.jandex.FieldInfo;
-import org.jboss.jandex.IndexView;
 import org.jboss.jandex.Indexer;
 import org.jboss.jandex.MethodInfo;
 import org.jboss.logging.Logger;
@@ -47,7 +43,6 @@
 import org.jboss.protean.arc.processor.BeanDeployment;
 import org.jboss.protean.arc.processor.BeanProcessor;
 import org.jboss.protean.arc.processor.BeanProcessor.Builder;
-import org.jboss.protean.arc.processor.DotNames;
 import org.jboss.protean.arc.processor.ReflectionRegistration;
 import org.jboss.protean.arc.processor.ResourceOutput;
 import org.jboss.shamrock.deployment.annotations.BuildProducer;
diff --git a/independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanGenerator.java b/independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanGenerator.java
index 70589b640d5..e9162056a00 100644
--- a/independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanGenerator.java
+++ b/independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanGenerator.java
@@ -48,8 +48,6 @@
 import org.jboss.jandex.FieldInfo;
 import org.jboss.jandex.MethodInfo;
 import org.jboss.jandex.Type;
-import org.jboss.logging.Logger;
-import org.jboss.logging.Logger.Level;
 import org.jboss.protean.arc.CreationalContextImpl;
 import org.jboss.protean.arc.CurrentInjectionPointProvider;
 import org.jboss.protean.arc.InitializedInterceptor;
@@ -59,6 +57,7 @@
 import org.jboss.protean.arc.LazyValue;
 import org.jboss.protean.arc.Subclass;
 import org.jboss.protean.arc.processor.BeanInfo.InterceptionInfo;
+import org.jboss.protean.arc.processor.BeanProcessor.PrivateMembersCollector;
 import org.jboss.protean.arc.processor.ResourceOutput.Resource;
 import org.jboss.protean.arc.processor.ResourceOutput.Resource.SpecialType;
 import org.jboss.protean.gizmo.AssignableResultHandle;
@@ -89,22 +88,23 @@ public class BeanGenerator extends AbstractGenerator {
 
     static final String SYNTHETIC_SUFFIX = "_Synthetic";
 
-    private static final Logger LOGGER = Logger.getLogger(BeanGenerator.class);
-
     protected static final String FIELD_NAME_DECLARING_PROVIDER = "declaringProvider";
     protected static final String FIELD_NAME_BEAN_TYPES = "types";
     protected static final String FIELD_NAME_QUALIFIERS = "qualifiers";
     protected static final String FIELD_NAME_STEREOTYPES = "stereotypes";
     protected static final String FIELD_NAME_PROXY = "proxy";
     protected static final String FIELD_NAME_PARAMS = "params";
-
+    
     protected final AnnotationLiteralProcessor annotationLiterals;
 
     protected final Predicate<DotName> applicationClassPredicate;
 
-    public BeanGenerator(AnnotationLiteralProcessor annotationLiterals, Predicate<DotName> applicationClassPredicate) {
+    protected final PrivateMembersCollector privateMembers;
+    
+    public BeanGenerator(AnnotationLiteralProcessor annotationLiterals, Predicate<DotName> applicationClassPredicate, PrivateMembersCollector privateMembers) {
         this.annotationLiterals = annotationLiterals;
         this.applicationClassPredicate = applicationClassPredicate;
+        this.privateMembers = privateMembers;
     }
 
     /**
@@ -644,7 +644,7 @@ protected void implementDestroy(BeanInfo bean, ClassCreator beanCreator, String
                         bean.getDeployment().getIndex());
                 for (MethodInfo callback : preDestroyCallbacks) {
                     if (Modifier.isPrivate(callback.flags())) {
-                        LOGGER.logf(isApplicationClass ? Level.INFO : Level.DEBUG, "PreDestroy callback %s#%s is private - users are encouraged to avoid using private callbacks", callback.declaringClass().name(), callback.name());
+                        privateMembers.add(isApplicationClass, String.format("@PreDestroy callback %s#%s()", callback.declaringClass().name(), callback.name()));
                         reflectionRegistration.registerMethod(callback);
                         destroy.invokeStaticMethod(MethodDescriptors.REFLECTIONS_INVOKE_METHOD, destroy.loadClass(callback.declaringClass().name().toString()),
                                 destroy.load(callback.name()), destroy.newArray(Class.class, destroy.load(0)), destroy.getMethodParam(0),
@@ -693,9 +693,8 @@ protected void implementDestroy(BeanInfo bean, ClassCreator beanCreator, String
             }
 
             if (Modifier.isPrivate(disposerMethod.flags())) {
-                Level level = isApplicationClass ? Level.INFO : Level.DEBUG;
-                LOGGER.logf(level, "Disposer %s#%s is private - users are encouraged to avoid using private disposers", disposerMethod.declaringClass().name(),
-                        disposerMethod.name());
+                privateMembers.add(isApplicationClass, String.format("Disposer %s#%s", disposerMethod.declaringClass().name(),
+                        disposerMethod.name()));
                 ResultHandle paramTypesArray = destroy.newArray(Class.class, destroy.load(referenceHandles.length));
                 ResultHandle argsArray = destroy.newArray(Object.class, destroy.load(referenceHandles.length));
                 for (int i = 0; i < referenceHandles.length; i++) {
@@ -881,9 +880,8 @@ protected void implementCreate(ClassOutput classOutput, ClassCreator beanCreator
                 FieldInfo injectedField = fieldInjection.target.asField();
                 if (isReflectionFallbackNeeded(injectedField, targetPackage)) {
                     if (Modifier.isPrivate(injectedField.flags())) {
-                        Level level = isApplicationClass ? Level.INFO : Level.DEBUG;
-                        LOGGER.logf(level, "@Inject %s#%s is private - users are encouraged to avoid using private injection fields",
-                                fieldInjection.target.asField().declaringClass().name(), fieldInjection.target.asField().name());
+                        privateMembers.add(isApplicationClass, String.format("@Inject field %s#%s", fieldInjection.target.asField().declaringClass().name(),
+                                fieldInjection.target.asField().name()));
                     }
                     reflectionRegistration.registerField(injectedField);
                     create.invokeStaticMethod(MethodDescriptors.REFLECTIONS_WRITE_FIELD, create.loadClass(injectedField.declaringClass().name().toString()),
@@ -908,9 +906,8 @@ protected void implementCreate(ClassOutput classOutput, ClassCreator beanCreator
                 MethodInfo initializerMethod = methodInjection.target.asMethod();
                 if (isReflectionFallbackNeeded(initializerMethod, targetPackage)) {
                     if (Modifier.isPrivate(initializerMethod.flags())) {
-                        Level level = isApplicationClass ? Level.INFO : Level.DEBUG;
-                        LOGGER.logf(level, "@Inject %s#%s() is private - users are encouraged to avoid using private injection initializers",
-                                initializerMethod.declaringClass().name(), initializerMethod.name());
+                        privateMembers.add(isApplicationClass,
+                                String.format("@Inject initializer %s#%s()", initializerMethod.declaringClass().name(), initializerMethod.name()));
                     }
                     ResultHandle paramTypesArray = create.newArray(Class.class, create.load(referenceHandles.length));
                     ResultHandle argsArray = create.newArray(Object.class, create.load(referenceHandles.length));
@@ -958,10 +955,8 @@ protected void implementCreate(ClassOutput classOutput, ClassCreator beanCreator
                         bean.getDeployment().getIndex());
                 for (MethodInfo callback : postConstructCallbacks) {
                     if (Modifier.isPrivate(callback.flags())) {
-                        Level level = isApplicationClass ? Level.INFO : Level.DEBUG;
-                        LOGGER.logf(level, "PostConstruct callback %s#%s is private - users are encouraged to avoid using private callbacks",
-                               callback.declaringClass().name(),
-                               callback.name());
+                        privateMembers.add(isApplicationClass,
+                                String.format("@PostConstruct callback %s#%s()", callback.declaringClass().name(), callback.name()));
                         reflectionRegistration.registerMethod(callback);
                         create.invokeStaticMethod(MethodDescriptors.REFLECTIONS_INVOKE_METHOD, create.loadClass(callback.declaringClass().name().toString()),
                                 create.load(callback.name()), create.newArray(Class.class, create.load(0)), instanceHandle,
@@ -1001,9 +996,7 @@ protected void implementCreate(ClassOutput classOutput, ClassCreator beanCreator
 
             MethodInfo producerMethod = bean.getTarget().get().asMethod();
             if (Modifier.isPrivate(producerMethod.flags())) {
-                Level level = isApplicationClass ? Level.INFO : Level.DEBUG;
-                LOGGER.logf(level, "Producer %s#%s is private - users are encouraged to avoid using private producers", producerMethod.declaringClass().name(),
-                         producerMethod.name());
+                privateMembers.add(isApplicationClass, String.format("Producer method %s#%s()", producerMethod.declaringClass().name(), producerMethod.name()));
                 ResultHandle paramTypesArray = create.newArray(Class.class, create.load(referenceHandles.length));
                 ResultHandle argsArray = create.newArray(Object.class, create.load(referenceHandles.length));
                 for (int i = 0; i < referenceHandles.length; i++) {
@@ -1043,9 +1036,7 @@ protected void implementCreate(ClassOutput classOutput, ClassCreator beanCreator
             }
 
             if (Modifier.isPrivate(producerField.flags())) {
-                Level level = isApplicationClass ? Level.INFO : Level.DEBUG;
-                LOGGER.logf(level, "Producer %s#%s is private - users are encouraged to avoid using private producers", producerField.declaringClass().name(),
-                            producerField.name());
+                privateMembers.add(isApplicationClass, String.format("Producer field %s#%s", producerField.declaringClass().name(), producerField.name()));
                 reflectionRegistration.registerField(producerField);
                 create.assign(instanceHandle, create.invokeStaticMethod(MethodDescriptors.REFLECTIONS_READ_FIELD,
                         create.loadClass(producerField.declaringClass().name().toString()), create.load(producerField.name()), declaringProviderInstanceHandle));
@@ -1134,9 +1125,7 @@ private ResultHandle newInstanceHandle(BeanInfo bean, ClassCreator beanCreator,
 
         } else if (constructorInjection.isPresent()) {
             if (Modifier.isPrivate(constructor.flags())) {
-                Level level = isApplicationClass ? Level.INFO : Level.DEBUG;
-                LOGGER.logf(level, "Constructor %s is private - users are encouraged to avoid using private constructors",
-                            constructor.declaringClass().name());
+                privateMembers.add(isApplicationClass, String.format("Bean constructor %s on %s", constructor, constructor.declaringClass().name()));
                 ResultHandle paramTypesArray = creator.newArray(Class.class, creator.load(providerHandles.size()));
                 ResultHandle argsArray = creator.newArray(Object.class, creator.load(providerHandles.size()));
                 for (int i = 0; i < injectionPoints.size(); i++) {
@@ -1158,9 +1147,8 @@ private ResultHandle newInstanceHandle(BeanInfo bean, ClassCreator beanCreator,
         } else {
             MethodInfo noArgsConstructor = bean.getTarget().get().asClass().method(Methods.INIT);
             if (Modifier.isPrivate(noArgsConstructor.flags())) {
-                Level level = isApplicationClass ? Level.INFO : Level.DEBUG;
-                LOGGER.logf(level, "Constructor %s is private - users are encouraged to avoid using private constructors",
-                        noArgsConstructor.declaringClass().name());
+                privateMembers.add(isApplicationClass,
+                        String.format("Bean constructor %s on %s", noArgsConstructor, noArgsConstructor.declaringClass().name()));
                 ResultHandle paramTypesArray = creator.newArray(Class.class, creator.load(0));
                 ResultHandle argsArray = creator.newArray(Object.class, creator.load(0));
 
diff --git a/independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanProcessor.java b/independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanProcessor.java
index 3cd2f4ce340..f64e9f633a6 100644
--- a/independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanProcessor.java
+++ b/independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanProcessor.java
@@ -31,6 +31,7 @@
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Predicate;
+import java.util.stream.Collectors;
 
 import javax.enterprise.context.control.ActivateRequestContext;
 import javax.enterprise.inject.Any;
@@ -150,20 +151,21 @@ public <V> V put(Key<V> key, V value) {
         this.beanRegistrars = initAndSort(beanRegistrars, buildContext);
         this.beanDeploymentValidators = initAndSort(beanDeploymentValidators, buildContext);
     }
-
+    
     public BeanDeployment process() throws IOException {
 
         BeanDeployment beanDeployment = new BeanDeployment(new IndexWrapper(index), additionalBeanDefiningAnnotations, annotationTransformers,
                 resourceAnnotations, beanRegistrars, buildContext, removeUnusedBeans, unusedExclusions);
         beanDeployment.init();
         beanDeployment.validate(buildContext, beanDeploymentValidators);
-
+        
+        PrivateMembersCollector privateMembers = new PrivateMembersCollector();
         AnnotationLiteralProcessor annotationLiterals = new AnnotationLiteralProcessor(sharedAnnotationLiterals, applicationClassPredicate);
-        BeanGenerator beanGenerator = new BeanGenerator(annotationLiterals, applicationClassPredicate);
+        BeanGenerator beanGenerator = new BeanGenerator(annotationLiterals, applicationClassPredicate, privateMembers);
         ClientProxyGenerator clientProxyGenerator = new ClientProxyGenerator(applicationClassPredicate);
-        InterceptorGenerator interceptorGenerator = new InterceptorGenerator(annotationLiterals, applicationClassPredicate);
+        InterceptorGenerator interceptorGenerator = new InterceptorGenerator(annotationLiterals, applicationClassPredicate, privateMembers);
         SubclassGenerator subclassGenerator = new SubclassGenerator(annotationLiterals, applicationClassPredicate);
-        ObserverGenerator observerGenerator = new ObserverGenerator(annotationLiterals, applicationClassPredicate);
+        ObserverGenerator observerGenerator = new ObserverGenerator(annotationLiterals, applicationClassPredicate, privateMembers);
         AnnotationLiteralGenerator annotationLiteralsGenerator = new AnnotationLiteralGenerator();
 
         Map<BeanInfo, String> beanToGeneratedName = new HashMap<>();
@@ -209,6 +211,8 @@ public BeanDeployment process() throws IOException {
             }
         }
 
+        privateMembers.log();
+
         // Generate _ComponentsProvider
         resources.addAll(new ComponentsProviderGenerator().generate(name, beanDeployment, beanToGeneratedName, observerToGeneratedName));
 
@@ -561,5 +565,42 @@ <V> V putInternal(String key, V value) {
         }
 
     }
+    
+    static class PrivateMembersCollector {
+        
+        private final List<String> appDescriptions;
+        private final List<String> fwkDescriptions;
+        
+        public PrivateMembersCollector() {
+            this.appDescriptions = new ArrayList<>();
+            this.fwkDescriptions = LOGGER.isDebugEnabled() ? new ArrayList<>() : null;
+        }
+
+        void add(boolean isApplicationClass, String description) {
+            if (isApplicationClass) {
+                appDescriptions.add(description);
+            } else if(fwkDescriptions != null) {
+                fwkDescriptions.add(description);
+            }
+        }
+        
+        private void log() {
+            // Log application problems
+            if (!appDescriptions.isEmpty()) {
+                int limit = LOGGER.isDebugEnabled() ? Integer.MAX_VALUE : 3;
+                String info = appDescriptions.stream().limit(limit).map(d -> "\\t- " + d).collect(Collectors.joining(",\\n"));
+                if (appDescriptions.size() > limit) {
+                    info += "\\n\\t- and " + (appDescriptions.size() - limit) + " more - please enable debug logging to see the full list";
+                }
+                LOGGER.infof("Found unrecommended usage of private members in application beans:\\n%s", info);
+            }
+            // Log fwk problems
+            if (fwkDescriptions != null && !fwkDescriptions.isEmpty()) {
+                LOGGER.debugf("Found unrecommended usage of private members in framework beans:\\n%s",
+                        fwkDescriptions.stream().map(d -> "\\t- " + d).collect(Collectors.joining(",\\n")));
+            }
+        }
+ 
+    }
 
 }
diff --git a/independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/InterceptorGenerator.java b/independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/InterceptorGenerator.java
index 349b97877bb..5080c3fb5f6 100644
--- a/independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/InterceptorGenerator.java
+++ b/independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/InterceptorGenerator.java
@@ -36,9 +36,8 @@
 import org.jboss.jandex.DotName;
 import org.jboss.jandex.MethodInfo;
 import org.jboss.jandex.Type;
-import org.jboss.logging.Logger;
-import org.jboss.logging.Logger.Level;
 import org.jboss.protean.arc.InjectableInterceptor;
+import org.jboss.protean.arc.processor.BeanProcessor.PrivateMembersCollector;
 import org.jboss.protean.arc.processor.ResourceOutput.Resource;
 import org.jboss.protean.arc.processor.ResourceOutput.Resource.SpecialType;
 import org.jboss.protean.gizmo.BranchResult;
@@ -57,16 +56,11 @@
  */
 public class InterceptorGenerator extends BeanGenerator {
 
-    private static final Logger LOGGER = Logger.getLogger(InterceptorGenerator.class);
-
     protected static final String FIELD_NAME_BINDINGS = "bindings";
 
-    /**
-     *
-     * @param annotationLiterals
-     */
-    public InterceptorGenerator(AnnotationLiteralProcessor annotationLiterals, Predicate<DotName> applicationClassPredicate) {
-        super(annotationLiterals, applicationClassPredicate);
+    public InterceptorGenerator(AnnotationLiteralProcessor annotationLiterals, Predicate<DotName> applicationClassPredicate,
+            PrivateMembersCollector privateMembers) {
+        super(annotationLiterals, applicationClassPredicate, privateMembers);
     }
 
     /**
@@ -216,11 +210,8 @@ private void addIntercept(MethodCreator intercept, MethodInfo interceptorMethod,
             Class<?> retType = InterceptionType.AROUND_INVOKE.equals(interceptionType) ? Object.class : void.class;
             ResultHandle ret;
             if (Modifier.isPrivate(interceptorMethod.flags())) {
-                Level level = isApplicationClass ? Level.INFO : Level.DEBUG;
-                LOGGER.logf(level, "Interceptor method %s#%s is private - users are encouraged to avoid using private interceptor methods",
-                        interceptorMethod.declaringClass()
-                                .name(),
-                        interceptorMethod.name());
+                privateMembers.add(isApplicationClass,
+                        String.format("Interceptor method %s#%s()", interceptorMethod.declaringClass().name(), interceptorMethod.name()));
                 // Use reflection fallback
                 ResultHandle paramTypesArray = trueBranch.newArray(Class.class, trueBranch.load(1));
                 trueBranch.writeArrayValue(paramTypesArray, 0, trueBranch.loadClass(InvocationContext.class));
diff --git a/independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/ObserverGenerator.java b/independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/ObserverGenerator.java
index 04b013b8f18..bc1c3282339 100644
--- a/independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/ObserverGenerator.java
+++ b/independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/ObserverGenerator.java
@@ -39,13 +39,12 @@
 import org.jboss.jandex.AnnotationInstance;
 import org.jboss.jandex.ClassInfo;
 import org.jboss.jandex.DotName;
-import org.jboss.logging.Logger;
-import org.jboss.logging.Logger.Level;
 import org.jboss.protean.arc.CreationalContextImpl;
 import org.jboss.protean.arc.CurrentInjectionPointProvider;
 import org.jboss.protean.arc.InjectableBean;
 import org.jboss.protean.arc.InjectableObserverMethod;
 import org.jboss.protean.arc.InjectableReferenceProvider;
+import org.jboss.protean.arc.processor.BeanProcessor.PrivateMembersCollector;
 import org.jboss.protean.arc.processor.ResourceOutput.Resource;
 import org.jboss.protean.arc.processor.ResourceOutput.Resource.SpecialType;
 import org.jboss.protean.gizmo.ClassCreator;
@@ -64,19 +63,17 @@ public class ObserverGenerator extends AbstractGenerator {
 
     static final String OBSERVER_SUFFIX = "_Observer";
 
-    private static final Logger LOGGER = Logger.getLogger(ObserverGenerator.class);
-
     private final AnnotationLiteralProcessor annotationLiterals;
 
     private final Predicate<DotName> applicationClassPredicate;
-    /**
-     *
-     * @param annotationLiterals
-     * @param applicationClassPredicate
-     */
-    public ObserverGenerator(AnnotationLiteralProcessor annotationLiterals, Predicate<DotName> applicationClassPredicate) {
+    
+    private final PrivateMembersCollector privateMembers;
+    
+    public ObserverGenerator(AnnotationLiteralProcessor annotationLiterals, Predicate<DotName> applicationClassPredicate,
+            PrivateMembersCollector privateMembers) {
         this.annotationLiterals = annotationLiterals;
         this.applicationClassPredicate = applicationClassPredicate;
+        this.privateMembers = privateMembers;
     }
 
     /**
@@ -211,9 +208,8 @@ protected void implementNotify(ObserverInfo observer, ClassCreator observerCreat
         }
 
         if (Modifier.isPrivate(observer.getObserverMethod().flags())) {
-            Level level = isApplicationClass ? Level.INFO : Level.DEBUG;
-            LOGGER.logf(level, "Observer %s#%s is private - users are encouraged to avoid using private observers", observer.getObserverMethod().declaringClass().name(),
-                    observer.getObserverMethod().name());
+            privateMembers.add(isApplicationClass,
+                    String.format("Observer method %s#%s()", observer.getObserverMethod().declaringClass().name(), observer.getObserverMethod().name()));
             ResultHandle paramTypesArray = notify.newArray(Class.class, notify.load(referenceHandles.length));
             ResultHandle argsArray = notify.newArray(Object.class, notify.load(referenceHandles.length));
             for (int i = 0; i < referenceHandles.length; i++) {
diff --git a/independent-projects/arc/processor/src/test/java/org/jboss/protean/arc/processor/BeanGeneratorTest.java b/independent-projects/arc/processor/src/test/java/org/jboss/protean/arc/processor/BeanGeneratorTest.java
index 4b00ba230bc..50c31b52f07 100644
--- a/independent-projects/arc/processor/src/test/java/org/jboss/protean/arc/processor/BeanGeneratorTest.java
+++ b/independent-projects/arc/processor/src/test/java/org/jboss/protean/arc/processor/BeanGeneratorTest.java
@@ -22,7 +22,6 @@
 import java.util.AbstractCollection;
 import java.util.AbstractList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
 
 import javax.enterprise.context.ApplicationScoped;
@@ -30,6 +29,7 @@
 import javax.enterprise.inject.Produces;
 
 import org.jboss.jandex.Index;
+import org.jboss.protean.arc.processor.BeanProcessor.PrivateMembersCollector;
 import org.jboss.protean.arc.processor.types.Foo;
 import org.jboss.protean.arc.processor.types.FooQualifier;
 import org.junit.Test;
@@ -43,7 +43,7 @@ public void testGenerator() throws IOException {
         BeanDeployment deployment = new BeanDeployment(index, null, null);
         deployment.init();
 
-        BeanGenerator generator = new BeanGenerator(new AnnotationLiteralProcessor(true, TruePredicate.INSTANCE), TruePredicate.INSTANCE);
+        BeanGenerator generator = new BeanGenerator(new AnnotationLiteralProcessor(true, TruePredicate.INSTANCE), TruePredicate.INSTANCE, new PrivateMembersCollector());
 
         deployment.getBeans().forEach(bean -> generator.generate(bean, ReflectionRegistration.NOOP));
         // TODO test generated bytecode
@@ -56,7 +56,7 @@ public void testGeneratorForNormalScopedProducer() throws IOException {
         BeanDeployment deployment = new BeanDeployment(index, null, null);
         deployment.init();
 
-        BeanGenerator generator = new BeanGenerator(new AnnotationLiteralProcessor(true, TruePredicate.INSTANCE), TruePredicate.INSTANCE);
+        BeanGenerator generator = new BeanGenerator(new AnnotationLiteralProcessor(true, TruePredicate.INSTANCE), TruePredicate.INSTANCE, new PrivateMembersCollector());
 
         deployment.getBeans().forEach(bean -> generator.generate(bean, ReflectionRegistration.NOOP));
         // TODO test generated bytecode
diff --git a/independent-projects/arc/processor/src/test/java/org/jboss/protean/arc/processor/ClientProxyGeneratorTest.java b/independent-projects/arc/processor/src/test/java/org/jboss/protean/arc/processor/ClientProxyGeneratorTest.java
index 945d0290130..f4603e87223 100644
--- a/independent-projects/arc/processor/src/test/java/org/jboss/protean/arc/processor/ClientProxyGeneratorTest.java
+++ b/independent-projects/arc/processor/src/test/java/org/jboss/protean/arc/processor/ClientProxyGeneratorTest.java
@@ -21,7 +21,6 @@
 import java.io.IOException;
 import java.util.AbstractList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
 
 import javax.enterprise.context.ApplicationScoped;
@@ -29,6 +28,7 @@
 import javax.enterprise.inject.Produces;
 
 import org.jboss.jandex.Index;
+import org.jboss.protean.arc.processor.BeanProcessor.PrivateMembersCollector;
 import org.jboss.protean.arc.processor.ResourceOutput.Resource;
 import org.junit.Test;
 
@@ -41,7 +41,7 @@ public void testGenerator() throws IOException {
         BeanDeployment deployment = new BeanDeployment(index, null, null);
         deployment.init();
 
-        BeanGenerator beanGenerator = new BeanGenerator( new AnnotationLiteralProcessor(true, TruePredicate.INSTANCE), TruePredicate.INSTANCE);
+        BeanGenerator beanGenerator = new BeanGenerator( new AnnotationLiteralProcessor(true, TruePredicate.INSTANCE), TruePredicate.INSTANCE, new PrivateMembersCollector());
         ClientProxyGenerator proxyGenerator = new ClientProxyGenerator(TruePredicate.INSTANCE);
 
         deployment.getBeans().stream().filter(bean -> bean.getScope().isNormal()).forEach(bean -> {
diff --git a/independent-projects/arc/processor/src/test/java/org/jboss/protean/arc/processor/InterceptorGeneratorTest.java b/independent-projects/arc/processor/src/test/java/org/jboss/protean/arc/processor/InterceptorGeneratorTest.java
index 47b1780fbdf..eb3d6e62a6c 100644
--- a/independent-projects/arc/processor/src/test/java/org/jboss/protean/arc/processor/InterceptorGeneratorTest.java
+++ b/independent-projects/arc/processor/src/test/java/org/jboss/protean/arc/processor/InterceptorGeneratorTest.java
@@ -23,7 +23,6 @@
 import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.Collections;
 
 import javax.annotation.Priority;
 import javax.inject.Inject;
@@ -34,6 +33,7 @@
 
 import org.jboss.jandex.DotName;
 import org.jboss.jandex.Index;
+import org.jboss.protean.arc.processor.BeanProcessor.PrivateMembersCollector;
 import org.jboss.protean.arc.processor.types.Baz;
 import org.junit.Test;
 
@@ -53,7 +53,7 @@ public void testGenerator() throws IOException {
         assertEquals(1, myInterceptor.getBindings().size());
         assertNotNull(myInterceptor.getAroundInvoke());
 
-        InterceptorGenerator generator = new InterceptorGenerator(new AnnotationLiteralProcessor(true, TruePredicate.INSTANCE), TruePredicate.INSTANCE);
+        InterceptorGenerator generator = new InterceptorGenerator(new AnnotationLiteralProcessor(true, TruePredicate.INSTANCE), TruePredicate.INSTANCE, new PrivateMembersCollector());
 
         deployment.getInterceptors().forEach(interceptor -> generator.generate(interceptor, ReflectionRegistration.NOOP));
         // TODO test generated bytecode
diff --git a/independent-projects/arc/processor/src/test/java/org/jboss/protean/arc/processor/SubclassGeneratorTest.java b/independent-projects/arc/processor/src/test/java/org/jboss/protean/arc/processor/SubclassGeneratorTest.java
index bac068afaaf..e74c577c880 100644
--- a/independent-projects/arc/processor/src/test/java/org/jboss/protean/arc/processor/SubclassGeneratorTest.java
+++ b/independent-projects/arc/processor/src/test/java/org/jboss/protean/arc/processor/SubclassGeneratorTest.java
@@ -25,7 +25,6 @@
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
-import java.util.Collections;
 
 import javax.annotation.Priority;
 import javax.enterprise.context.Dependent;
@@ -37,6 +36,7 @@
 
 import org.jboss.jandex.DotName;
 import org.jboss.jandex.Index;
+import org.jboss.protean.arc.processor.BeanProcessor.PrivateMembersCollector;
 import org.jboss.protean.arc.processor.ResourceOutput.Resource;
 import org.jboss.protean.arc.processor.types.Baz;
 import org.junit.Test;
@@ -51,7 +51,7 @@ public void testGenerator() throws IOException {
         deployment.init();
 
         AnnotationLiteralProcessor annotationLiteralProcessor = new AnnotationLiteralProcessor(true, TruePredicate.INSTANCE);
-        BeanGenerator beanGenerator = new BeanGenerator(annotationLiteralProcessor, TruePredicate.INSTANCE);
+        BeanGenerator beanGenerator = new BeanGenerator(annotationLiteralProcessor, TruePredicate.INSTANCE, new PrivateMembersCollector());
         SubclassGenerator generator = new SubclassGenerator(annotationLiteralProcessor, TruePredicate.INSTANCE);
         BeanInfo simpleBean = deployment.getBeans().stream()
                 .filter(b -> b.getTarget().get().asClass().name().equals(DotName.createSimple(SimpleBean.class.getName()))).findAny().get();