support injectable Ruby logger in Java event and related specs

This commit is contained in:
Colin Surprenant 2015-11-24 23:04:53 -05:00
parent d6a9627f54
commit 29ea7f495b
5 changed files with 117 additions and 23 deletions

View file

@ -135,4 +135,54 @@ describe LogStash::Event do
expect(event["tags"]).to eq(["foo"])
end
end
module DummyLogger
def self.warn(message)
# do nothing
end
end
context "logger" do
let(:logger) { double("Logger") }
after(:each) { LogStash::Event.logger = nil }
it "should set logger using a module" do
LogStash::Event.logger = DummyLogger
expect(DummyLogger).to receive(:warn).once
LogStash::Event.new(TIMESTAMP => "invalid timestamp")
end
it "should set logger using a mock" do
LogStash::Event.logger = logger
expect(logger).to receive(:warn)
LogStash::Event.new(TIMESTAMP => "invalid timestamp")
end
it "should unset logger" do
# first set
LogStash::Event.logger = logger
expect(logger).to receive(:warn).once
LogStash::Event.new(TIMESTAMP => "invalid timestamp")
# then unset
LogStash::Event.logger = nil
expect(logger).to receive(:warn).never
# this will produce a log line in stdout by the Java Event
LogStash::Event.new(TIMESTAMP => "ignore this log")
end
it "should warn on parsing errort" do
LogStash::Event.logger = logger
expect(logger).to receive(:warn).once.with(/^Error parsing/)
LogStash::Event.new(TIMESTAMP => "invalid timestamp")
end
it "should warn on invalid timestamp object" do
LogStash::Event.logger = logger
expect(logger).to receive(:warn).once.with(/^Unrecognized/)
LogStash::Event.new(TIMESTAMP => Object.new)
end
end
end

View file

@ -28,9 +28,9 @@ public class Event implements Cloneable, Serializable {
public static final String VERSION = "@version";
public static final String VERSION_ONE = "1";
private static final ObjectMapper mapper = new ObjectMapper();
// TODO: add metadata support
private static final Logger DEFAULT_LOGGER = new StdioLogger();
private transient final ObjectMapper mapper = new ObjectMapper();
private transient static Logger logger = DEFAULT_LOGGER;
public Event()
{
@ -225,18 +225,16 @@ public class Event implements Cloneable, Serializable {
} else if (o instanceof RubySymbol) {
return new Timestamp(((RubySymbol) o).asJavaString());
} else {
// TODO: add logging
//return Timestamp.now();
throw new IllegalArgumentException();
Event.logger.warn("Unrecognized " + TIMESTAMP + " value type=" + o.getClass().toString());
}
} catch (IllegalArgumentException e) {
// TODO: add error logging
tag(TIMESTAMP_FAILURE_TAG);
this.data.put(TIMESTAMP_FAILURE_FIELD, o);
return Timestamp.now();
Event.logger.warn("Error parsing " + TIMESTAMP + " string value=" + o.toString());
}
tag(TIMESTAMP_FAILURE_TAG);
this.data.put(TIMESTAMP_FAILURE_FIELD, o);
return Timestamp.now();
}
public void tag(String tag) {
@ -250,4 +248,8 @@ public class Event implements Cloneable, Serializable {
tags.add(tag);
}
}
public static void setLogger(Logger logger) {
Event.logger = (logger == null) ? DEFAULT_LOGGER : logger;
}
}

View file

@ -0,0 +1,13 @@
package com.logstash;
// minimalist Logger interface to wire a logger callback in the Event class
// for now only warn is defined because this is the only method that's required
// in the Event class.
// TODO: (colin) generalize this
public interface Logger {
// TODO: (colin) complete interface beyond warn when needed
void warn(String message);
}

View file

@ -0,0 +1,10 @@
package com.logstash;
public class StdioLogger implements Logger {
// TODO: (colin) complete implementation beyond warn when needed
public void warn(String message) {
System.out.println(message);
}
}

View file

@ -1,15 +1,10 @@
package com.logstash.ext;
import com.logstash.Event;
import com.logstash.PathCache;
import com.logstash.RubyToJavaConverter;
import com.logstash.Timestamp;
import com.logstash.*;
import org.jruby.*;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyConstant;
import org.jruby.anno.JRubyMethod;
import org.jruby.exceptions.RaiseException;
import org.jruby.java.proxies.MapJavaProxy;
import org.jruby.javasupport.JavaUtil;
import org.jruby.runtime.Arity;
import org.jruby.runtime.ObjectAllocator;
@ -25,13 +20,13 @@ public class JrubyEventExtLibrary implements Library {
public void load(Ruby runtime, boolean wrap) throws IOException {
RubyModule module = runtime.defineModule("LogStash");
RubyClass clazz = runtime.defineClassUnder("Event", runtime.getObject(), new ObjectAllocator() {
public IRubyObject allocate(Ruby runtime, RubyClass rubyClass) {
return new RubyEvent(runtime, rubyClass);
}
}, module);
clazz.setConstant("LOGGER", runtime.getModule("Cabin").getClass("Channel")
.callMethod("get", runtime.getModule("LogStash")));
clazz.setConstant("TIMESTAMP", runtime.newString(Event.TIMESTAMP));
clazz.setConstant("TIMESTAMP_FAILURE_TAG", runtime.newString(Event.TIMESTAMP_FAILURE_TAG));
clazz.setConstant("TIMESTAMP_FAILURE_FIELD", runtime.newString(Event.TIMESTAMP_FAILURE_FIELD));
@ -39,9 +34,24 @@ public class JrubyEventExtLibrary implements Library {
clazz.defineAnnotatedConstants(RubyEvent.class);
}
public static class ProxyLogger implements Logger {
private RubyObject logger;
public ProxyLogger(RubyObject logger) {
this.logger = logger;
}
// TODO: (colin) complete implementation beyond warn when needed
public void warn(String message) {
logger.callMethod("warn", RubyString.newString(logger.getRuntime(), message));
}
}
@JRubyClass(name = "Event", parent = "Object")
public static class RubyEvent extends RubyObject {
private Event event;
private static RubyObject logger;
public RubyEvent(Ruby runtime, RubyClass klass) {
super(runtime, klass);
@ -206,9 +216,11 @@ public class JrubyEventExtLibrary implements Library {
try {
return RubyString.newString(context.runtime, event.sprintf(format.toString()));
} catch (IOException e) {
throw new RaiseException(getRuntime(),
(RubyClass) getRuntime().getModule("LogStash").getClass("Error"),
"timestamp field is missing", true);
throw new RaiseException(
getRuntime(),
(RubyClass) getRuntime().getModule("LogStash").getClass("Error"),
"timestamp field is missing", true
);
}
}
@ -274,5 +286,12 @@ public class JrubyEventExtLibrary implements Library {
public IRubyObject ruby_timestamp(ThreadContext context) throws IOException {
return new JrubyTimestampExtLibrary.RubyTimestamp(context.getRuntime(), this.event.getTimestamp());
}
@JRubyMethod(name = "logger=", required = 1, meta = true)
public static IRubyObject ruby_set_logger(ThreadContext context, IRubyObject recv, IRubyObject value)
{
Event.setLogger(value.isNil() ? null : new ProxyLogger((RubyObject)value));
return value;
}
}
}