Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Context map feature #65

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ In your `logback.xml`:
<includeMdc>false</includeMdc> <!-- optional (default false) -->
<maxMessageSize>100</maxMessageSize> <!-- optional (default -1 -->
<authentication class="com.internetitem.logback.elasticsearch.config.BasicAuthentication" /> <!-- optional -->
<enableContextMap>false</enableContextMap><!-- optional (default false) -->
<properties>
<property>
<name>host</name>
Expand Down Expand Up @@ -110,6 +111,7 @@ Configuration Reference
* `includeMdc` (optional, default false): If set to `true`, then all [MDC](http://www.slf4j.org/api/org/slf4j/MDC.html) values will be mapped to properties on the JSON payload.
* `maxMessageSize` (optional, default -1): If set to a number greater than 0, truncate messages larger than this length, then append "`..`" to denote that the message was truncated
* `authentication` (optional): Add the ability to send authentication headers (see below)
* `enableContextMap` (optional): If the latest parameter in logger call is of type java.util.Map then all content of it will be traversed and written with prefix `context.*`. For event-specific custom fields.

The fields `@timestamp` and `message` are always sent and can not currently be configured. Additional fields can be sent by adding `<property>` elements to the `<properties>` set.

Expand Down Expand Up @@ -154,3 +156,17 @@ Included is also an Elasticsearch appender for Logback Access. The configuration

* The Appender class name is `com.internetitem.logback.elasticsearch.ElasticsearchAccessAppender`
* The `value` for each `property` uses the [Logback Access conversion words](http://logback.qos.ch/manual/layouts.html#logback-access).

Event-specific custom fields
============================
Log line:

log.info("Service started in {} seconds", duration/1000, Collections.singletonMap("duration", duration));

Result:

{
"@timestamp": "2014-06-04T15:26:14.464+02:00",
"message": "Service started in 12 seconds",
"duration": 12368,
}
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,8 @@ public void setAuthentication(Authentication auth) {
public void setMaxMessageSize(int maxMessageSize) {
settings.setMaxMessageSize(maxMessageSize);
}

public void setEnableContextMap(boolean enableContextMap) {
settings.setEnableContextMap(enableContextMap);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,18 @@
import com.internetitem.logback.elasticsearch.config.Settings;
import com.internetitem.logback.elasticsearch.util.AbstractPropertyAndEncoder;
import com.internetitem.logback.elasticsearch.util.ClassicPropertyAndEncoder;
import com.internetitem.logback.elasticsearch.util.ContextMapWriter;
import com.internetitem.logback.elasticsearch.util.ErrorReporter;

public class ClassicElasticsearchPublisher extends AbstractElasticsearchPublisher<ILoggingEvent> {

public ClassicElasticsearchPublisher(Context context, ErrorReporter errorReporter, Settings settings, ElasticsearchProperties properties, HttpRequestHeaders headers) throws IOException {
super(context, errorReporter, settings, properties, headers);
contextMapWriter = new ContextMapWriter();
}

protected ContextMapWriter contextMapWriter;

@Override
protected AbstractPropertyAndEncoder<ILoggingEvent> buildPropertyAndEncoder(Context context, Property property) {
return new ClassicPropertyAndEncoder(property, context);
Expand All @@ -40,10 +44,15 @@ protected void serializeCommonFields(JsonGenerator gen, ILoggingEvent event) thr
gen.writeObjectField("message", formattedMessage);
}

if(settings.isIncludeMdc()) {
if (settings.isIncludeMdc()) {
for (Map.Entry<String, String> entry : event.getMDCPropertyMap().entrySet()) {
gen.writeObjectField(entry.getKey(), entry.getValue());
}
}

if (settings.isEnableContextMap()) {
contextMapWriter.writeContextMap(gen, event);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public class Settings {
private int maxQueueSize = 100 * 1024 * 1024;
private Authentication authentication;
private int maxMessageSize = -1;
private boolean enableContextMap;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your indentation in this MR seems to be off (inconsistent use of tabs and spaces)


public String getIndex() {
return index;
Expand Down Expand Up @@ -130,7 +131,7 @@ public String getErrorLoggerName() {
public void setErrorLoggerName(String errorLoggerName) {
this.errorLoggerName = errorLoggerName;
}

public boolean isRawJsonMessage() {
return rawJsonMessage;
}
Expand Down Expand Up @@ -162,4 +163,12 @@ public int getMaxMessageSize() {
public void setMaxMessageSize(int maxMessageSize) {
this.maxMessageSize = maxMessageSize;
}

public boolean isEnableContextMap() {
return enableContextMap;
}

public void setEnableContextMap(boolean enableContextMap) {
this.enableContextMap = enableContextMap;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.internetitem.logback.elasticsearch.util;

import ch.qos.logback.classic.spi.ILoggingEvent;
import com.fasterxml.jackson.core.JsonGenerator;

import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;

public class ContextMapWriter {

public void writeContextMap(JsonGenerator gen, ILoggingEvent event) throws IOException {
Object[] arguments = event.getArgumentArray();
if (arguments == null || arguments.length == 0) return;
Object lastElement = arguments[arguments.length - 1];
if (lastElement instanceof Map) {
Map<String, Object> indexes = traverseObject(new HashMap<String, Object>(), "context", lastElement);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest to call traverseMap directly here. It took me a bit to figure out why traverseObject is performing instanceof checks again, when lastElement is already guaranteed to be a map here.

for (Map.Entry<String, Object> entry : indexes.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
gen.writeObjectField(key, value);
}
}
}

static Map<String, Object> traverseObject(Map<String, Object> accumulator, String context, Object object) {
if (object == null) {
return accumulator;
}
if (object instanceof Map) {
traverseMap(accumulator, context, (Map) object);
} else if (object instanceof Collection) {
traverseCollection(accumulator, context, (Collection) object);
} else if (object instanceof Number) {
accumulator.put(context, object);
} else {
accumulator.put(context, Objects.toString(object));
}
return accumulator;
}

static Map<String, Object> traverseCollection(Map<String, Object> accumulator, String context, Collection object) {
Iterator iterator = object.iterator();
int i = 0;
while (iterator.hasNext()) {
Object v = iterator.next();
traverseObject(accumulator, context + "." + i, v);
i++;
}
return accumulator;
}

static Map<String, Object> traverseMap(Map<String, Object> accumulator, String context, Map<Object, Object> object) {
for (Map.Entry entry : object.entrySet()) {
traverseObject(accumulator, context + "." + entry.getKey(), entry.getValue());
}
return accumulator;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.internetitem.logback.elasticsearch;

import ch.qos.logback.classic.spi.LoggingEvent;
import com.fasterxml.jackson.core.JsonGenerator;
import com.internetitem.logback.elasticsearch.util.ContextMapWriter;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import java.io.IOException;

import static org.mockito.Mockito.verifyZeroInteractions;

@RunWith(MockitoJUnitRunner.class)
public class ContextMapWriterTest {

@Mock
private JsonGenerator jsonGenerator;


private ContextMapWriter contextMapWriter;

@Before
public void setup() throws IOException {
contextMapWriter = new ContextMapWriter();
}

@Test
public void should_not_write_if_arguments_null_or_empty() throws IOException {
LoggingEvent event = new LoggingEvent();
contextMapWriter.writeContextMap(jsonGenerator, event);
event.setArgumentArray(new Object[]{});
contextMapWriter.writeContextMap(jsonGenerator, event);
verifyZeroInteractions(jsonGenerator);
}

@Test
public void should_not_write_if_last_element_not_map() throws IOException {
LoggingEvent event = new LoggingEvent();
event.setArgumentArray(new Object[]{"23", 3243});
contextMapWriter.writeContextMap(jsonGenerator, event);
verifyZeroInteractions(jsonGenerator);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ public void should_delegate_setters_to_settings() throws MalformedURLException {
int aSleepTime = 10000;
int readTimeout = 10000;
int connectTimeout = 5000;
boolean enableContextMap = true;

appender.setIncludeCallerData(includeCallerData);
appender.setSleepTime(aSleepTime);
Expand All @@ -202,6 +203,7 @@ public void should_delegate_setters_to_settings() throws MalformedURLException {
appender.setConnectTimeout(connectTimeout);
appender.setRawJsonMessage(rawJsonMessage);
appender.setIncludeMdc(includeMdc);
appender.setEnableContextMap(enableContextMap);

verify(settings, times(1)).setReadTimeout(readTimeout);
verify(settings, times(1)).setSleepTime(aSleepTime);
Expand All @@ -218,6 +220,8 @@ public void should_delegate_setters_to_settings() throws MalformedURLException {
verify(settings, times(1)).setConnectTimeout(connectTimeout);
verify(settings, times(1)).setRawJsonMessage(rawJsonMessage);
verify(settings, times(1)).setIncludeMdc(includeMdc);
verify(settings, times(1)).setEnableContextMap(enableContextMap);

}


Expand Down