Skip to content

Conversation

@jsvd
Copy link
Member

@jsvd jsvd commented Oct 8, 2025

This removes the annoying log info:

[2025-10-14T16:46:35,085][INFO ][org.reflections.Reflections] Reflections took 44 ms to scan 1 urls, producing 149 keys and 523 values

Given we only use reflectiong to grab locally installed java plugin classes by annotation, we implement here a tiny scanner to do the same, and then we can remove the dependency.

exhaustive test suite run: https://buildkite.com/elastic/logstash-exhaustive-tests-pipeline/builds/2701/steps/canvas

@github-actions
Copy link
Contributor

github-actions bot commented Oct 8, 2025

🤖 GitHub comments

Expand to view the GitHub comments

Just comment with:

  • run docs-build : Re-trigger the docs validation. (use unformatted text in the comment!)

@mergify
Copy link
Contributor

mergify bot commented Oct 8, 2025

This pull request does not have a backport label. Could you fix it @jsvd? 🙏
To fixup this pull request, you need to add the backport labels for the needed
branches, such as:

  • backport-8./d is the label to automatically backport to the 8./d branch. /d is the digit.
  • If no backport is necessary, please add the backport-skip label

@jsvd jsvd changed the title attempt at removing dependency on org.reflections remove dependency on org.reflections Oct 14, 2025
@jsvd jsvd marked this pull request as ready for review October 14, 2025 15:56
@jsvd jsvd requested a review from Copilot October 24, 2025 10:41
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR removes the dependency on org.reflections library and replaces it with a custom lightweight package scanner. The change eliminates verbose logging from the reflections library while maintaining the same plugin discovery functionality.

Key Changes:

  • Implemented a custom PackageScanner class to scan for annotated plugin classes
  • Refactored PluginRegistry to use the new scanner instead of the reflections library
  • Removed the org.reflections:reflections dependency from build configuration and licensing files

Reviewed Changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
logstash-core/src/main/java/org/logstash/plugins/discovery/PackageScanner.java New custom scanner implementation for finding annotated classes in packages
logstash-core/src/main/java/org/logstash/plugins/discovery/PluginRegistry.java Updated to use PackageScanner instead of reflections library, simplified plugin discovery logic
logstash-core/src/test/java/org/logstash/plugins/discovery/PluginRegistryTest.java New test suite verifying plugin discovery works for built-in plugins
logstash-core/build.gradle Removed org.reflections:reflections dependency
tools/dependencies-report/src/main/resources/licenseMapping.csv Removed license mapping entry for reflections
tools/dependencies-report/src/main/resources/notices/org.reflections!reflections-NOTICE.txt Removed license notice file
NOTICE.TXT Removed reflections library attribution

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Copy link
Contributor

@andsel andsel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a couple of notes

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 62 to 68
if ("file".equals(protocol)) {
scanDirectory(resource, basePackage, classNames);
} else if ("jar".equals(protocol)) {
scanJar(resource, resourcePath, classNames);
} else if (resource.openConnection() instanceof JarURLConnection) {
scanJar(resource, resourcePath, classNames);
}
Copy link

Copilot AI Dec 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The protocol check at lines 64-67 calls resource.openConnection() potentially multiple times (once at line 66 to check the protocol, then again at line 96 inside scanJar). This creates unnecessary connection overhead and could lead to resource leaks. Consider checking the connection type once and reusing it.

Copilot uses AI. Check for mistakes.
@jsvd jsvd force-pushed the remove_reflections_library branch from d2c7b47 to c18eefb Compare December 19, 2025 15:07
@elasticmachine
Copy link

💛 Build succeeded, but was flaky

Failed CI Steps

History

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +67 to +70
URLConnection connection = resource.openConnection();
if (connection instanceof JarURLConnection) {
scanJar((JarURLConnection) connection, resourcePath, classNames);
}
Copy link

Copilot AI Dec 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The URLConnection opened on line 67 is not explicitly closed. While JarURLConnection is handled with try-with-resources in scanJar, if the connection is not a JarURLConnection but is some other type of URLConnection that requires cleanup, it may lead to a resource leak. Consider closing the connection in a finally block or using try-with-resources if the connection type is uncertain.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scanJar cover with try-with-resources just the JarFile obtained from connection.getJarFile() not the URLConnection itself.

@jsvd maybe we could use a try-with-resources when we get the connection at resource.openConnection();

Suggested change
URLConnection connection = resource.openConnection();
if (connection instanceof JarURLConnection) {
scanJar((JarURLConnection) connection, resourcePath, classNames);
}
try (URLConnection connection = resource.openConnection()) {
if (connection instanceof JarURLConnection) {
scanJar((JarURLConnection) connection, resourcePath, classNames);
}
}

Copy link
Contributor

@andsel andsel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a couple of suggestion to cover the issue that Copilot partially surfaced, and an readability improvement of the class names scanning.

Comment on lines +67 to +70
URLConnection connection = resource.openConnection();
if (connection instanceof JarURLConnection) {
scanJar((JarURLConnection) connection, resourcePath, classNames);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scanJar cover with try-with-resources just the JarFile obtained from connection.getJarFile() not the URLConnection itself.

@jsvd maybe we could use a try-with-resources when we get the connection at resource.openConnection();

Suggested change
URLConnection connection = resource.openConnection();
if (connection instanceof JarURLConnection) {
scanJar((JarURLConnection) connection, resourcePath, classNames);
}
try (URLConnection connection = resource.openConnection()) {
if (connection instanceof JarURLConnection) {
scanJar((JarURLConnection) connection, resourcePath, classNames);
}
}

Comment on lines +38 to +52
Set<Class<?>> result = new HashSet<>();
for (String className : classNames) {
if (className.contains("$")) {
continue;
}
try {
Class<?> candidate = Class.forName(className, false, loader);
if (candidate.isAnnotationPresent(annotation)) {
result.add(candidate);
}
} catch (ClassNotFoundException | LinkageError e) {
throw new IllegalStateException("Unable to load class discovered during scanning: " + className, e);
}
}
return result;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that the size of classNames is more than some tens, I think we could use stream syntax to separate looping from logic to be applied:

Suggested change
Set<Class<?>> result = new HashSet<>();
for (String className : classNames) {
if (className.contains("$")) {
continue;
}
try {
Class<?> candidate = Class.forName(className, false, loader);
if (candidate.isAnnotationPresent(annotation)) {
result.add(candidate);
}
} catch (ClassNotFoundException | LinkageError e) {
throw new IllegalStateException("Unable to load class discovered during scanning: " + className, e);
}
}
return result;
Set<Class<?>> result =classNames.stream()
.filter(PackageScanner::isInnerClass)
.map(className -> loadClass(loader, className))
.filter(cls -> cls.isAnnotationPresent(annotation))
.collect(Collectors.toSet());

where the isInenrClass and loadClass methods are:

    private static Class<?> loadClass(ClassLoader loader, String className) {
        try {
            return Class.forName(className, false, loader);
        } catch (ClassNotFoundException | LinkageError e) {
            throw new IllegalStateException("Unable to load class discovered during scanning: " + className, e);
        }
    }

    private static boolean isInnerClass(String name) {
        return name.contains("$");
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants