more sprintf java impl, relates to #4191

First the case with single fieldref

Add support for +%s => epoch

Small comments concerning the caching mechanism and the memory usage

Make the #sprintf method work with Array and Hash
This commit is contained in:
Pier-Hugues Pellerin 2015-06-02 11:37:45 -04:00 committed by Colin Surprenant
parent a8185a2043
commit 731d5dbb00
11 changed files with 129 additions and 29 deletions

View file

@ -4,6 +4,9 @@ import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import java.io.IOError;
import java.io.IOException;
/**
* Created by ph on 15-05-22.
*/
@ -15,7 +18,7 @@ public class DateNode implements TemplateNode {
}
@Override
public String evaluate(Event event) {
public String evaluate(Event event) throws IOException {
return event.getTimestamp().getTime().toString(this.formatter);
}
}

View file

@ -1,5 +1,7 @@
package com.logstash;
import java.io.IOException;
/**
* Created by ph on 15-05-22.
*/
@ -7,9 +9,7 @@ public class EpochNode implements TemplateNode {
public EpochNode(){ }
@Override
public String evaluate(Event event) {
// TODO: Change this for the right call
Long epoch = 1L;
return String.valueOf(epoch);
public String evaluate(Event event) throws IOException {
return String.valueOf(event.getTimestamp().getTime().getMillis() / 1000);
}
}

View file

@ -42,5 +42,5 @@ public interface Event {
Event append(Event e);
String sprintf(String s);
String sprintf(String s) throws IOException;
}

View file

@ -133,9 +133,8 @@ public class EventImpl implements Event, Cloneable, Serializable {
}
@Override
public String sprintf(String s) {
// TODO: implement sprintf
return s;
public String sprintf(String s) throws IOException {
return StringInterpolation.getInstance().evaluate(this, s);
}
public Event clone() {

View file

@ -1,5 +1,12 @@
package com.logstash;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.map.ObjectMapper;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/**
* Created by ph on 15-05-22.
*/
@ -15,11 +22,19 @@ public class KeyNode implements TemplateNode {
leverage jackson lib to do the actual.
*/
@Override
public String evaluate(Event event) {
public String evaluate(Event event) throws IOException {
Object value = event.getField(this.key);
if (value != null) {
return String.valueOf(event.getField(this.key));
if (value instanceof List) {
return String.join(",", (List) value);
} else if (value instanceof Map) {
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString((Map<String, Object>)value);
} else {
return event.getField(this.key).toString();
}
} else {
return "%{" + this.key + "}";
}

View file

@ -1,5 +1,7 @@
package com.logstash;
import java.io.IOException;
/**
* Created by ph on 15-05-22.
*/
@ -11,7 +13,7 @@ public class StaticNode implements TemplateNode {
}
@Override
public String evaluate(Event event) {
public String evaluate(Event event) throws IOException {
return this.content;
}
}

View file

@ -1,7 +1,9 @@
package com.logstash;
import java.io.IOException;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -15,11 +17,19 @@ public class StringInterpolation {
}
private StringInterpolation() {
// TODO: this may need some tweaking for the concurrency level to get better memory usage.
// TODO:
// This may need some tweaking for the concurrency level to get better memory usage.
// The current implementation doesn't allow the keys to expire, I think under normal usage
// the keys will converge to a fixed number.
//
// If this code make logstash goes OOM, we have the following options:
// - If the key doesn't contains a `%` do not cache it, this will reduce the key size at a performance cost.
// - Use some kind LRU cache
// - Create a new data structure that use weakref or use Google Guava for the cache https://code.google.com/p/guava-libraries/
this.cache = new ConcurrentHashMap<>();
}
public String evaluate(Event event, String template) {
public String evaluate(Event event, String template) throws IOException {
TemplateNode compiledTemplate = (TemplateNode) this.cache.get(template);
if(compiledTemplate == null) {
@ -46,11 +56,11 @@ public class StringInterpolation {
while (matcher.find()) {
if (matcher.start() > 0) {
compiledTemplate.add(new StaticNode(template.substring(pos, matcher.start())));
pos = matcher.end();
}
tag = matcher.group(1);
compiledTemplate.add(identifyTag(tag));
pos = matcher.end();
}
if(pos < template.length() - 1) {
@ -67,11 +77,12 @@ public class StringInterpolation {
}
}
// TODO: add support for array, hash, float and epoch
public TemplateNode identifyTag(String tag) {
// Doesnt support parsing the float yet
if(tag.charAt(0) == '+') {
return new DateNode(tag.substring(1));
if(tag.equals("+%s")) {
return new EpochNode();
} else if(tag.charAt(0) == '+') {
return new DateNode(tag.substring(1));
} else {
return new KeyNode(tag);
}

View file

@ -1,5 +1,6 @@
package com.logstash;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@ -20,7 +21,7 @@ public class Template implements TemplateNode {
}
@Override
public String evaluate(Event event) {
public String evaluate(Event event) throws IOException {
StringBuffer results = new StringBuffer();
for (int i = 0; i < nodes.size(); i++) {

View file

@ -0,0 +1,12 @@
package com.logstash;
import org.codehaus.jackson.JsonGenerationException;
import java.io.IOException;
/**
* Created by ph on 15-05-22.
*/
public interface TemplateNode {
String evaluate(Event event) throws IOException;
}

View file

@ -193,8 +193,7 @@ public class JrubyEventExtLibrary implements Library {
}
@JRubyMethod(name = "sprintf", required = 1)
public IRubyObject ruby_sprintf(ThreadContext context, IRubyObject format)
{
public IRubyObject ruby_sprintf(ThreadContext context, IRubyObject format) throws IOException {
return RubyString.newString(context.runtime, event.sprintf(format.toString()));
}

View file

@ -1,8 +1,11 @@
package com.logstash;
import org.joda.time.DateTime;
import org.junit.Test;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
@ -11,7 +14,7 @@ import static org.junit.Assert.*;
public class StringInterpolationTest {
@Test
public void testCompletelyStaticTemplate() {
public void testCompletelyStaticTemplate() throws IOException {
Event event = getTestEvent();
String path = "/full/path/awesome";
StringInterpolation si = StringInterpolation.getInstance();
@ -20,7 +23,7 @@ public class StringInterpolationTest {
}
@Test
public void testOneLevelField() {
public void testOneLevelField() throws IOException {
Event event = getTestEvent();
String path = "/full/%{bar}/awesome";
StringInterpolation si = StringInterpolation.getInstance();
@ -29,7 +32,7 @@ public class StringInterpolationTest {
}
@Test
public void testMultipleLevelField() {
public void testMultipleLevelField() throws IOException {
Event event = getTestEvent();
String path = "/full/%{bar}/%{awesome}";
StringInterpolation si = StringInterpolation.getInstance();
@ -38,7 +41,7 @@ public class StringInterpolationTest {
}
@Test
public void testMissingKey() {
public void testMissingKey() throws IOException {
Event event = getTestEvent();
String path = "/full/%{do-not-exist}";
StringInterpolation si = StringInterpolation.getInstance();
@ -47,7 +50,7 @@ public class StringInterpolationTest {
}
@Test
public void testDateFormater() {
public void testDateFormater() throws IOException {
Event event = getTestEvent();
String path = "/full/%{+YYYY}";
StringInterpolation si = StringInterpolation.getInstance();
@ -56,7 +59,7 @@ public class StringInterpolationTest {
}
@Test
public void TestMixDateAndFields() {
public void TestMixDateAndFields() throws IOException {
Event event = getTestEvent();
String path = "/full/%{+YYYY}/weeee/%{bar}";
StringInterpolation si = StringInterpolation.getInstance();
@ -65,7 +68,7 @@ public class StringInterpolationTest {
}
@Test
public void testUnclosedTag() {
public void testUnclosedTag() throws IOException {
Event event = getTestEvent();
String path = "/full/%{+YYY/web";
StringInterpolation si = StringInterpolation.getInstance();
@ -73,10 +76,65 @@ public class StringInterpolationTest {
assertEquals("/full/%{+YYY/web", si.evaluate(event, path));
}
@Test
public void TestStringIsOneDateTag() throws IOException {
Event event = getTestEvent();
String path = "%{+YYYY}";
StringInterpolation si = StringInterpolation.getInstance();
assertEquals("2015", si.evaluate(event, path));
}
@Test
public void TestFieldRef() throws IOException {
Event event = getTestEvent();
String path = "%{[j][k1]}";
StringInterpolation si = StringInterpolation.getInstance();
assertEquals("v", si.evaluate(event, path));
}
@Test
public void TestEpoch() throws IOException {
Event event = getTestEvent();
String path = "%{+%s}";
StringInterpolation si = StringInterpolation.getInstance();
assertEquals("1443672000", si.evaluate(event, path));
}
@Test
public void TestValueIsArray() throws IOException {
ArrayList l = new ArrayList();
l.add("Hello");
l.add("world");
Event event = getTestEvent();
event.setField("message", l);
String path = "%{message}";
StringInterpolation si = StringInterpolation.getInstance();
assertEquals("Hello,world", si.evaluate(event, path));
}
@Test
public void TestValueIsHash() throws IOException {
Event event = getTestEvent();
String path = "%{j}";
StringInterpolation si = StringInterpolation.getInstance();
assertEquals("{\"k1\":\"v\"}", si.evaluate(event, path));
}
public Event getTestEvent() {
Map data = new HashMap();
Map inner = new HashMap();
inner.put("k1", "v");
data.put("bar", "foo");
data.put("awesome", "logstash");
data.put("j", inner);
data.put("@timestamp", new DateTime(2015, 10, 1, 0, 0, 0));
Event event = new EventImpl(data);
return event;