0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

一行l(wèi)og日志,引發(fā)了P1的線上事故

jf_ro2CN3Fa ? 來源:芋道源碼 ? 作者:芋道源碼 ? 2022-11-07 15:48 ? 次閱讀


線上事故回顧

前段時間同事新增了一個特別簡單的功能,晚上上線前review代碼時想到公司拼搏進(jìn)取的價值觀臨時他加一行 log 日志,覺得就一行簡單的日志基本上沒啥問題,結(jié)果剛上完線后一堆報警,趕緊回滾了代碼,找到問題刪除了添加日志的代碼,重新上線完畢。

基于 Spring Boot + MyBatis Plus + Vue & Element 實現(xiàn)的后臺管理系統(tǒng) + 用戶小程序,支持 RBAC 動態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能

  • 項目地址:https://gitee.com/zhijiantianya/ruoyi-vue-pro
  • 視頻教程:https://doc.iocoder.cn/video/

情景還原

?

定義了一個 CountryDTO

?

publicclassCountryDTO{
privateStringcountry;

publicvoidsetCountry(Stringcountry){
this.country=country;
}

publicStringgetCountry(){
returnthis.country;
}

publicBooleanisChinaName(){
returnthis.country.equals("中國");
}
}

?

定義測試類 FastJonTest

?

publicclassFastJonTest{
@Test
publicvoidtestSerialize(){
CountryDTOcountryDTO=newCountryDTO();
Stringstr=JSON.toJSONString(countryDTO);
System.out.println(str);
}
}

運行時報空指針錯誤:

64775822-5d7b-11ed-a3b6-dac502259ad0.png空指針

通過報錯信息可以看出來是序列化的過程中執(zhí)行了isChinaName()方法,這時候this.country變量為空,那么問題來了:

  • 序列化為什么會執(zhí)行isChinaName()呢?
  • 引申一下,序列化過程中會執(zhí)行那些方法呢?

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現(xiàn)的后臺管理系統(tǒng) + 用戶小程序,支持 RBAC 動態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能

  • 項目地址:https://gitee.com/zhijiantianya/yudao-cloud
  • 視頻教程:https://doc.iocoder.cn/video/

源碼分析

通過 debug 觀察調(diào)用鏈路的堆棧信息

652d8e80-5d7b-11ed-a3b6-dac502259ad0.png65920e46-5d7b-11ed-a3b6-dac502259ad0.jpg

調(diào)用鏈中的ASMSerializer_1_CountryDTO.writeFastJson使用asm技術(shù)動態(tài)生成了一個類ASMSerializer_1_CountryDTO。

?

asm技術(shù)其中一項使用場景就是通過到動態(tài)生成類用來代替java反射,從而避免重復(fù)執(zhí)行時的反射開銷

?

JavaBeanSerizlier序列化原理

通過下圖看出序列化的過程中,主要是調(diào)用JavaBeanSerializer類的write()方法。

65b3b3de-5d7b-11ed-a3b6-dac502259ad0.pngObjectSerializer實現(xiàn)類JavaBeanSerializer

JavaBeanSerializer主要是通過getObjectWriter()方法獲取,通過對getObjectWriter()執(zhí)行過程的調(diào)試,找到比較關(guān)鍵的com.alibaba.fastjson.serializer.SerializeConfig#createJavaBeanSerializer方法,進(jìn)而找到 com.alibaba.fastjson.util.TypeUtils#computeGetters

publicstaticListcomputeGetters(Classclazz,//
JSONTypejsonType,//
MapaliasMap,//
MapfieldCacheMap,//
booleansorted,//
PropertyNamingStrategypropertyNamingStrategy//
){
//省略部分代碼....
Method[]methods=clazz.getMethods();
for(Methodmethod:methods){
//省略部分代碼...
if(method.getReturnType().equals(Void.TYPE)){
continue;
}
if(method.getParameterTypes().length!=0){
continue;
}
//省略部分代碼...
JSONFieldannotation=TypeUtils.getAnnotation(method,JSONField.class);
//省略部分代碼...
if(annotation!=null){
if(!annotation.serialize()){
continue;
}
if(annotation.name().length()!=0){
//省略部分代碼...
}
}
if(methodName.startsWith("get")){
//省略部分代碼...
}
if(methodName.startsWith("is")){
//省略部分代碼...
}
}
}

從代碼中大致分為三種情況:

  • @JSONField(.serialize = false, name = "xxx")注解
  • getXxx() : get開頭的方法
  • isXxx():is開頭的方法

序列化流程圖

65d61064-5d7b-11ed-a3b6-dac502259ad0.png序列化流程圖

示例代碼

/**
*case1:@JSONField(serialize=false)
*case2:getXxx()返回值為void
*case3:isXxx()返回值不等于布爾類型
*case4:@JSONType(ignores="xxx")
*/
@JSONType(ignores="otherName")
publicclassCountryDTO{
privateStringcountry;

publicvoidsetCountry(Stringcountry){
this.country=country;
}

publicStringgetCountry(){
returnthis.country;
}

publicstaticvoidqueryCountryList(){
System.out.println("queryCountryList()執(zhí)行!!");
}

publicBooleanisChinaName(){
System.out.println("isChinaName()執(zhí)行!!");
returntrue;
}

publicStringgetEnglishName(){
System.out.println("getEnglishName()執(zhí)行!!");
return"lucy";
}

publicStringgetOtherName(){
System.out.println("getOtherName()執(zhí)行!!");
return"lucy";
}

/**
*case1:@JSONField(serialize=false)
*/
@JSONField(serialize=false)
publicStringgetEnglishName2(){
System.out.println("getEnglishName2()執(zhí)行!!");
return"lucy";
}

/**
*case2:getXxx()返回值為void
*/
publicvoidgetEnglishName3(){
System.out.println("getEnglishName3()執(zhí)行!!");
}

/**
*case3:isXxx()返回值不等于布爾類型
*/
publicStringisChinaName2(){
System.out.println("isChinaName2()執(zhí)行!!");
return"isChinaName2";
}
}

運行結(jié)果為:

isChinaName()執(zhí)行!!
getEnglishName()執(zhí)行!!
{"chinaName":true,"englishName":"lucy"}

代碼規(guī)范

可以看出來序列化的規(guī)則還是很多的,比如有時需要關(guān)注返回值,有時需要關(guān)注參數(shù)個數(shù),有時需要關(guān)注@JSONType注解,有時需要關(guān)注@JSONField注解;當(dāng)一個事物的判別方式有多種的時候,由于團(tuán)隊人員掌握知識點的程度不一樣,這個方差很容易導(dǎo)致代碼問題,所以盡量有一種推薦方案。

這里推薦使用@JSONField(serialize = false)來顯式的標(biāo)注方法不參與序列化,下面是使用@JSONField注解后的代碼,是不是一眼就能看出來哪些方法不需要參與序列化了。

publicclassCountryDTO{
privateStringcountry;

publicvoidsetCountry(Stringcountry){
this.country=country;
}

publicStringgetCountry(){
returnthis.country;
}

@JSONField(serialize=false)
publicstaticvoidqueryCountryList(){
System.out.println("queryCountryList()執(zhí)行!!");
}

publicBooleanisChinaName(){
System.out.println("isChinaName()執(zhí)行!!");
returntrue;
}

publicStringgetEnglishName(){
System.out.println("getEnglishName()執(zhí)行!!");
return"lucy";
}

@JSONField(serialize=false)
publicStringgetOtherName(){
System.out.println("getOtherName()執(zhí)行!!");
return"lucy";
}

@JSONField(serialize=false)
publicStringgetEnglishName2(){
System.out.println("getEnglishName2()執(zhí)行!!");
return"lucy";
}

@JSONField(serialize=false)
publicvoidgetEnglishName3(){
System.out.println("getEnglishName3()執(zhí)行!!");
}

@JSONField(serialize=false)
publicStringisChinaName2(){
System.out.println("isChinaName2()執(zhí)行!!");
return"isChinaName2";
}
}

三個頻率高的序列化的情況

65fbca66-5d7b-11ed-a3b6-dac502259ad0.png三個頻率高的序列化的情況

以上流程基本遵循,發(fā)現(xiàn)問題 --> 原理分析 --> 解決問題 --> 升華(編程規(guī)范)。

  • 圍繞業(yè)務(wù)上:解決問題 -> 如何選擇一種好的額解決方案 -> 好的解決方式如何擴(kuò)展 n 個系統(tǒng)應(yīng)用;
  • 圍繞技術(shù)上:解決單個問題,順著單個問題掌握這條線上的原理。

但其實這段代碼我并不滿意,原因是和 FastJson 依賴太高了。我想要的效果是,不依賴任何特定的 JSON 序列化框架。當(dāng)我需要替換掉它的時候,隨時可以替換掉。

并且在寫代碼時,不要過于依賴日志。打日志只需要打緊要且關(guān)鍵的信息即可,不要什么日志都打,我曾見過一個系統(tǒng),一個小時,把 128G 磁盤跑滿的管理系統(tǒng)。幾乎沒啥并發(fā),但幾乎每個請求都輸出幾 M 的日志,這件事我后面會單獨拿出來講講。

關(guān)于@JSONField@JSONType等特性注解,后面我會在團(tuán)隊內(nèi)規(guī)范并給出新的解耦方案,把它們移除掉。



審核編輯 :李倩


聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 代碼
    +關(guān)注

    關(guān)注

    30

    文章

    4671

    瀏覽量

    67770
  • 日志
    +關(guān)注

    關(guān)注

    0

    文章

    129

    瀏覽量

    10593

原文標(biāo)題:一行l(wèi)og日志,引發(fā)了P1的線上事故

文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    光伏互感器p1p2方向的區(qū)別

    光伏互感器是種用于測量光伏系統(tǒng)中電流和電壓的設(shè)備,它在光伏發(fā)電系統(tǒng)中起著至關(guān)重要的作用。光伏互感器的P1P2方向是指互感器的輸入端和輸出端的方向,它們對于互感器的正常工作和測量精度具有重要
    的頭像 發(fā)表于 08-21 18:21 ?933次閱讀

    清華PE產(chǎn)投匯一行赴谷東科技考察與交流

    日前,PE產(chǎn)投匯一行赴國家級專精特新小巨人校友企業(yè)——谷東科技北京公司進(jìn)行考察與交流。
    的頭像 發(fā)表于 08-13 09:45 ?424次閱讀

    在VSCODE終端make時遇到錯誤要一行一行看然后定位,可以直接跳轉(zhuǎn)點擊或者VSCODE定位錯誤嗎?

    每次在VSCODE終端make時遇到錯誤要一行一行看然后定位,可以直接跳轉(zhuǎn)點擊或者VSCODE直接定位錯誤嗎?能給個指引鏈接嗎?謝謝!
    發(fā)表于 06-25 07:37

    如何檢查日志中是否有類似cm_cy_log_msg( CYLF_MIDDLEWARE, CY_LOG_ERR) 的內(nèi)容?

    運行,有什么方法可以檢查 cm_cy_log_msg 生成的日志? 像這樣: cm_cy_log_msg( CYLF_MIDDLEWARE, CY_LOG_ERR,\"Invalid
    發(fā)表于 05-31 12:45

    甘肅考察團(tuán)一行蒞臨拓維信息參觀調(diào)研

    4月12至13日,甘肅考察團(tuán)一行蒞臨拓維信息調(diào)研,深入
    的頭像 發(fā)表于 04-18 08:14 ?332次閱讀
    甘肅考察團(tuán)<b class='flag-5'>一行</b>蒞臨拓維信息參觀調(diào)研

    python多行數(shù)據(jù)合并成一行

    一行: merged_line = line 1 + " " + line 2 + " " + line 3 print (merged_line) 輸出結(jié)果將是: This is line 1. This is line 2
    的頭像 發(fā)表于 11-24 09:48 ?1786次閱讀

    python如何讓多行輸出為一行

    Python中有多種方法可以將多行輸出改為一行輸出。 種方法是使用end參數(shù)將換行符替換為其他字符。默認(rèn)情況下,print函數(shù)會在輸出的結(jié)尾處自動添加個換行符,導(dǎo)致每個print語句輸出
    的頭像 發(fā)表于 11-24 09:45 ?5203次閱讀

    python如何將多行合并成一行

    在Python中,有多種方法可以將多行合并成一行。以下是詳細(xì)解釋和示例: 方法:使用字符串的replace()方法 你可以使用字符串的replace()方法來刪除換行符并將多行合并為一行。首先,你
    的頭像 發(fā)表于 11-24 09:42 ?4090次閱讀

    logcat命令抓取日志方法

    :代表每份文件日志最大size為1024KB,也就是1M -n:日志輸出最大數(shù)目,最多是n+1份 -n 5:日志文件輸出最多5+
    的頭像 發(fā)表于 11-23 17:31 ?897次閱讀
    logcat命令抓取<b class='flag-5'>日志</b>方法

    Android日志與logd交互過程

    2.2.3 Android日志與logd交互過程 2.2.3.1 Android日志傳遞給logd Android app層或framework層,通過調(diào)用Log/Slog/Rlog中d方法打印
    的頭像 發(fā)表于 11-23 17:06 ?751次閱讀
    Android<b class='flag-5'>日志</b>與logd交互過程

    Android開發(fā)中的日志接口介紹

    _ID_MAIN、LOG_ID_RADIO、LOG_ID_EVENTS、LOG_ID_SYSTEM、 LOG_ID_CRASH。 1.1.1 日志
    的頭像 發(fā)表于 11-23 16:27 ?904次閱讀
    Android開發(fā)中的<b class='flag-5'>日志</b>接口介紹

    Log4cpp優(yōu)勢及優(yōu)點

    1、log4cpp概述 Log4cpp是個開源的C++類庫,它提供了C++程序中使用日志和跟蹤調(diào)試的功能,它的優(yōu)點如下: 提供應(yīng)用程序運行
    的頭像 發(fā)表于 11-09 14:27 ?549次閱讀
    <b class='flag-5'>Log</b>4cpp優(yōu)勢及優(yōu)點

    C++異步日志實踐

    個高效可拓展的異步C++日志庫:RING LOG,本文分享了了其設(shè)計方案與技術(shù)原理等內(nèi)容 導(dǎo)論 同步日志與缺點 傳統(tǒng)的日志也叫同步
    的頭像 發(fā)表于 11-09 10:29 ?531次閱讀
    C++異步<b class='flag-5'>日志</b>實踐

    保護(hù)Log4j日志中的敏感數(shù)據(jù),兩步搞定!

    log4j在準(zhǔn)備添加日志消息時調(diào)用此方法。在MaskingAppender類中,我們覆蓋這個方法來攔截日志消息,使用maskSensitiveData()方法對敏感數(shù)據(jù)應(yīng)用masking,然后將修改后的消息傳遞給超類的appen
    的頭像 發(fā)表于 10-18 16:03 ?565次閱讀

    Python寫入到日志文件完整代碼

    寫入到日志文件 上面我們說的是將日志打印到控制臺中,但是我們Python代碼寫完并且在運行當(dāng)中后,我們就不可能這樣玩了,所以我們需要將日志保存到
    的頭像 發(fā)表于 10-07 11:50 ?1100次閱讀
    Python寫入到<b class='flag-5'>日志</b>文件完整代碼