All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.micronaut.context.ConsoleBeanResolutionTracer Maven / Gradle / Ivy

/*
 * Copyright 2017-2025 original authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.micronaut.context;

import io.micronaut.context.annotation.ConfigurationReader;
import io.micronaut.context.env.Environment;
import io.micronaut.context.env.PropertyEntry;
import io.micronaut.context.env.PropertySource;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.naming.NameUtils;
import io.micronaut.core.naming.Named;
import io.micronaut.core.order.OrderUtil;
import io.micronaut.core.type.Argument;
import io.micronaut.core.type.TypeInformation;
import io.micronaut.core.util.AnsiColour;
import io.micronaut.core.util.StringUtils;
import io.micronaut.inject.BeanDefinition;
import io.micronaut.inject.BeanDefinitionReference;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

abstract sealed class ConsoleBeanResolutionTracer
    implements BeanResolutionTracer {
    private static final String RIGHT_ARROW = AnsiColour.isSupported() ? " ➡️  " : " -> ";
    private static final String RIGHT_ARROW_LOOP = AnsiColour.isSupported() ? " ↪️  " : "\\---> ";
    private static final CharSequence START_TIME = "BeanResolutionTrace-start";
    private static final Set INTERNAL_PACKAGES = Set.of(
        "io.micronaut.context",
        "io.micronaut.aop",
        "io.micronaut.core.util",
        "org.codehaus.groovy.vmplugin",
        "java.util"
    );


    @Override
    public void traceInitialConfiguration(Environment environment, Collection> beanReferences, Collection> disabledBeans) {
        Collection propertySources = environment.getPropertySources();
        Set activeNames = environment.getActiveNames();
        StringWriter sw = new StringWriter();
        try (BufferedWriter writer = new BufferedWriter(sw)) {
            writer.newLine();
            writer.write("Configuration Profile");
            writer.newLine();
            writer.write("---------------------");
            writer.newLine();
            writer.write(AnsiColour.brightBlue("Active Environment Names: ") + activeNames);
            writer.newLine();
            writer.newLine();
            writer.write(AnsiColour.brightBlue("Available Property Sources (Priority Order Highest to Lowest): "));
            writer.newLine();
            propertySources.stream().sorted(OrderUtil.REVERSE_ORDERED_COMPARATOR)
                .forEach(propertySource -> {
                    try {
                        writer.write(" ✚ ");
                        writer.write(AnsiColour.formatObject(propertySource));
                        writer.write(" (");
                        writer.write(propertySource.getOrigin().location() + ")");
                        writer.newLine();
                    } catch (IOException e) {
                        // ignore.
                    }
                });
            writer.newLine();
            writer.write(AnsiColour.brightBlue("Configurable Beans: "));
            writer.newLine();
            List> configRefs = beanReferences.stream()
                .filter(ref -> ref.hasStereotype(ConfigurationReader.class) &&
                    ref.stringValue(ConfigurationReader.class, "prefix").isPresent())
                .sorted((b1, b2) ->
                    {
                        String p1 = b1.stringValue(ConfigurationReader.class, "prefix").get();
                        String p2 = b2.stringValue(ConfigurationReader.class, "prefix").get();
                        return p1.compareTo(p2);
                    }
                ).toList();

            configRefs.forEach(ref -> {
                String prefix = ref.stringValue(ConfigurationReader.class, "prefix").orElse(null);
                if (prefix != null) {
                    Argument argument = ref.asArgument();
                    try {
                        writer.write(" ✚ ");
                        writer.write(AnsiColour.formatObject(prefix));
                        writer.write(RIGHT_ARROW);
                        writer.write(TypeInformation.TypeFormat.getTypeString(
                            TypeInformation.TypeFormat.ANSI_SHORTENED,
                            argument.getType(),
                            argument.getTypeVariables()
                        ));
                        writer.newLine();
                    } catch (IOException e) {
                        // ignore
                    }
                }
            });
            writer.newLine();
            writer.write(AnsiColour.brightBlue("Applicable Configuration Present: "));
            writer.newLine();
            configRefs.stream()
                .flatMap(ref -> ref.stringValue(ConfigurationReader.class, "prefix").stream())
                .flatMap(prefix -> {
                    if (prefix.endsWith(".*")) {
                        String eachProperty = prefix.substring(0, prefix.length() - 2);
                        return environment.getPropertyEntries(eachProperty).stream().flatMap(entry ->
                            {
                                String path = eachProperty + '.' + entry;
                                return environment.getPropertyEntries(
                                    path
                                ).stream().map(p -> path + '.' + p);
                            }
                        );
                    } else {
                        return environment.getPropertyEntries(prefix).stream().map(entry ->
                            prefix + '.' + entry
                        );
                    }
                }).sorted().distinct().forEach(entry -> {
                    try {
                        writer.write(" ✚ ");
                        writer.write(AnsiColour.formatObject(entry));
                        PropertyEntry pe = environment.getPropertyEntry(entry).orElse(null);
                        if (pe != null) {
                            String text = " Origin(" + AnsiColour.formatObject(pe.raw()) + " from " + AnsiColour.brightYellow(pe.origin().location()) + ")";
                            writer.write(text);
                        }
                        writer.newLine();
                    } catch (IOException e) {
                        // ignore
                    }
                });
            writer.write("---------------------");
            writer.newLine();
        } catch (IOException e) {
            // ignore, should never happen
        }

        writeOutput(sw.toString());
    }

    /**
     * Write the output for the tracer.
     * @param output The output
     */
    protected abstract void writeOutput(@NonNull String output);

    @Override
    public void traceBeanCreation(
        BeanResolutionContext resolutionContext,
        BeanDefinition beanDefinition, Argument beanType) {
        resolutionContext.setAttribute(START_TIME, System.currentTimeMillis());
        List createdAt = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE)
            .walk(s ->
                s.dropWhile(f ->
                        (INTERNAL_PACKAGES.stream().anyMatch(p -> f.getClassName().startsWith(p)) || f.getDeclaringClass().isSynthetic()) &&
                            // capture startup beans
                            !(f.getClassName().equals(DefaultBeanContext.class.getName()) && f.getMethodName().equals("start")))
                    .limit(3)
                    .collect(Collectors.toList())
            );

        String beanName = getBeanName(beanType, beanDefinition);
        StringWriter sw = new StringWriter();
        try (BufferedWriter writer = new BufferedWriter(sw)) {
            writer.newLine();
            String beanDescription = beanDefinition.getBeanDescription(TypeInformation.TypeFormat.ANSI_SHORTENED);
            writer.write(beanName + RIGHT_ARROW + beanDescription + " at location:");
            writer.newLine();
            for (StackWalker.StackFrame stackFrame : createdAt) {
                StackTraceElement traceElement = stackFrame.toStackTraceElement();
                StackTraceElement shortened = new StackTraceElement(
                    NameUtils.getShortenedName(traceElement.getClassName()),
                    traceElement.getMethodName(),
                    traceElement.getFileName(),
                    traceElement.getLineNumber()
                );
                writer.write(shortened.toString());
                writer.newLine();
            }
            writer.newLine();
        } catch (IOException e) {
            // ignore, should never happen
        }

        writeOutput(sw.toString());
    }

    @Override
    public  void traceBeanResolved(BeanResolutionContext resolutionContext, Argument beanType, Qualifier qualifier, T bean) {
        String prefix = padLeft(resolutionContext, 1) + RIGHT_ARROW;
        StringWriter sw = new StringWriter();
        try (BufferedWriter writer = new BufferedWriter(sw)) {

            writer.write(prefix);
            writer.write(bean != null ? "✅ " : "❌ ");
            if (bean != null) {
                if (qualifier != null) {
                    if (qualifier instanceof Named named) {
                        writer.write(AnsiColour.yellow("@Named("));
                        writer.write(AnsiColour.green("\"" + named.getName() + "\""));
                        writer.write(AnsiColour.yellow(")"));
                    } else {
                        writer.write(AnsiColour.yellow(qualifier.toString()));
                    }
                    writer.write(" ");
                }
                writer.write(AnsiColour.formatObject(bean));
            } else {
                writer.write("Bean of type ");
                writer.write(beanType.getBeanTypeString(TypeInformation.TypeFormat.ANSI_SIMPLE));
                writer.write(" does not exist.");
            }

            writer.newLine();
        } catch (IOException e) {
            // ignore, should never happen
        }

        writeOutput(sw.toString());
    }

    @Override
    public  void traceBeanDisabled(BeanResolutionContext resolutionContext, Argument beanType, Qualifier qualifier, String disabledBeanMessage) {
        String[] lines = disabledBeanMessage.split("\\r?\\n");
        String prefix = padLeft(resolutionContext, 3);
        StringWriter sw = new StringWriter();
        try (BufferedWriter writer = new BufferedWriter(sw)) {
            for (String line : lines) {
                if (StringUtils.isNotEmpty(line.trim())) {
                    writer.write(prefix);
                    if (line.startsWith("*")) {
                        writer.write("❌ ");
                        writer.write(line.substring(1));
                        writer.newLine();
                    } else {
                        writer.write(line);
                        writer.newLine();
                    }
                }
            }
        } catch (IOException e) {
            // ignore, should not happen
        }

        writeOutput(sw.toString());
    }

    @Override
    public  void traceValueResolved(BeanResolutionContext resolutionContext, Argument argument, String property, T value) {
        BeanContext context = resolutionContext.getContext();
        if (context instanceof ApplicationContext applicationContext) {
            Environment environment = applicationContext.getEnvironment();
            PropertySource.Origin origin = environment.getPropertyEntry(property)
                .map(PropertyEntry::origin)
                .orElse(null);
            String prefix = padLeft(resolutionContext, 1) + RIGHT_ARROW;
            StringWriter sw = new StringWriter();
            try (BufferedWriter writer = new BufferedWriter(sw)) {
                writer.write(prefix);
                writer.write(AnsiColour.formatObject(property));
                writer.write(" = ");
                writer.write(AnsiColour.formatObject(value));
                if (origin != null) {
                    writer.write(" (Origin: " + AnsiColour.brightYellow(origin.location()) + ")");
                }
                writer.newLine();
            } catch (IOException e) {
                // ignore, should not happen
            }
            writeOutput(sw.toString());
        }

    }

    @Override
    public  void traceBeanCreated(BeanResolutionContext resolutionContext, BeanDefinition beanDefinition) {
        Object v = resolutionContext.getAttribute(START_TIME);
        if (v instanceof Long start) {
            StringWriter sw = new StringWriter();
            try (BufferedWriter writer = new BufferedWriter(sw)) {
                writer.newLine();
                String beanName = beanDefinition.getBeanDescription(TypeInformation.TypeFormat.ANSI_SIMPLE, false);
                long now = System.currentTimeMillis();
                writer.write("✅ Created " + beanName + " in " + (now - start) + "ms");
                writer.newLine();
                writer.write("------------");
                writer.newLine();
            } catch (IOException e) {
                // ignore, should not happen
            }
            writeOutput(sw.toString());
        }
    }

    @Override
    public  void traceInjectBean(BeanResolutionContext resolutionContext, BeanResolutionContext.Segment segment) {
        BeanResolutionContext.Path path = resolutionContext.getPath();
        if (segment.getDeclaringType() != null &&
            segment.getDeclaringType().getBeanType().isSynthetic() &&
            segment.getArgument().getName().startsWith("$")) {
            // skip synthetic arguments
            return;
        }
        int size = path.size();
        String prefix = "";
        if (size > 1) {
            String spaces = "   ".repeat(size);
            prefix = spaces + RIGHT_ARROW_LOOP;
        }
        String content = prefix + segment.toConsoleString(AnsiColour.isSupported());
        writeOutput(content);
    }

    private static String getBeanName(Argument beanType, BeanDefinition beanDefinition) {
        String beanName;
        if (beanType.getType().isSynthetic()) {
            beanName = beanDefinition.getTypeInformation().getBeanTypeString(TypeInformation.TypeFormat.ANSI_SIMPLE);
        } else {
            beanName = beanType
                .getBeanTypeString(TypeInformation.TypeFormat.ANSI_SIMPLE);
        }
        return beanName;
    }


    @NonNull
    private static String padLeft(BeanResolutionContext resolutionContext, int amount) {
        int size = resolutionContext.getPath().size() + amount;
        String prefix = "";
        if (size > 1) {
            prefix = "   ".repeat(size);
        }
        return prefix;
    }


    static final class LoggingBeanResolutionTracer extends ConsoleBeanResolutionTracer {
        static final Logger LOGGER = LoggerFactory.getLogger("io.micronaut.inject");

        @Override
        protected void writeOutput(String output) {
            LOGGER.trace(output);
        }
    }

    static final class SystemOutBeanResolutionTracer extends ConsoleBeanResolutionTracer {
        @Override
        protected void writeOutput(String output) {
            System.out.println(output);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy