mirror of
https://github.com/elastic/logstash.git
synced 2025-04-24 14:47:19 -04:00
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:
parent
e5fb1d2977
commit
08c707fada
8 changed files with 297 additions and 20 deletions
21
src/main/java/com/logstash/DateNode.java
Normal file
21
src/main/java/com/logstash/DateNode.java
Normal 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);
|
||||
}
|
||||
}
|
15
src/main/java/com/logstash/EpochNode.java
Normal file
15
src/main/java/com/logstash/EpochNode.java
Normal 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);
|
||||
}
|
||||
}
|
27
src/main/java/com/logstash/KeyNode.java
Normal file
27
src/main/java/com/logstash/KeyNode.java
Normal 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 + "}";
|
||||
}
|
||||
}
|
||||
}
|
17
src/main/java/com/logstash/StaticNode.java
Normal file
17
src/main/java/com/logstash/StaticNode.java
Normal 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;
|
||||
}
|
||||
}
|
83
src/main/java/com/logstash/StringInterpolation.java
Normal file
83
src/main/java/com/logstash/StringInterpolation.java
Normal 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;
|
||||
}
|
||||
}
|
31
src/main/java/com/logstash/Template.java
Normal file
31
src/main/java/com/logstash/Template.java
Normal 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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
84
src/test/java/com/logstash/StringInterpolationTest.java
Normal file
84
src/test/java/com/logstash/StringInterpolationTest.java
Normal 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;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue