-
Notifications
You must be signed in to change notification settings - Fork 3.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Java Entrypoint #8161
Java Entrypoint #8161
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
package org.logstash; | ||
|
||
import java.io.InputStream; | ||
import java.io.PrintStream; | ||
import java.nio.file.Path; | ||
import java.nio.file.Paths; | ||
import org.apache.logging.log4j.LogManager; | ||
import org.apache.logging.log4j.Logger; | ||
import org.jruby.Ruby; | ||
import org.jruby.RubyException; | ||
import org.jruby.RubyInstanceConfig; | ||
import org.jruby.RubyNumeric; | ||
import org.jruby.exceptions.RaiseException; | ||
import org.jruby.runtime.builtin.IRubyObject; | ||
|
||
/** | ||
* Logstash Main Entrypoint. | ||
*/ | ||
public final class Logstash implements Runnable, AutoCloseable { | ||
|
||
private static final Logger LOGGER = LogManager.getLogger(Logstash.class); | ||
|
||
/** | ||
* Configuration for {@link #ruby}. | ||
*/ | ||
private final RubyInstanceConfig config; | ||
|
||
/** | ||
* JRuby Runtime Environment. | ||
*/ | ||
private final Ruby ruby; | ||
|
||
/** | ||
* Ruby Entrypoint Script. | ||
*/ | ||
private final InputStream script; | ||
|
||
/** | ||
* Main Entrypoint. | ||
* Requires environment {@code "LS_HOME"} to be set to the Logstash root directory. | ||
* @param args Logstash CLI Arguments | ||
*/ | ||
public static void main(final String... args) { | ||
final String lsHome = System.getenv("LS_HOME"); | ||
if (lsHome == null) { | ||
throw new IllegalStateException( | ||
"LS_HOME environment variable must be set. This is likely a bug that should be reported." | ||
); | ||
} | ||
final Path home = Paths.get(lsHome).toAbsolutePath(); | ||
try ( | ||
final Logstash logstash = new Logstash(home, args, System.out, System.err, System.in) | ||
) { | ||
logstash.run(); | ||
} catch (final Throwable t) { | ||
LOGGER.error(t); | ||
System.exit(1); | ||
} | ||
System.exit(0); | ||
} | ||
|
||
/** | ||
* Ctor. | ||
* @param home Logstash Root Directory | ||
* @param args Commandline Arguments | ||
* @param output Output Stream Capturing StdOut | ||
* @param error Output Stream Capturing StdErr | ||
* @param input Input Stream Capturing StdIn | ||
*/ | ||
Logstash(final Path home, final String[] args, final PrintStream output, | ||
final PrintStream error, final InputStream input) { | ||
config = buildConfig(home, args); | ||
config.setOutput(output); | ||
config.setError(error); | ||
config.setInput(input); | ||
script = config.getScriptSource(); | ||
ruby = Ruby.newInstance(config); | ||
} | ||
|
||
@Override | ||
public void run() { | ||
// @todo: Refactor codebase to not rely on global constant for Ruby Runtime | ||
if (RubyUtil.RUBY != ruby) { | ||
throw new IllegalStateException( | ||
"More than one JRuby Runtime detected in the current JVM!" | ||
); | ||
} | ||
try { | ||
Thread.currentThread().setContextClassLoader(ruby.getJRubyClassLoader()); | ||
ruby.runFromMain(script, config.displayedFileName()); | ||
} catch (final RaiseException ex) { | ||
final RubyException rexep = ex.getException(); | ||
if (ruby.getSystemExit().isInstance(rexep)) { | ||
final IRubyObject status = | ||
rexep.callMethod(ruby.getCurrentContext(), "status"); | ||
if (status != null && !status.isNil() && RubyNumeric.fix2int(status) != 0) { | ||
throw new IllegalStateException(ex); | ||
} | ||
} | ||
} | ||
} | ||
|
||
@Override | ||
public void close() throws Exception { | ||
ruby.tearDown(false); | ||
script.close(); | ||
} | ||
|
||
/** | ||
* Sets up the correct {@link RubyInstanceConfig} for a given Logstash installation and set of | ||
* CLI arguments. | ||
* @param home Logstash Root Path | ||
* @param args Commandline Arguments Passed to Logstash | ||
* @return RubyInstanceConfig | ||
*/ | ||
private static RubyInstanceConfig buildConfig(final Path home, final String[] args) { | ||
final String[] arguments = new String[args.length + 2]; | ||
System.arraycopy(args, 0, arguments, 2, args.length); | ||
arguments[0] = safePath(home, "lib", "bootstrap", "environment.rb"); | ||
arguments[1] = safePath(home, "logstash-core", "lib", "logstash", "runner.rb"); | ||
final RubyInstanceConfig config = new RubyInstanceConfig(); | ||
config.processArguments(arguments); | ||
return config; | ||
} | ||
|
||
/** | ||
* Builds the correct path for a file under the given Logstash root and defined by its sub path | ||
* elements relative to the Logstash root. | ||
* Ensures that the file exists and throws an exception of it's missing. | ||
* This is done to avoid hard to interpret errors thrown by JRuby that could result from missing | ||
* Ruby bootstrap scripts. | ||
* @param home Logstash Root Path | ||
* @param subs Path elements relative to {@code home} | ||
* @return Absolute Path a File under the Logstash Root. | ||
*/ | ||
private static String safePath(final Path home, final String... subs) { | ||
Path resolved = home; | ||
for (final String element : subs) { | ||
resolved = resolved.resolve(element); | ||
} | ||
if (!resolved.toFile().exists()) { | ||
throw new IllegalArgumentException(String.format("Missing: %s.", resolved)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we make this more actionable, or would a user never expect to see this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jordansissel jup same here => user should never ever see this except for when there's a bug :) |
||
} | ||
return resolved.toString(); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should still be /bin/sh
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jordansissel
bash
is more portable here imo :( Your code only works withsh
4.x
=>bash
3.x
works fine though => standard OSX and old Linux will break if we go withsh
=>bash
wins, especially since JRuby requiresbash
anyways in the way we currently used to invoke it? :)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, fair. I didn't realize that bin/jruby invoked bash.