logstash-logback-encoder
logstash-logback-encoder copied to clipboard
Provide detection and handling of types determined "unsafe" to serialize
As discussed here and here, there is a need for logstash-logback-encoder to provide a convenient way to filter/handle types that cannot be serialized by Jackson (e.g. recursion), or that do not serialize as desired by Jackson (e.g. nesting too deep), when logged as
- a
StructuredArgument
- a non-structured argument (with
includeNonStructuredArguments=true
)
One of several unsafe detection strategies could be implemented:
- An allowlist of known "safe" types
- A denylist of known "unsafe" types
- Try serializing first, then add to denylist on failure
- perhaps other strategies?
In addition, one of several unsafe handler strategies could be implemented
- Don't serialize the type (and optionally warn?)
- Serialize the
toString()
of the object instead
Currently, logstash-logback-encoder provides primitives that allow applications to implement any one of these strategies. Specifically,
- a
JsonProvider
could be implemented (similar to theArgumentsJsonProvider
) to check argument types before serializing- This could handle the "try serializing first, then add to denylist" strategy, but has the downside that it cannot detect nested "unsafe" types
- a
BeanSerializerModifier
could be added to Jackson'sSerializerFactory
- This could not handle the "try first, then add to denylist strategy", but has the advantage that it could detect nested "unsafe" types
For example, a simple allowlist detection strategy that handles unsafe types by serializing their toString()
value could be implemented with a BeanSerializerModifier
like this:
import java.util.HashSet;
import java.util.Set;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.MappingJsonFactory;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
public class AllowedTypeFilterDecorator implements JsonFactoryDecorator {
/**
* Set of class names deemed safe to serialize.
* Any objects of this type (or subtype) will be serialized by the {@link JsonSerializer} selected by Jackson.
* Any objects NOT of this type (or subtype) will be serialized as a string by invoking {@link Object#toString()} on the object.
*/
private Set<String> allowed = new HashSet<>();
@Override
public MappingJsonFactory decorate(MappingJsonFactory factory) {
factory.getCodec().setSerializerFactory(factory.getCodec().getSerializerFactory().withSerializerModifier(new BeanSerializerModifier() {
@Override
public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription desc, JsonSerializer<?> serializer) {
for (String allowedClassName : allowed) {
try {
Class<?> allowedClass = Class.forName(allowedClassName, true, desc.getBeanClass().getClassLoader());
if (allowedClass.isAssignableFrom(desc.getBeanClass())) {
return serializer;
}
} catch (ClassNotFoundException e) {
// Allowed class is unknown to the bean class' classloader
// Therefore, the bean class cannot possibly be an instance of the allowed class
// Therefore, continue
}
}
return ToStringSerializer.instance;
}
}));
return factory;
}
public void addAllowed(String allowedClassName) throws ClassNotFoundException {
this.allowed.add(allowedClassName);
}
}
and configured like this
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<includeNonStructuredArguments>true</includeNonStructuredArguments>
<jsonFactoryDecorator class="net.logstash.logback.decorate.AllowedTypeFilterDecorator">
<allowed>foo.Baz</allowed>
<allowed>foo.Bar</allowed>
</jsonFactoryDecorator>
</encoder>
Need to decide:
- Which unsafe detection strategies to implement?
- Which unsafe handler strategies to implement?
- Should logstash-logback-encoder come with a default set of "safe" types configured?