mirror of
https://github.com/elastic/logstash.git
synced 2025-04-23 22:27:21 -04:00
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:
parent
18d4f26c81
commit
e5fb1d2977
25 changed files with 1481 additions and 257 deletions
45
lib/jruby_event/Rakefile
Normal file
45
lib/jruby_event/Rakefile
Normal 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
|
BIN
lib/jruby_event/jackson-core-asl-1.9.13.jar
Normal file
BIN
lib/jruby_event/jackson-core-asl-1.9.13.jar
Normal file
Binary file not shown.
BIN
lib/jruby_event/jackson-mapper-asl-1.9.13.jar
Normal file
BIN
lib/jruby_event/jackson-mapper-asl-1.9.13.jar
Normal file
Binary file not shown.
BIN
lib/jruby_event/jruby_event.jar
Normal file
BIN
lib/jruby_event/jruby_event.jar
Normal file
Binary file not shown.
19
lib/jruby_event/jruby_event.rb
Normal file
19
lib/jruby_event/jruby_event.rb
Normal 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"
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
111
spec/jruby_event/event_spec.rb
Normal file
111
spec/jruby_event/event_spec.rb
Normal 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
|
||||
``
|
25
spec/jruby_event/timestamp_spec.rb
Normal file
25
spec/jruby_event/timestamp_spec.rb
Normal 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
|
||||
``
|
|
@ -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
|
||||
|
||||
|
|
14
src/main/java/JrubyEventExtService.java
Normal file
14
src/main/java/JrubyEventExtService.java
Normal 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;
|
||||
}
|
||||
}
|
15
src/main/java/JrubyTimestampExtService.java
Normal file
15
src/main/java/JrubyTimestampExtService.java
Normal 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;
|
||||
}
|
||||
}
|
124
src/main/java/com/logstash/Accessors.java
Normal file
124
src/main/java/com/logstash/Accessors.java
Normal 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;
|
||||
}
|
||||
}
|
46
src/main/java/com/logstash/Event.java
Normal file
46
src/main/java/com/logstash/Event.java
Normal 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);
|
||||
}
|
191
src/main/java/com/logstash/EventImpl.java
Normal file
191
src/main/java/com/logstash/EventImpl.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
40
src/main/java/com/logstash/FieldReference.java
Normal file
40
src/main/java/com/logstash/FieldReference.java
Normal 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);
|
||||
}
|
||||
}
|
47
src/main/java/com/logstash/PathCache.java
Normal file
47
src/main/java/com/logstash/PathCache.java
Normal 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;
|
||||
}
|
||||
}
|
64
src/main/java/com/logstash/Timestamp.java
Normal file
64
src/main/java/com/logstash/Timestamp.java
Normal 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();
|
||||
}
|
||||
}
|
17
src/main/java/com/logstash/TimestampSerializer.java
Normal file
17
src/main/java/com/logstash/TimestampSerializer.java
Normal 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());
|
||||
}
|
||||
}
|
237
src/main/java/com/logstash/ext/JrubyEventExtLibrary.java
Normal file
237
src/main/java/com/logstash/ext/JrubyEventExtLibrary.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
140
src/main/java/com/logstash/ext/JrubyTimestampExtLibrary.java
Normal file
140
src/main/java/com/logstash/ext/JrubyTimestampExtLibrary.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
160
src/test/java/com/logstash/AccessorsTest.java
Normal file
160
src/test/java/com/logstash/AccessorsTest.java
Normal 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");
|
||||
}
|
||||
}
|
87
src/test/java/com/logstash/EventTest.java
Normal file
87
src/test/java/com/logstash/EventTest.java
Normal 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]"));
|
||||
}
|
||||
}
|
40
src/test/java/com/logstash/FieldReferenceTest.java
Normal file
40
src/test/java/com/logstash/FieldReferenceTest.java
Normal 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");
|
||||
}
|
||||
}
|
46
src/test/java/com/logstash/TimestampTest.java
Normal file
46
src/test/java/com/logstash/TimestampTest.java
Normal 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());
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue