Native java implementation of Event#sprintf, relates to #4191

This implementation is different from the master version since instead
of doing JIT Template compilation on every events we create a data
structure of a compiled templates. This template is cached in a
concurrent hashmap and used on other requests.

fixing conflict
This commit is contained in:
Pier-Hugues Pellerin 2015-06-01 23:05:19 -04:00 committed by Colin Surprenant
parent e5fb1d2977
commit 08c707fada
8 changed files with 297 additions and 20 deletions

View file

@ -0,0 +1,21 @@
package com.logstash;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
/**
* Created by ph on 15-05-22.
*/
public class DateNode implements TemplateNode {
private DateTimeFormatter formatter;
public DateNode(String format) {
this.formatter = DateTimeFormat.forPattern(format).withZone(DateTimeZone.UTC);
}
@Override
public String evaluate(Event event) {
return event.getTimestamp().getTime().toString(this.formatter);
}
}

View file

@ -0,0 +1,15 @@
package com.logstash;
/**
* Created by ph on 15-05-22.
*/
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);
}
}

View file

@ -0,0 +1,27 @@
package com.logstash;
/**
* Created by ph on 15-05-22.
*/
public class KeyNode implements TemplateNode {
private String key;
public KeyNode(String key) {
this.key = key;
}
/**
This will be more complicated with hash and array.
leverage jackson lib to do the actual.
*/
@Override
public String evaluate(Event event) {
Object value = event.getField(this.key);
if (value != null) {
return String.valueOf(event.getField(this.key));
} else {
return "%{" + this.key + "}";
}
}
}

View file

@ -0,0 +1,17 @@
package com.logstash;
/**
* Created by ph on 15-05-22.
*/
public class StaticNode implements TemplateNode {
private String content;
public StaticNode(String content) {
this.content = content;
}
@Override
public String evaluate(Event event) {
return this.content;
}
}

View file

@ -0,0 +1,83 @@
package com.logstash;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class StringInterpolation {
static Pattern TEMPLATE_TAG = Pattern.compile("%\\{([^}]+)\\}");
static Map cache;
protected static class HoldCurrent {
private static final StringInterpolation INSTANCE = new StringInterpolation();
}
private StringInterpolation() {
// TODO: this may need some tweaking for the concurrency level to get better memory usage.
this.cache = new ConcurrentHashMap<>();
}
public String evaluate(Event event, String template) {
TemplateNode compiledTemplate = (TemplateNode) this.cache.get(template);
if(compiledTemplate == null) {
compiledTemplate = this.compile(template);
TemplateNode set = (TemplateNode) this.cache.putIfAbsent(template, compiledTemplate);
compiledTemplate = (set != null) ? set : compiledTemplate;
}
return compiledTemplate.evaluate(event);
}
public TemplateNode compile(String template) {
Template compiledTemplate = new Template();
if (template.indexOf('%') == -1) {
// Move the nodes to a custom instance
// so we can remove the iterator and do one `.evaluate`
compiledTemplate.add(new StaticNode(template));
} else {
Matcher matcher = TEMPLATE_TAG.matcher(template);
String tag;
int pos = 0;
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));
}
if(pos < template.length() - 1) {
compiledTemplate.add(new StaticNode(template.substring(pos)));
}
}
// if we only have one node return the node directly
// and remove the need to loop.
if(compiledTemplate.size() == 1) {
return compiledTemplate.get(0);
} else {
return compiledTemplate;
}
}
// 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));
} else {
return new KeyNode(tag);
}
}
static StringInterpolation getInstance() {
return HoldCurrent.INSTANCE;
}
}

View file

@ -0,0 +1,31 @@
package com.logstash;
import java.util.ArrayList;
import java.util.List;
public class Template implements TemplateNode {
public List nodes = new ArrayList<>();
public Template() {}
public void add(TemplateNode node) {
nodes.add(node);
}
public int size() {
return nodes.size();
}
public TemplateNode get(int index) {
return (TemplateNode) nodes.get(index);
}
@Override
public String evaluate(Event event) {
StringBuffer results = new StringBuffer();
for (int i = 0; i < nodes.size(); i++) {
results.append(((TemplateNode) nodes.get(i)).evaluate(event));
}
return results.toString();
}
}

View file

@ -1,6 +1,5 @@
package com.logstash;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
@ -27,38 +26,38 @@ public class Timestamp {
}
public Timestamp(Timestamp t) {
this.time = t.getTime();
}
this.time = t.getTime();
}
public Timestamp(long epoch_milliseconds) {
this.time = new DateTime(epoch_milliseconds, DateTimeZone.UTC);
}
this.time = new DateTime(epoch_milliseconds, DateTimeZone.UTC);
}
public Timestamp(Long epoch_milliseconds) {
this.time = new DateTime(epoch_milliseconds, DateTimeZone.UTC);
}
this.time = new DateTime(epoch_milliseconds, DateTimeZone.UTC);
}
public Timestamp(Date date) {
this.time = new DateTime(date, DateTimeZone.UTC);
}
this.time = new DateTime(date, DateTimeZone.UTC);
}
public Timestamp(DateTime date) {
this.time = date.toDateTime(DateTimeZone.UTC);
}
this.time = date.toDateTime(DateTimeZone.UTC);
}
public DateTime getTime() {
return time;
}
return time;
}
public static Timestamp now() {
return new Timestamp();
}
return new Timestamp();
}
public String toIso8601() {
return this.iso8601Formatter.print(this.time);
}
return this.iso8601Formatter.print(this.time);
}
public String toString() {
return toIso8601();
}
}
return toIso8601();
}
}

View file

@ -0,0 +1,84 @@
package com.logstash;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
import static org.junit.Assert.*;
public class StringInterpolationTest {
@Test
public void testCompletelyStaticTemplate() {
Event event = getTestEvent();
String path = "/full/path/awesome";
StringInterpolation si = StringInterpolation.getInstance();
assertEquals(path, si.evaluate(event, path));
}
@Test
public void testOneLevelField() {
Event event = getTestEvent();
String path = "/full/%{bar}/awesome";
StringInterpolation si = StringInterpolation.getInstance();
assertEquals("/full/foo/awesome", si.evaluate(event, path));
}
@Test
public void testMultipleLevelField() {
Event event = getTestEvent();
String path = "/full/%{bar}/%{awesome}";
StringInterpolation si = StringInterpolation.getInstance();
assertEquals("/full/foo/logstash", si.evaluate(event, path));
}
@Test
public void testMissingKey() {
Event event = getTestEvent();
String path = "/full/%{do-not-exist}";
StringInterpolation si = StringInterpolation.getInstance();
assertEquals("/full/%{do-not-exist}", si.evaluate(event, path));
}
@Test
public void testDateFormater() {
Event event = getTestEvent();
String path = "/full/%{+YYYY}";
StringInterpolation si = StringInterpolation.getInstance();
assertEquals("/full/2015", si.evaluate(event, path));
}
@Test
public void TestMixDateAndFields() {
Event event = getTestEvent();
String path = "/full/%{+YYYY}/weeee/%{bar}";
StringInterpolation si = StringInterpolation.getInstance();
assertEquals("/full/2015/weeee/foo", si.evaluate(event, path));
}
@Test
public void testUnclosedTag() {
Event event = getTestEvent();
String path = "/full/%{+YYY/web";
StringInterpolation si = StringInterpolation.getInstance();
assertEquals("/full/%{+YYY/web", si.evaluate(event, path));
}
public Event getTestEvent() {
Map data = new HashMap();
data.put("bar", "foo");
data.put("awesome", "logstash");
Event event = new EventImpl(data);
return event;
}
}