logstash-core-event-java initial impl, relates to #4191

wip initial Accessors, Event, EventImpl, Path, Timestamp and PathTest

wip

wip

FieldReference and Accessors implementation

rename targetCache to lut and set it protected

initial Accessors tests

todo comment

more tests

Timestamp implementation

Timestamp tests

fix method name

add Long constructor

event initialization, timestamp handling and json serialization

add <> type information

custom json serializer for Timestamp

remove toJson test

initial Event test

more tests

comments

debug traces

initial jruby Event wrapper and specs

added PathCache

implemented includes

added clone

wrap all Event methods

Rakefile to build and jar

missing getters and implement overwrite

support Date conversion

proper cast and coercion

replace Ruby Event with Java Event

test for field reference setter type coercion

disable specs

timestap setter should also set in map, accept more timestamp types

pre cache timestamp and expose isTimestamp

constructor from DateTime

expose proper Ruby Timestamp object

Ruby Timestamp basic specs

also load JRuby Timestamp

transpose Java<->Ruby Timestamp

fix timestamp specs

new jar

cleanup object construction
This commit is contained in:
Colin Surprenant 2015-05-20 19:00:24 -04:00
parent 18d4f26c81
commit e5fb1d2977
25 changed files with 1481 additions and 257 deletions

45
lib/jruby_event/Rakefile Normal file
View file

@ -0,0 +1,45 @@
begin
require 'ant'
rescue
puts("error: unable to load Ant, make sure Ant is installed, in your PATH and $ANT_HOME is defined properly")
puts("\nerror details:\n#{$!}")
exit(1)
end
BASEDIR = File.expand_path("../../../", __FILE__)
TARGET = File.join(BASEDIR, "out/production/main/")
SRC = File.join(BASEDIR, "src/main/java")
JAR = File.join(BASEDIR, "lib/jruby_event/jruby_event.jar")
task :setup do
ant.mkdir 'dir' => TARGET
ant.path 'id' => 'classpath' do
fileset 'dir' => TARGET
end
end
desc "compile Java classes"
task :build => [:setup] do |t, args|
require 'jruby/jrubyc'
ant.javac(
:srcdir => SRC,
:destdir => TARGET,
:classpathref => "classpath",
:debug => true,
:includeantruntime => "no",
:verbose => false,
:listfiles => true,
:source => "1.7",
:target => "1.7",
) {}
end
task :setup_jar do
ant.delete 'file' => JAR
end
desc "build the jar"
task :jar => [:setup_jar, :build] do
ant.jar :basedir => TARGET, :destfile => JAR, :includes => "**/*.class"
end

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,19 @@
# encoding: utf-8
require "java"
# local dev setup
classes_dir = File.expand_path("../../../out/production/main", __FILE__)
if File.directory?(classes_dir)
# if in local dev setup, add to classpath
$CLASSPATH << classes_dir unless $CLASSPATH.include?(classes_dir)
else
# otherwise use included jar
require_relative "jruby_event.jar"
end
require_relative "jackson-core-asl-1.9.13.jar"
require_relative "jackson-mapper-asl-1.9.13.jar"
require "jruby_event_ext"
require "jruby_timestamp_ext"

View file

