I need a handy way to censore DTO fields/secrets when converting them into a json string (for logging).
Im using objectmapper and I heard about @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
but it completely hides the annotated field and it can be confusing because if I want to trace an error in the logs I might think that it was just null, therefore It would be better if the property's value was simply replaced with something like ***
.
Defining a list of field names to replace values is also not suitable because i might censore fields that i dont want to. Also refactoring would complicate things too.
My idea is to annotate fields with a custom annotation and implement some json postprocessing with objectmapper but i couldnt find a way to do it. For example:
public record Person(String name, @Censored String secret) {
}
// and later
log.info(objectMapper.writeValueAsString(person));
The output should be
{"name":"John","secret":"***"}
Is there a way to configure ObjectMapper
to censore fields that are annotated with my custom @Censored
annotation?
Im also open to other suggestions to implement log censoring logic.
I tried to use @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) but it didnt achieve the result that i wanted.
The simplest way I can think to implement this via Jackson would be with a custom serializer:
public class CensoredSerializer<T> extends StdSerializer<T> {
public CensoredSerializer() {
this(null);
}
public CensoredSerializer(Class<T> t) {
super(t);
}
@Override
public void serialize(T value, JsonGenerator gen, SerializerProvider provider)
throws IOException
{
gen.writeString("***");
}
}
You would use this like so:
public record Person(String name, @JsonSerialize(using = CensoredSerializer.class) String secret) {
}
If that's too verbose for you, you can use it as the basis for a custom annotation:
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = CensoredSerializer.class)
public @interface Censored {
}
This lets you use @Censored
as a shorthand for @JsonSerialize(using = CensoredSerializer.class)
, like so:
public record Person(String name, @Censored String secret) {
}
As mentioned in the comments, it is also possible to do this by masking values in the logging config. I personally feel like there's some value in having it defined directly in the class instead, but your milage may vary!