今天,念念不忘一件事... ✅打印程序日志时,如何结合Fastjson序列化来优雅打印大对象?
项目里 前后端页面的http请求 及 dubbo服务间的RPC调用,返回值类型统一是 Result<T> 结构,如下。
@Data public class Result<T> implements Serializable { private static final long serialVersionUID = 1L; /** * 返回处理消息 */ private String message = "操作成功!"; /** * 返回code码 */ private Integer code = 0; /** * 返回数据对象 data */ @Getter private T data; }
关于各微服务间的dubbo调用,我们在底层定义了一个公用的DubboTraceFilter。这个Filter会将程序调用的dubbo接口的入参和出参打印到log文件里。其中,返回值 Result<T> 对象通过Fastjson序列化。
背景介绍完毕。接下来说我要解决的事情。
注意到 Result<T> 的data字段,它是泛型T的实例,就是说,这个data会是任意类型的数据。
从log里看,当data里是集合数据,例如,分页查询的场景,打印出来的log会超级长。
这导致日志量很大,同时,这种无用的日志刷屏,也不利于我们排查问题。
简言之,看下面两段Result<T>对象序列化的json串(为便于阅读,进行了格式化),我希望log里出现后者这种缩减版的文本。
{ "message": "成功", "code": 200, "data": "[\"0memob92142f2-ad8a-4812-913e-002f8f9d1894\", \"1memo77d4ad82-078f-4f73-a26e-c5302a596042\", \"2memoa69185c2-670d-480b-b1d2-19fd1326ecd5\", \"3memoee5d13a7-83bd-4430-a4b0-198e65201dc7\", \"4memo519d9d69-a27f-4864-8dd4-889ada1790a3\", \"5memo85034936-564b-41d8-94f0-ff1ac7be8d92\", \"6memoa22d4b20-828a-4ac5-a3fe-461283fc4154\", \"7memo7b2b8880-80b2-41f8-93d9-553467287e13\", \"8memo55afe9f2-e6b5-481c-9978-773fb5ff0f14\", \"9memoa5a92ffd-4e72-42f1-8d81-7221d2f371a3\"]", "timestamp": 1666961782888 }
{ "code": 200, "message": "成功", "data": "[\"0memob92142f2-ad8a-4812-913e-002f8f9d1894\",\"1memo77d4ad82-078f-4f73-a26e-c5302a596042\",\"2memoa6...", "timestamp": 1666961782888 }
那么,如何解决这个痛点?
我相信,找开发组里的任何一位同学,他都能解决。改DubboTraceFilter里的代码就行了,对序列化的json串进行相关截取。
而我想说什么呢?---->不光是这个dubbo的Filter打印经过 Fastjson 序列化后的 Result<T>,在我们程序内部还有许多处也在使用 Result<T> 作为方法的返回值,也会打印经过 Fastjson 序列化后的 Result<T>。难道逐个改吗?
我是具备“懒人思维”的,我希望的是一劳永逸!
所以,有没有简单的办法,改一处就全改了。
世上无易事,用心求精进。只要不放弃,办法就会有。
解决办法是 在 Result<T>本身做文章,利用Fastjson的两个成员 -- c.a.f.serializer.ObjectSerializer 和 c.a.f.annotation.JSONField 。
Fastjson的ObjectSerializer 是Fastjson的序列化器接口,相应地,ObjectDeserializer是反序列化接口。在c.a.f.serializer包下,有许多默认的XxxCoDoc或XxxSerializer实现,如StringCodec、ToStringSerializer、IntegerCodec、EnumSerializer等。(Codec:CoDec,Compressor Decompressor,压缩解压缩器,编码解码器) XxxCodec会同时实现 ObjectSerializer 和 ObjectDeserializer两个接口,XxxSerializer只实现ObjectSerializer接口。
ObjectSerializer 和 ObjectDeserializer这两个接口类的javadoc,写明了如何使用它们。
首先,通过实现ObjectSerializer接口来自定义一个序列化器 StringAbbreviatingSerializer,用以截断属性值对应的字符串。

import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.serializer.JSONSerializer; import com.alibaba.fastjson.serializer.ObjectSerializer; import org.apache.commons.lang.StringUtils; import java.io.IOException; import java.lang.reflect.Type; public class StringAbbreviatingSerializer implements ObjectSerializer { /** * * @param serializer * @param object field的值 * @param fieldName field的name * @param fieldType field的类型,如java.lang.String * @param features * @throws IOException */ @Override public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException { serializer.write( StringUtils.abbreviate(JSON.toJSONString(object),100)); } }
然后,使用 c.a.f.annotation.JSONField 注解。
@JSONField(serializeUsing = StringAbbreviatingSerializer.class) private T data;
over了吗?我要补充—— 一并重写Result<T>的toString方法。彻底一劳永逸。pretty,graceful,pretty graceful. 优雅到极致!
@Data public class Result<T> implements Serializable { 。。。。 @Override public String toString() { return JSON.toJSONString(this); } }
这么改,对别的地方有影响吗?
要说明的是,加上这个Fastjson注解后,对与http响应 和 dubbo响应 是不会有任何影响的。为什么?因为这两者的序列化不是Fastjson。SpringMVC默认使用Jackson作为序列化工具;dubbo RPC默认启用的序列化方式是hessian2(实际不是原生的hessian2序列化,而是阿里修改过的hessian lite)。以上已亲测。
所以,放心用,放心使用Fastjson JSON#toJSONString(Result<T>) 去记录日志。妈妈再也不用担心我的日志爆屏了。
over!
附:
使用Fastjson提供的接口实现自定义的编解码器
在项目开发中经常会遇到一些业务需要对某些数据进行特殊的定制化处理,Fastjson为我们提供了接口可以用于实现自定义的编解码器来完成我们的业务要求。
ObjectSerializer和ObjectDeserializer分别是Fastjson的编码器和解码器接口。
当看到一些不好的代码时,会发现我还算优秀;当看到优秀的代码时,也才意识到持续学习的重要!--buguge
本文来自博客园,转载请注明原文链接:https://www.cnblogs.com/buguge/p/16837507.html