@ -1,10 +1,12 @@
# encoding: utf-8
require "time"
require "date"
require "cabin"
require "jruby_event/jruby_event"
# require "time"
# require "date"
# require "cabin"
require "logstash/namespace"
require "logstash/util/accessors"
require "logstash/timestamp"
# require "logstash/util/accessors"
# require "logstash/timestamp"
require "logstash/json"
require "logstash/string_interpolation"
@ -23,253 +25,7 @@ module LogStash
SHUTDOWN = LogStash::ShutdownEvent.new
end
# the logstash event object.
#
# An event is simply a tuple of (timestamp, data).
# The 'timestamp' is an ISO8601 timestamp. Data is anything - any message,
# context, references, etc that are relevant to this event.
#
# Internally, this is represented as a hash with only two guaranteed fields.
#
# * "@timestamp" - an ISO8601 timestamp representing the time the event
# occurred at.
# * "@version" - the version of the schema. Currently "1"
#
# They are prefixed with an "@" symbol to avoid clashing with your
# own custom fields.
#
# When serialized, this is represented in JSON. For example:
#
# {
# "@timestamp": "2013-02-09T20:39:26.234Z",
# "@version": "1",
# message: "hello world"
# }
class LogStash::Event
class DeprecatedMethod < StandardError; end
CHAR_PLUS = "+"
TIMESTAMP = "@timestamp"
VERSION = "@version"
VERSION_ONE = "1"
TIMESTAMP_FAILURE_TAG = "_timestampparsefailure"
TIMESTAMP_FAILURE_FIELD = "_@timestamp"
end
METADATA = "@metadata".freeze
METADATA_BRACKETS = "[#{METADATA}]".freeze
# Floats outside of these upper and lower bounds are forcibly converted
# to scientific notation by Float#to_s
MIN_FLOAT_BEFORE_SCI_NOT = 0.0001
MAX_FLOAT_BEFORE_SCI_NOT = 1000000000000000.0
LOGGER = Cabin::Channel.get(LogStash)
public
def initialize(data = {})
@cancelled = false
@data = data
@accessors = LogStash::Util::Accessors.new(data)
@data[VERSION] ||= VERSION_ONE
ts = @data[TIMESTAMP]
@data[TIMESTAMP] = ts ? init_timestamp(ts) : LogStash::Timestamp.now
@metadata = @data.delete(METADATA) || {}
@metadata_accessors = LogStash::Util::Accessors.new(@metadata)
end # def initialize
public
def cancel
@cancelled = true
end # def cancel
public
def uncancel
@cancelled = false
end # def uncancel
public
def cancelled?
return @cancelled
end # def cancelled?
# Create a deep-ish copy of this event.
public
def clone
copy = {}
@data.each do |k,v|
# TODO(sissel): Recurse if this is a hash/array?
copy[k] = begin v.clone rescue v end
end
return self.class.new(copy)
end # def clone
public
def to_s
self.sprintf("#{timestamp.to_iso8601} %{host} %{message}")
end # def to_s
public
def timestamp; return @data[TIMESTAMP]; end # def timestamp
def timestamp=(val); return @data[TIMESTAMP] = val; end # def timestamp=
def unix_timestamp
raise DeprecatedMethod
end # def unix_timestamp
def ruby_timestamp
raise DeprecatedMethod
end # def unix_timestamp
public
def [](fieldref)
if fieldref.start_with?(METADATA_BRACKETS)
@metadata_accessors.get(fieldref[METADATA_BRACKETS.length .. -1])
elsif fieldref == METADATA
@metadata
else
@accessors.get(fieldref)
end
end # def []
public
def []=(fieldref, value)
if fieldref == TIMESTAMP && !value.is_a?(LogStash::Timestamp)
raise TypeError, "The field '@timestamp' must be a (LogStash::Timestamp, not a #{value.class} (#{value})"
end
if fieldref.start_with?(METADATA_BRACKETS)
@metadata_accessors.set(fieldref[METADATA_BRACKETS.length .. -1], value)
elsif fieldref == METADATA
@metadata = value
@metadata_accessors = LogStash::Util::Accessors.new(@metadata)
else
@accessors.set(fieldref, value)
end
end # def []=
public
def fields
raise DeprecatedMethod
end
public
def to_json(*args)
# ignore arguments to respect accepted to_json method signature
LogStash::Json.dump(@data)
end # def to_json
public
def to_hash
@data
end # def to_hash
public
def overwrite(event)
# pickup new event @data and also pickup @accessors
# otherwise it will be pointing on previous data
@data = event.instance_variable_get(:@data)
@accessors = event.instance_variable_get(:@accessors)
#convert timestamp if it is a String
if @data[TIMESTAMP].is_a?(String)
@data[TIMESTAMP] = LogStash::Timestamp.parse_iso8601(@data[TIMESTAMP])
end
end
public
def include?(fieldref)
if fieldref.start_with?(METADATA_BRACKETS)
@metadata_accessors.include?(fieldref[METADATA_BRACKETS.length .. -1])
elsif fieldref == METADATA
true
else
@accessors.include?(fieldref)
end
end # def include?
# Append an event to this one.
public
def append(event)
# non-destructively merge that event with ourselves.
# no need to reset @accessors here because merging will not disrupt any existing field paths
# and if new ones are created they will be picked up.
LogStash::Util.hash_merge(@data, event.to_hash)
end # append
# Remove a field or field reference. Returns the value of that field when
# deleted
public
def remove(fieldref)
@accessors.del(fieldref)
end # def remove
# sprintf. This could use a better method name.
# The idea is to take an event and convert it to a string based on
# any format values, delimited by %{foo} where 'foo' is a field or
# metadata member.
#
# For example, if the event has type == "foo" and host == "bar"
# then this string:
# "type is %{type} and source is %{host}"
# will return
# "type is foo and source is bar"
#
# If a %{name} value is an array, then we will join by ','
# If a %{name} value does not exist, then no substitution occurs.
public
def sprintf(format)
LogStash::StringInterpolation.evaluate(self, format)
end
def tag(value)
# Generalize this method for more usability
self["tags"] ||= []
self["tags"] << value unless self["tags"].include?(value)
end
private
def init_timestamp(o)
begin
timestamp = LogStash::Timestamp.coerce(o)
return timestamp if timestamp
LOGGER.warn("Unrecognized #{TIMESTAMP} value, setting current time to #{TIMESTAMP}, original in #{TIMESTAMP_FAILURE_FIELD}field", :value => o.inspect)
rescue LogStash::TimestampParserError => e
LOGGER.warn("Error parsing #{TIMESTAMP} string, setting current time to #{TIMESTAMP}, original in #{TIMESTAMP_FAILURE_FIELD} field", :value => o.inspect, :exception => e.message)
end
@data["tags"] ||= []
@data["tags"] << TIMESTAMP_FAILURE_TAG unless @data["tags"].include?(TIMESTAMP_FAILURE_TAG)
@data[TIMESTAMP_FAILURE_FIELD] = o
LogStash::Timestamp.now
end
public
def to_hash_with_metadata
@metadata.empty? ? to_hash : to_hash.merge(METADATA => @metadata)
end
public
def to_json_with_metadata(*args)
# ignore arguments to respect accepted to_json method signature
LogStash::Json.dump(to_hash_with_metadata)
end # def to_json
def self.validate_value(value)
case value
when String
raise("expected UTF-8 encoding for value=#{value}, encoding=#{value.encoding.inspect}") unless value.encoding == Encoding::UTF_8
raise("invalid UTF-8 encoding for value=#{value}, encoding=#{value.encoding.inspect}") unless value.valid_encoding?
value
when Array
value.each{|v| validate_value(v)} # don't map, return original object
value
else
value
end
end
end # class LogStash::Event

View file

@ -317,16 +317,16 @@ describe LogStash::Event do
it "should coerce timestamp" do
t = Time.iso8601("2014-06-12T00:12:17.114Z")
expect(LogStash::Timestamp).to receive(:coerce).exactly(3).times.and_call_original
# expect(LogStash::Timestamp).to receive(:coerce).exactly(3).times.and_call_original
expect(LogStash::Event.new("@timestamp" => t).timestamp.to_i).to eq(t.to_i)
expect(LogStash::Event.new("@timestamp" => LogStash::Timestamp.new(t)).timestamp.to_i).to eq(t.to_i)
expect(LogStash::Event.new("@timestamp" => "2014-06-12T00:12:17.114Z").timestamp.to_i).to eq(t.to_i)
end
it "should assign current time when no timestamp" do
ts = LogStash::Timestamp.now
expect(LogStash::Timestamp).to receive(:now).and_return(ts)
expect(LogStash::Event.new({}).timestamp.to_i).to eq(ts.to_i)
# ts = LogStash::Timestamp.now
# expect(LogStash::Timestamp).to receive(:now).and_return(ts)
expect(LogStash::Event.new({}).timestamp.to_i).to be_within(1).of Time.now.to_i
end
it "should tag and warn for invalid value" do

View file

@ -0,0 +1,111 @@
$LOAD_PATH << File.expand_path("../../../lib", __FILE__)
require "jruby_event/jruby_event"
TIMESTAMP = "@timestamp"
describe LogStash::Event do
context "to_json" do
it "should serialize snmple values" do
e = LogStash::Event.new({"foo" => "bar", "bar" => 1, "baz" => 1.0, TIMESTAMP => "2015-05-28T23:02:05.350Z"})
expect(e.to_json).to eq("{\"foo\":\"bar\",\"bar\":1,\"baz\":1.0,\"@timestamp\":\"2015-05-28T23:02:05.350Z\",\"@version\":\"1\"}")
end
it "should serialize deep hash values" do
e = LogStash::Event.new({"foo" => {"bar" => 1, "baz" => 1.0, "biz" => "boz"}, TIMESTAMP => "2015-05-28T23:02:05.350Z"})
expect(e.to_json).to eq("{\"foo\":{\"bar\":1,\"baz\":1.0,\"biz\":\"boz\"},\"@timestamp\":\"2015-05-28T23:02:05.350Z\",\"@version\":\"1\"}")
end
it "should serialize deep array values" do
e = LogStash::Event.new({"foo" => ["bar", 1, 1.0], TIMESTAMP => "2015-05-28T23:02:05.350Z"})
expect(e.to_json).to eq("{\"foo\":[\"bar\",1,1.0],\"@timestamp\":\"2015-05-28T23:02:05.350Z\",\"@version\":\"1\"}")
end
it "should serialize deep hash from field reference assignments" do
e = LogStash::Event.new({TIMESTAMP => "2015-05-28T23:02:05.350Z"})
e["foo"] = "bar"
e["bar"] = 1
e["baz"] = 1.0
e["[fancy][pants][socks]"] = "shoes"
expect(e.to_json).to eq("{\"@timestamp\":\"2015-05-28T23:02:05.350Z\",\"@version\":\"1\",\"foo\":\"bar\",\"bar\":1,\"baz\":1.0,\"fancy\":{\"pants\":{\"socks\":\"shoes\"}}}")
end
end
context "[]" do
it "should get simple values" do
e = LogStash::Event.new({"foo" => "bar", "bar" => 1, "baz" => 1.0, TIMESTAMP => "2015-05-28T23:02:05.350Z"})
expect(e["foo"]).to eq("bar")
expect(e["[foo]"]).to eq("bar")
expect(e["bar"]).to eq(1)
expect(e["[bar]"]).to eq(1)
expect(e["baz"]).to eq(1.0)
expect(e["[baz]"]).to eq(1.0)
expect(e[TIMESTAMP].to_s).to eq("2015-05-28T23:02:05.350Z")
expect(e["[#{TIMESTAMP}]"].to_s).to eq("2015-05-28T23:02:05.350Z")
end
it "should get deep hash values" do
e = LogStash::Event.new({"foo" => {"bar" => 1, "baz" => 1.0}})
expect(e["[foo][bar]"]).to eq(1)
expect(e["[foo][baz]"]).to eq(1.0)
end
it "should get deep array values" do
e = LogStash::Event.new({"foo" => ["bar", 1, 1.0]})
expect(e["[foo][0]"]).to eq("bar")
expect(e["[foo][1]"]).to eq(1)
expect(e["[foo][2]"]).to eq(1.0)
expect(e["[foo][3]"]).to be_nil
end
end
context "[]=" do
it "should set simple values" do
e = LogStash::Event.new()
expect(e["foo"] = "bar").to eq("bar")
expect(e["foo"]).to eq("bar")
e = LogStash::Event.new({"foo" => "test"})
expect(e["foo"] = "bar").to eq("bar")
expect(e["foo"]).to eq("bar")
end
it "should set deep hash values" do
e = LogStash::Event.new()
expect(e["[foo][bar]"] = "baz").to eq("baz")
expect(e["[foo][bar]"]).to eq("baz")
expect(e["[foo][baz]"]).to be_nil
end
it "should set deep array values" do
e = LogStash::Event.new()
expect(e["[foo][0]"] = "bar").to eq("bar")
expect(e["[foo][0]"]).to eq("bar")
expect(e["[foo][1]"] = 1).to eq(1)
expect(e["[foo][1]"]).to eq(1)
expect(e["[foo][2]"] = 1.0 ).to eq(1.0)
expect(e["[foo][2]"]).to eq(1.0)
expect(e["[foo][3]"]).to be_nil
end
end
context "timestamp" do
it "getters should present a Ruby LogStash::Timestamp" do
e = LogStash::Event.new()
expect(e.timestamp.class).to eq(LogStash::Timestamp)
expect(e[TIMESTAMP].class).to eq(LogStash::Timestamp)
end
it "to_hash should inject a Ruby LogStash::Timestamp" do
e = LogStash::Event.new()
expect(e.to_java).to be_kind_of(Java::ComLogstash::Event)
expect(e.to_java.get_field(TIMESTAMP)).to be_kind_of(Java::ComLogstash::Timestamp)
expect(e.to_hash[TIMESTAMP]).to be_kind_of(LogStash::Timestamp)
# now make sure the original map was not touched
expect(e.to_java.get_field(TIMESTAMP)).to be_kind_of(Java::ComLogstash::Timestamp)
end
end
end
``

View file

@ -0,0 +1,25 @@
$LOAD_PATH << File.expand_path("../../../lib", __FILE__)
require "jruby_event/jruby_event"
describe LogStash::Timestamp do
context "constructors" do
it "should work" do
t = LogStash::Timestamp.new
expect(t.time.to_i).to be_within(1).of Time.now.to_i
t = LogStash::Timestamp.now
expect(t.time.to_i).to be_within(1).of Time.now.to_i
now = Time.now.utc
t = LogStash::Timestamp.new(now)
expect(t.time).to eq(now)
t = LogStash::Timestamp.at(now.to_i)
expect(t.time.to_i).to eq(now.to_i)
end
end
end
``

View file

@ -2,7 +2,7 @@
require "spec_helper"
require "logstash/util/accessors"
describe LogStash::Util::Accessors, :if => true do
describe LogStash::Util::Accessors, :if => true dogit ad
context "using simple field" do

View file

@ -0,0 +1,14 @@
import com.logstash.ext.JrubyEventExtLibrary;
import org.jruby.Ruby;
import org.jruby.runtime.load.BasicLibraryService;
import java.io.IOException;
public class JrubyEventExtService implements BasicLibraryService {
public boolean basicLoad(final Ruby runtime)
throws IOException
{
new JrubyEventExtLibrary().load(runtime, false);
return true;
}
}

View file

@ -0,0 +1,15 @@
import com.logstash.ext.JrubyEventExtLibrary;
import com.logstash.ext.JrubyTimestampExtLibrary;
import org.jruby.Ruby;
import org.jruby.runtime.load.BasicLibraryService;
import java.io.IOException;
public class JrubyTimestampExtService implements BasicLibraryService {
public boolean basicLoad(final Ruby runtime)
throws IOException
{
new JrubyTimestampExtLibrary().load(runtime, false);
return true;
}
}

View file

@ -0,0 +1,124 @@
package com.logstash;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
public class Accessors {
private Map<String, Object> data;
protected Map<String, Object> lut;
public Accessors(Map<String, Object> data) {
this.data = data;
this.lut = new HashMap<>(); // reference -> target LUT
}
public Object get(String reference) {
FieldReference field = PathCache.getInstance().cache(reference);
Object target = findTarget(field);
return (target == null) ? null : fetch(target, field.getKey());
}
public Object set(String reference, Object value) {
FieldReference field = PathCache.getInstance().cache(reference);
Object target = findCreateTarget(field);
return store(target, field.getKey(), value);
}
public Object del(String reference) {
// TODO: implement
return null;
}
public boolean includes(String reference) {
FieldReference field = PathCache.getInstance().cache(reference);
Object target = findTarget(field);
return (target == null) ? false : (fetch(target, field.getKey()) != null);
}
private Object findTarget(FieldReference field) {
Object target;
if ((target = this.lut.get(field.getReference())) != null) {
return target;
}
target = this.data;
for (String key : field.getPath()) {
target = fetch(target, key);
if (target == null) {
return null;
}
}
this.lut.put(field.getReference(), target);
return target;
}
private Object findCreateTarget(FieldReference field) {
Object target;
if ((target = this.lut.get(field.getReference())) != null) {
return target;
}
target = this.data;
for (String key : field.getPath()) {
Object result = fetch(target, key);
if (result == null) {
result = new HashMap<String, Object>();
if (target instanceof Map) {
((Map<String, Object>)target).put(key, result);
} else if (target instanceof List) {
int i = Integer.parseInt(key);
// TODO: what about index out of bound?
((List<Object>)target).set(i, result);
} else {
throw new ClassCastException("expecting List or Map");
}
}
target = result;
}
this.lut.put(field.getReference(), target);
return target;
}
private Object fetch(Object target, String key) {
if (target instanceof Map) {
Object result = ((Map<String, Object>) target).get(key);
// if (result != null) {
// System.out.println("fetch class=" + result.getClass().getName() + ", toString=" + result.toString());
// }
return result;
} else if (target instanceof List) {
int i = Integer.parseInt(key);
if (i < 0 || i >= ((List) target).size()) {
return null;
}
Object result = ((List<Object>) target).get(i);
// if (result != null) {
// System.out.println("fetch class=" + result.getClass().getName() + ", toString=" + result.toString());
// }
return result;
} else {
throw new ClassCastException("expecting List or Map");
}
}
private Object store(Object target, String key, Object value) {
if (target instanceof Map) {
((Map<String, Object>) target).put(key, value);
} else if (target instanceof List) {
int i = Integer.parseInt(key);
// TODO: what about index out of bound?
((List<Object>) target).set(i, value);
} else {
throw new ClassCastException("expecting List or Map");
}
return value;
}
}

View file

@ -0,0 +1,46 @@
package com.logstash;
import org.codehaus.jackson.JsonGenerationException;
import java.io.IOException;
import java.util.Map;
public interface Event {
String toString();
void cancel();
void uncancel();
boolean isCancelled();
Event clone();
Map<String, Object> getData();
Accessors getAccessors();
Timestamp getTimestamp();
void setTimestamp(Timestamp t);
Object getField(String reference);
void setField(String reference, Object value);
boolean includes(String reference);
Object remove(String reference);
String toJson() throws IOException;
// TODO: see if we need that here or just as a to_hash in the JRuby layer
Map toMap();
Event overwrite(Event e);
Event append(Event e);
String sprintf(String s);
}

View file

@ -0,0 +1,191 @@
package com.logstash;
import com.logstash.ext.JrubyTimestampExtLibrary;
import org.codehaus.jackson.map.ObjectMapper;
import org.joda.time.DateTime;
import java.io.IOException;
import java.io.Serializable;
import java.util.*;
public class EventImpl implements Event, Cloneable, Serializable {
private boolean cancelled;
private Map<String, Object> data;
private Timestamp timestamp;
private Accessors accessors;
private static final String TIMESTAMP = "@timestamp";
private static final String TIMESTAMP_FAILURE_TAG = "_timestampparsefailure";
private static final String TIMESTAMP_FAILURE_FIELD = "_@timestamp";
private static final String VERSION = "@version";
private static final String VERSION_ONE = "1";
private static final ObjectMapper mapper = new ObjectMapper();
// TODO: add metadata support
public EventImpl()
{
this.data = new HashMap<String, Object>();
this.data.put(VERSION, VERSION_ONE);
this.cancelled = false;
this.timestamp = new Timestamp();
this.data.put(TIMESTAMP, this.timestamp);
this.accessors = new Accessors(this.data);
}
public EventImpl(Map data)
{
this.data = data;
this.data.putIfAbsent(VERSION, VERSION_ONE);
this.cancelled = false;
this.timestamp = initTimestamp(data.get(TIMESTAMP));
this.data.put(TIMESTAMP, this.timestamp);
this.accessors = new Accessors(this.data);
}
@Override
public Map<String, Object> getData() {
return this.data;
}
@Override
public Accessors getAccessors() {
return this.accessors;
}
@Override
public void cancel() {
this.cancelled = true;
}
@Override
public void uncancel() {
this.cancelled = false;
}
@Override
public boolean isCancelled() {
return this.cancelled;
}
@Override
public Timestamp getTimestamp() {
return this.timestamp;
}
@Override
public void setTimestamp(Timestamp t) {
this.timestamp = t;
this.data.put(TIMESTAMP, this.timestamp);
}
@Override
public Object getField(String reference) {
// TODO: add metadata support
return this.accessors.get(reference);
}
@Override
public void setField(String reference, Object value) {
// TODO: add metadata support
this.accessors.set(reference, value);
}
@Override
public boolean includes(String reference) {
// TODO: add metadata support
return this.accessors.includes(reference);
}
@Override
public String toJson() throws IOException {
return mapper.writeValueAsString((Map<String, Object>)this.data);
}
@Override
public Map toMap() {
return this.data;
}
@Override
public Event overwrite(Event e) {
this.data = e.getData();
this.accessors = e.getAccessors();
this.cancelled = e.isCancelled();
this.timestamp = e.getTimestamp();
return this;
}
@Override
public Event append(Event e) {
// TODO: implement
throw new UnsupportedOperationException("append() not yet implemented");
}
@Override
public Object remove(String path) {
return this.accessors.del(path);
}
@Override
public String sprintf(String s) {
// TODO: implement sprintf
return s;
}
public Event clone() {
throw new UnsupportedOperationException("clone() not yet implemented");
}
public String toString() {
// TODO: until we have sprintf
String host = (String)this.data.getOrDefault("host", "%{host}");
String message = (String)this.data.getOrDefault("message", "%{message}");
return getTimestamp().toIso8601() + " " + host + " " + message;
}
private Timestamp initTimestamp(Object o) {
try {
if (o == null) {
// most frequent
return new Timestamp();
} else if (o instanceof String) {
// second most frequent
return new Timestamp((String) o);
} else if (o instanceof JrubyTimestampExtLibrary.RubyTimestamp) {
return new Timestamp(((JrubyTimestampExtLibrary.RubyTimestamp) o).getTimestamp());
} else if (o instanceof Timestamp) {
return new Timestamp((Timestamp) o);
} else if (o instanceof Long) {
return new Timestamp((Long) o);
} else if (o instanceof DateTime) {
return new Timestamp((DateTime) o);
} else if (o instanceof Date) {
return new Timestamp((Date) o);
} else {
// TODO: add logging
return new Timestamp();
}
} catch (IllegalArgumentException e) {
// TODO: add error logging
List<Object> tags = (List<Object>) this.data.get("tags");
if (tags == null) {
tags = new ArrayList<>();
this.data.put("tags", tags);
}
if (!tags.contains(TIMESTAMP_FAILURE_TAG)) {
tags.add(TIMESTAMP_FAILURE_TAG);
}
this.data.put(TIMESTAMP_FAILURE_FIELD, o.toString());
return new Timestamp();
}
}
}

View file

@ -0,0 +1,40 @@
package com.logstash;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
// TODO: implement thread-safe path cache singleton to avoid parsing
public class FieldReference {
private List<String> path;
private String key;
private String reference;
private static List<String> EMPTY_STRINGS = new ArrayList(Arrays.asList(new String[]{""}));
public FieldReference(List<String> path, String key, String reference) {
this.path = path;
this.key = key;
this.reference = reference;
}
public List<String> getPath() {
return path;
}
public String getKey() {
return key;
}
public String getReference() {
return reference;
}
public static FieldReference parse(String reference) {
List<String> path = new ArrayList(Arrays.asList(reference.split("[\\[\\]]")));
path.removeAll(EMPTY_STRINGS);
String key = path.remove(path.size() - 1);
return new FieldReference(path, key, reference);
}
}

View file

@ -0,0 +1,47 @@
package com.logstash;
import java.util.concurrent.ConcurrentHashMap;
public class PathCache {
private static PathCache instance = null;
private static ConcurrentHashMap<String, FieldReference> cache = new ConcurrentHashMap<>();
private FieldReference timestamp;
// TODO: dry with Event
public static final String TIMESTAMP = "@timestamp";
public static final String BRACKETS_TIMESTAMP = "[" + TIMESTAMP + "]";
protected PathCache() {
// inject @timestamp
this.timestamp = cache(TIMESTAMP);
cache(BRACKETS_TIMESTAMP, this.timestamp);
}
public static PathCache getInstance() {
if (instance == null) {
instance = new PathCache();
}
return instance;
}
public boolean isTimestamp(String reference) {
return (cache(reference) == this.timestamp);
}
public FieldReference cache(String reference) {
// atomicity between the get and put is not important
FieldReference result = cache.get(reference);
if (result == null) {
result = FieldReference.parse(reference);
cache.put(reference, result);
}
return result;
}
public FieldReference cache(String reference, FieldReference field) {
cache.put(reference, field);
return field;
}
}

View file

@ -0,0 +1,64 @@
package com.logstash;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
import org.jruby.Ruby;
import org.jruby.RubyString;
import java.util.Date;
@JsonSerialize(using = TimestampSerializer.class)
public class Timestamp {
private DateTime time;
// TODO: is this DateTimeFormatter thread safe?
private static DateTimeFormatter iso8601Formatter = ISODateTimeFormat.dateTime();
public Timestamp() {
this.time = new DateTime(DateTimeZone.UTC);
}
public Timestamp(String iso8601) {
this.time = ISODateTimeFormat.dateTimeParser().parseDateTime(iso8601).toDateTime(DateTimeZone.UTC);
}
public Timestamp(Timestamp t) {
this.time = t.getTime();
}
public Timestamp(long epoch_milliseconds) {
this.time = new DateTime(epoch_milliseconds, DateTimeZone.UTC);
}
public Timestamp(Long epoch_milliseconds) {
this.time = new DateTime(epoch_milliseconds, DateTimeZone.UTC);
}
public Timestamp(Date date) {
this.time = new DateTime(date, DateTimeZone.UTC);
}
public Timestamp(DateTime date) {
this.time = date.toDateTime(DateTimeZone.UTC);
}
public DateTime getTime() {
return time;
}
public static Timestamp now() {
return new Timestamp();
}
public String toIso8601() {
return this.iso8601Formatter.print(this.time);
}
public String toString() {
return toIso8601();
}
}

View file

@ -0,0 +1,17 @@
package com.logstash;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.map.JsonSerializer;
import org.codehaus.jackson.map.SerializerProvider;
import java.io.IOException;
public class TimestampSerializer extends JsonSerializer<Timestamp> {
@Override
public void serialize(Timestamp value, JsonGenerator jgen, SerializerProvider provider)
throws IOException
{
jgen.writeString(value.toIso8601());
}
}

View file

@ -0,0 +1,237 @@
package com.logstash.ext;
import com.logstash.Event;
import com.logstash.EventImpl;
import com.logstash.PathCache;
import com.logstash.Timestamp;
import org.jruby.*;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.java.proxies.MapJavaProxy;
import org.jruby.javasupport.JavaUtil;
import org.jruby.runtime.Arity;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.load.Library;
import java.io.IOException;
import java.nio.file.Path;
import java.util.*;
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.defineAnnotatedMethods(RubyEvent.class);
}
@JRubyClass(name = "Event", parent = "Object")
public static class RubyEvent extends RubyObject {
private Event event;
public RubyEvent(Ruby runtime, RubyClass klass) {
super(runtime, klass);
}
public RubyEvent(Ruby runtime) {
this(runtime, runtime.getModule("LogStash").getClass("Timestamp"));
}
public RubyEvent(Ruby runtime, Event event) {
this(runtime);
this.event = event;
}
public static RubyEvent newRubyEvent(Ruby runtime, Event event) {
return new RubyEvent(runtime, event);
}
public Event getEvent() {
return event;
}
public void setEvent(Event event) {
this.event = event;
}
// def initialize(data = {})
@JRubyMethod(name = "initialize", optional = 1)
public IRubyObject ruby_initialize(ThreadContext context, IRubyObject[] args)
{
args = Arity.scanArgs(context.runtime, args, 0, 1);
IRubyObject data = args[0];
if (data.isNil()) {
this.event = new EventImpl();
} else if (data instanceof Map) {
this.event = new EventImpl((Map)data);
} else if (Map.class.isAssignableFrom(data.getJavaClass())) {
this.event = new EventImpl((Map)data.toJava(Map.class));
} else {
throw context.runtime.newTypeError("wrong argument type " + data.getMetaClass() + " (expected Hash)");
}
return context.nil;
}
@JRubyMethod(name = "[]", required = 1)
public IRubyObject ruby_get_field(ThreadContext context, RubyString reference)
{
String r = reference.asJavaString();
if (PathCache.getInstance().isTimestamp(r)) {
return JrubyTimestampExtLibrary.RubyTimestamp.newRubyTimestamp(context.runtime, this.event.getTimestamp());
} else {
Object value = this.event.getField(r);
if (value instanceof Timestamp) {
return JrubyTimestampExtLibrary.RubyTimestamp.newRubyTimestamp(context.runtime, (Timestamp)value);
} else {
return JavaUtil.convertJavaToRuby(context.runtime, value);
}
}
}
@JRubyMethod(name = "[]=", required = 2)
public IRubyObject ruby_set_field(ThreadContext context, RubyString reference, IRubyObject value)
{
String r = reference.asJavaString();
if (PathCache.getInstance().isTimestamp(r)) {
if (!(value instanceof JrubyTimestampExtLibrary.RubyTimestamp)) {
throw context.runtime.newTypeError("wrong argument type " + value.getMetaClass() + " (expected LogStash::Timestamp)");
}
this.event.setTimestamp(((JrubyTimestampExtLibrary.RubyTimestamp)value).getTimestamp());
} else {
if (value instanceof RubyString) {
this.event.setField(r, ((RubyString) value).asJavaString());
} else if (value instanceof RubyInteger) {
this.event.setField(r, ((RubyInteger) value).getLongValue());
} else if (value instanceof RubyFloat) {
this.event.setField(r, ((RubyFloat) value).getDoubleValue());
} else if (value instanceof JrubyTimestampExtLibrary.RubyTimestamp) {
// RubyTimestamp could be assigned in another field thant @timestamp
this.event.setField(r, ((JrubyTimestampExtLibrary.RubyTimestamp) value).getTimestamp());
}
}
return value;
}
@JRubyMethod(name = "cancel")
public IRubyObject ruby_cancel(ThreadContext context)
{
this.event.cancel();
return RubyBoolean.createTrueClass(context.runtime);
}
@JRubyMethod(name = "uncancel")
public IRubyObject ruby_uncancel(ThreadContext context)
{
this.event.uncancel();
return RubyBoolean.createFalseClass(context.runtime);
}
@JRubyMethod(name = "cancelled?")
public IRubyObject ruby_cancelled(ThreadContext context)
{
return RubyBoolean.newBoolean(context.runtime, this.event.isCancelled());
}
@JRubyMethod(name = "timestamp")
public IRubyObject ruby_get_timestamp(ThreadContext context)
{
return JrubyTimestampExtLibrary.RubyTimestamp.newRubyTimestamp(context.runtime, this.event.getTimestamp());
}
@JRubyMethod(name = "timestamp=", required = 1)
public IRubyObject ruby_set_timestamp(ThreadContext context, IRubyObject timestamp)
{
if (!(timestamp instanceof JrubyTimestampExtLibrary.RubyTimestamp)) {
throw context.runtime.newTypeError("wrong argument type " + timestamp.getMetaClass() + " (expected LogStash::Timestamp)");
}
this.event.setTimestamp(((JrubyTimestampExtLibrary.RubyTimestamp)timestamp).getTimestamp());
return timestamp;
}
@JRubyMethod(name = "include?", required = 1)
public IRubyObject ruby_includes(ThreadContext context, RubyString reference)
{
return RubyBoolean.newBoolean(context.runtime, this.event.includes(reference.asJavaString()));
}
@JRubyMethod(name = "remove", required = 1)
public IRubyObject ruby_remove(ThreadContext context, RubyString reference)
{
return JavaUtil.convertJavaToRuby(context.runtime, this.event.remove(reference.asJavaString()));
}
@JRubyMethod(name = "clone")
public IRubyObject ruby_clone(ThreadContext context)
{
return RubyEvent.newRubyEvent(context.runtime, this.event.clone());
}
@JRubyMethod(name = "overwrite", required = 1)
public IRubyObject ruby_overwrite(ThreadContext context, IRubyObject value)
{
if (!(value instanceof RubyEvent)) {
throw context.runtime.newTypeError("wrong argument type " + value.getMetaClass() + " (expected LogStash::Event)");
}
return RubyEvent.newRubyEvent(context.runtime, this.event.overwrite(((RubyEvent) value).event));
}
@JRubyMethod(name = "append", required = 1)
public IRubyObject ruby_append(ThreadContext context, IRubyObject value)
{
if (!(value instanceof RubyEvent)) {
throw context.runtime.newTypeError("wrong argument type " + value.getMetaClass() + " (expected LogStash::Event)");
}
return RubyEvent.newRubyEvent(context.runtime, this.event.append(((RubyEvent)value).event));
}
@JRubyMethod(name = "sprintf", required = 1)
public IRubyObject ruby_sprintf(ThreadContext context, IRubyObject format)
{
return RubyString.newString(context.runtime, event.sprintf(format.toString()));
}
@JRubyMethod(name = "to_s")
public IRubyObject ruby_to_s(ThreadContext context)
{
return RubyString.newString(context.runtime, event.toString());
}
@JRubyMethod(name = "to_hash")
public IRubyObject ruby_to_hash(ThreadContext context)
{
// TODO: is this the most efficient?
RubyHash hash = JavaUtil.convertJavaToUsableRubyObject(context.runtime, this.event.toMap()).convertToHash();
// inject RubyTimestamp in new hash
hash.put(PathCache.TIMESTAMP, JrubyTimestampExtLibrary.RubyTimestamp.newRubyTimestamp(context.runtime, this.event.getTimestamp()));
return hash;
}
@JRubyMethod(name = "to_java")
public IRubyObject ruby_to_java(ThreadContext context)
{
return JavaUtil.convertJavaToUsableRubyObject(context.runtime, this.event);
}
@JRubyMethod(name = "to_json", rest = true)
public IRubyObject ruby_to_json(ThreadContext context, IRubyObject[] args)
throws IOException
{
return RubyString.newString(context.runtime, event.toJson());
}
@JRubyMethod(name = "validate_value", required = 1, meta = true)
public static IRubyObject ruby_validate_value(ThreadContext context, IRubyObject recv, IRubyObject value)
{
// TODO: add UTF-8 validation
return value;
}
}
}

View file

@ -0,0 +1,140 @@
package com.logstash.ext;
import com.logstash.*;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.jruby.*;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.javasupport.JavaUtil;
import org.jruby.runtime.Arity;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.load.Library;
import java.io.IOException;
public class JrubyTimestampExtLibrary implements Library {
public void load(Ruby runtime, boolean wrap) throws IOException {
RubyModule module = runtime.defineModule("LogStash");
RubyClass clazz = runtime.defineClassUnder("Timestamp", runtime.getObject(), new ObjectAllocator() {
public IRubyObject allocate(Ruby runtime, RubyClass rubyClass) {
return new RubyTimestamp(runtime, rubyClass);
}
}, module);
clazz.defineAnnotatedMethods(RubyTimestamp.class);
}
@JRubyClass(name = "Timestamp", parent = "Object")
public static class RubyTimestamp extends RubyObject {
private Timestamp timestamp;
public RubyTimestamp(Ruby runtime, RubyClass klass) {
super(runtime, klass);
}
public RubyTimestamp(Ruby runtime, RubyClass klass, Timestamp timestamp) {
this(runtime, klass);
this.timestamp = timestamp;
}
public RubyTimestamp(Ruby runtime, Timestamp timestamp) {
this(runtime, runtime.getModule("LogStash").getClass("Timestamp"), timestamp);
}
public RubyTimestamp(Ruby runtime) {
this(runtime, new Timestamp());
}
public static RubyTimestamp newRubyTimestamp(Ruby runtime) {
return new RubyTimestamp(runtime);
}
public static RubyTimestamp newRubyTimestamp(Ruby runtime, long epoch) {
// Ruby epoch is in seconds, Java in milliseconds
return new RubyTimestamp(runtime, new Timestamp(epoch * 1000));
}
public static RubyTimestamp newRubyTimestamp(Ruby runtime, Timestamp timestamp) {
return new RubyTimestamp(runtime, timestamp);
}
public Timestamp getTimestamp() {
return timestamp;
}
public void setTimestamp(Timestamp timestamp) {
this.timestamp = timestamp;
}
// def initialize(time = Time.new)
@JRubyMethod(name = "initialize", optional = 1)
public IRubyObject ruby_initialize(ThreadContext context, IRubyObject[] args)
{
args = Arity.scanArgs(context.runtime, args, 0, 1);
IRubyObject time = args[0];
if (time.isNil()) {
this.timestamp = new Timestamp();
} else if (time instanceof RubyTime) {
this.timestamp = new Timestamp(((RubyTime)time).getDateTime());
} else {
throw context.runtime.newTypeError("wrong argument type " + time.getMetaClass() + " (expected Time)");
}
return context.nil;
}
@JRubyMethod(name = "time")
public IRubyObject ruby_time(ThreadContext context)
{
return RubyTime.newTime(context.runtime, this.timestamp.getTime());
}
@JRubyMethod(name = "to_i")
public IRubyObject ruby_to_i(ThreadContext context)
{
return RubyFixnum.newFixnum(context.runtime, this.timestamp.getTime().getMillis() / 1000);
}
@JRubyMethod(name = "to_s")
public IRubyObject ruby_to_s(ThreadContext context)
{
return ruby_to_iso8601(context);
}
@JRubyMethod(name = "to_iso8601")
public IRubyObject ruby_to_iso8601(ThreadContext context)
{
return RubyString.newString(context.runtime, this.timestamp.toIso8601());
}
@JRubyMethod(name = "to_java")
public IRubyObject ruby_to_java(ThreadContext context)
{
return JavaUtil.convertJavaToUsableRubyObject(context.runtime, this.timestamp);
}
@JRubyMethod(name = "to_json", rest = true)
public IRubyObject ruby_to_json(ThreadContext context, IRubyObject[] args)
{
return RubyString.newString(context.runtime, "\"" + this.timestamp.toIso8601() + "\"");
}
@JRubyMethod(name = "at", required = 1, meta = true)
public static IRubyObject ruby_at(ThreadContext context, IRubyObject recv, IRubyObject epoch)
{
if (!(epoch instanceof RubyInteger)) {
throw context.runtime.newTypeError("wrong argument type " + epoch.getMetaClass() + " (expected integer Fixnum)");
}
//
return RubyTimestamp.newRubyTimestamp(context.runtime, (((RubyInteger) epoch).getLongValue()));
}
@JRubyMethod(name = "now", meta = true)
public static IRubyObject ruby_at(ThreadContext context, IRubyObject recv)
{
return RubyTimestamp.newRubyTimestamp(context.runtime);
}
}
}

View file

@ -0,0 +1,160 @@
package com.logstash;
import org.junit.Test;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class AccessorsTest {
public class TestableAccessors extends Accessors {
public TestableAccessors(Map data) {
super(data);
}
public Map<String, Object> getLut() {
return lut;
}
public Object lutGet(String reference) {
return this.lut.get(reference);
}
}
@Test
public void testBareGet() throws Exception {
Map data = new HashMap();
data.put("foo", "bar");
String reference = "foo";
TestableAccessors accessors = new TestableAccessors(data);
assertEquals(accessors.lutGet(reference), null);
assertEquals(accessors.get(reference), "bar");
assertEquals(accessors.lutGet(reference), data);
}
@Test
public void testAbsentBareGet() throws Exception {
Map data = new HashMap();
data.put("foo", "bar");
String reference = "baz";
TestableAccessors accessors = new TestableAccessors(data);
assertEquals(accessors.lutGet(reference), null);
assertEquals(accessors.get(reference), null);
assertEquals(accessors.lutGet(reference), data);
}
@Test
public void testBareBracketsGet() throws Exception {
Map data = new HashMap();
data.put("foo", "bar");
String reference = "[foo]";
TestableAccessors accessors = new TestableAccessors(data);
assertEquals(accessors.lutGet(reference), null);
assertEquals(accessors.get(reference), "bar");
assertEquals(accessors.lutGet(reference), data);
}
@Test
public void testDeepMapGet() throws Exception {
Map data = new HashMap();
Map inner = new HashMap();
data.put("foo", inner);
inner.put("bar", "baz");
String reference = "[foo][bar]";
TestableAccessors accessors = new TestableAccessors(data);
assertEquals(accessors.lutGet(reference), null);
assertEquals(accessors.get(reference), "baz");
assertEquals(accessors.lutGet(reference), inner);
}
@Test
public void testAbsentDeepMapGet() throws Exception {
Map data = new HashMap();
Map inner = new HashMap();
data.put("foo", inner);
inner.put("bar", "baz");
String reference = "[foo][foo]";
TestableAccessors accessors = new TestableAccessors(data);
assertEquals(accessors.lutGet(reference), null);
assertEquals(accessors.get(reference), null);
assertEquals(accessors.lutGet(reference), inner);
}
@Test
public void testDeepListGet() throws Exception {
Map data = new HashMap();
List inner = new ArrayList();
data.put("foo", inner);
inner.add("bar");
String reference = "[foo][0]";
TestableAccessors accessors = new TestableAccessors(data);
assertEquals(accessors.lutGet(reference), null);
assertEquals(accessors.get(reference), "bar");
assertEquals(accessors.lutGet(reference), inner);
}
@Test
public void testAbsentDeepListGet() throws Exception {
Map data = new HashMap();
List inner = new ArrayList();
data.put("foo", inner);
inner.add("bar");
String reference = "[foo][1]";
TestableAccessors accessors = new TestableAccessors(data);
assertEquals(accessors.lutGet(reference), null);
assertEquals(accessors.get(reference), null);
assertEquals(accessors.lutGet(reference), inner);
}
@Test
public void testBarePut() throws Exception {
Map data = new HashMap();
String reference = "foo";
TestableAccessors accessors = new TestableAccessors(data);
assertEquals(accessors.lutGet(reference), null);
assertEquals(accessors.set(reference, "bar"), "bar");
assertEquals(accessors.lutGet(reference), data);
assertEquals(accessors.get(reference), "bar");
}
@Test
public void testBareBracketsPut() throws Exception {
Map data = new HashMap();
String reference = "[foo]";
TestableAccessors accessors = new TestableAccessors(data);
assertEquals(accessors.lutGet(reference), null);
assertEquals(accessors.set(reference, "bar"), "bar");
assertEquals(accessors.lutGet(reference), data);
assertEquals(accessors.get(reference), "bar");
}
@Test
public void testDeepMapSet() throws Exception {
Map data = new HashMap();
String reference = "[foo][bar]";
TestableAccessors accessors = new TestableAccessors(data);
assertEquals(accessors.lutGet(reference), null);
assertEquals(accessors.set(reference, "baz"), "baz");
assertEquals(accessors.lutGet(reference), data.get("foo"));
assertEquals(accessors.get(reference), "baz");
}
}

View file

@ -0,0 +1,87 @@
package com.logstash;
import org.jruby.ir.operands.Hash;
import org.junit.Test;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.*;
public class EventTest {
@Test
public void testBareToJson() throws Exception {
Event e = new EventImpl();
assertEquals("{\"@timestamp\":\"" + e.getTimestamp().toIso8601() + "\",\"@version\":\"1\"}", e.toJson());
}
@Test
public void testSimpleStringFieldToJson() throws Exception {
Map data = new HashMap();
data.put("foo", "bar");
Event e = new EventImpl(data);
assertEquals("{\"@timestamp\":\"" + e.getTimestamp().toIso8601() + "\",\"foo\":\"bar\",\"@version\":\"1\"}", e.toJson());
}
@Test
public void testSimpleIntegerFieldToJson() throws Exception {
Map data = new HashMap();
data.put("foo", 1);
Event e = new EventImpl(data);
assertEquals("{\"@timestamp\":\"" + e.getTimestamp().toIso8601() + "\",\"foo\":1,\"@version\":\"1\"}", e.toJson());
}
@Test
public void testSimpleDecimalFieldToJson() throws Exception {
Map data = new HashMap();
data.put("foo", 1.0);
Event e = new EventImpl(data);
assertEquals("{\"@timestamp\":\"" + e.getTimestamp().toIso8601() + "\",\"foo\":1.0,\"@version\":\"1\"}", e.toJson());
}
@Test
public void testSimpleMultipleFieldToJson() throws Exception {
Map data = new HashMap();
data.put("foo", 1.0);
data.put("bar", "bar");
data.put("baz", 1);
Event e = new EventImpl(data);
assertEquals("{\"bar\":\"bar\",\"@timestamp\":\"" + e.getTimestamp().toIso8601() + "\",\"foo\":1.0,\"@version\":\"1\",\"baz\":1}", e.toJson());
}
@Test
public void testDeepMapFieldToJson() throws Exception {
Event e = new EventImpl();
e.setField("[foo][bar][baz]", 1);
assertEquals("{\"@timestamp\":\"" + e.getTimestamp().toIso8601() + "\",\"foo\":{\"bar\":{\"baz\":1}},\"@version\":\"1\"}", e.toJson());
e = new EventImpl();
e.setField("[foo][0][baz]", 1);
assertEquals("{\"@timestamp\":\"" + e.getTimestamp().toIso8601() + "\",\"foo\":{\"0\":{\"baz\":1}},\"@version\":\"1\"}", e.toJson());
}
@Test
public void testGetFieldList() throws Exception {
Map data = new HashMap();
List l = new ArrayList();
data.put("foo", l);
l.add(1);
Event e = new EventImpl(data);
assertEquals(1, e.getField("[foo][0]"));
}
@Test
public void testDeepGetField() throws Exception {
Map data = new HashMap();
List l = new ArrayList();
data.put("foo", l);
Map m = new HashMap();
m.put("bar", "baz");
l.add(m);
Event e = new EventImpl(data);
assertEquals("baz", e.getField("[foo][0][bar]"));
}
}

View file

@ -0,0 +1,40 @@
package com.logstash;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.*;
public class FieldReferenceTest {
@Test
public void testParseSingleBareField() throws Exception {
FieldReference f = FieldReference.parse("foo");
assertTrue(f.getPath().isEmpty());
assertEquals(f.getKey(), "foo");
}
@Test
public void testParseSingleFieldPath() throws Exception {
FieldReference f = FieldReference.parse("[foo]");
assertTrue(f.getPath().isEmpty());
assertEquals(f.getKey(), "foo");
}
@Test
public void testParse2FieldsPath() throws Exception {
FieldReference f = FieldReference.parse("[foo][bar]");
assertEquals(f.getPath().toArray(), new String[]{"foo"});
assertEquals(f.getKey(), "bar");
}
@Test
public void testParse3FieldsPath() throws Exception {
FieldReference f = FieldReference.parse("[foo][bar]]baz]");
assertEquals(f.getPath().toArray(), new String[]{"foo", "bar"});
assertEquals(f.getKey(), "baz");
}
}

View file

@ -0,0 +1,46 @@
package com.logstash;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.junit.Test;
import static org.junit.Assert.*;
public class TimestampTest {
@Test
public void testCircularIso8601() throws Exception {
Timestamp t1 = new Timestamp();
Timestamp t2 = new Timestamp(t1.toIso8601());
assertEquals(t1.getTime(), t2.getTime());
}
@Test
public void testToIso8601() throws Exception {
Timestamp t = new Timestamp("2014-09-23T00:00:00-0800");
assertEquals("2014-09-23T08:00:00.000Z", t.toIso8601());
}
// Timestamp should always be in a UTC representation
@Test
public void testUTC() throws Exception {
Timestamp t;
t = new Timestamp();
assertEquals(DateTimeZone.UTC, t.getTime().getZone());
t = new Timestamp("2014-09-23T00:00:00-0800");
assertEquals(DateTimeZone.UTC, t.getTime().getZone());
t = new Timestamp("2014-09-23T08:00:00.000Z");
assertEquals(DateTimeZone.UTC, t.getTime().getZone());
t = new Timestamp(new Timestamp());
assertEquals(DateTimeZone.UTC, t.getTime().getZone());
long ms = DateTime.now(DateTimeZone.forID("EST")).getMillis();
t = new Timestamp(ms);
assertEquals(DateTimeZone.UTC, t.getTime().getZone());
}
}