與一個簡單的算法不同,一個合格的系統不僅僅要求具有運行的高效和計算的准確,同時又必須兼顧穩定性、可靠性。其次,對於開發人員來說,又必須具有可拓展性和可維護性。各方面都必須很完善,這樣的一個系統才能稱得上是一個合格完美的系統。簡單的站在開發人員的角度分析,比較重視的是系統的可維護性,畢竟開發人員直面的是系統的代碼實現。一個代碼結構冗雜、模塊設計混亂、命名“異想天開”的系統對於開發者來說簡直到了咬牙切齒的地步!不能忍!堅決不能忍!所以在平時的開發過程中就要時刻注意着系統的實現機制,從宏觀設計和微觀實現上面同時進行精雕細琢。前幾天看到阿里巴巴出的《Java 開發規范》,這里附上鏈接,建議大家看看。
說到可維護性及不得不涉及到系統監控和Bug的快速定位。在開發階段還比較容易對系統進行監控,一般都會在本機上對系統的運行進行實時監控。而對於bug的定位,開發者都會熟練使用debug功能進行bug定位,更有甚者通過多年的開發經驗根據系統的異常信息直接能分析出來Bug產生的原因、位置以及解決方案。但是,系統畢竟是人開發的,我們無法預料到在運行中會出行什么想不到的問題,即使在各種測試中沒有出現,但是也無法保證不會出現一些意想不到的問題。那么在系統運行期間如果產生問題出現異常且無法在測試環境中重現,我們又該如何快速、准確地對bug進行定位分析和解決呢?舉個親身的例子吧,公司一套設備監控系統,用來對上萬個節點進行實時監控,如果該節點有異常(比如溫度過高、電壓過高等)則向系統進行發出告警信息。在開發環境中只有五十多個設備被安全(不會產生什么告警)的放在機房中供開發和測試使用,這種測試環境根本無法模擬實際環境。系統在測試中沒有出現過什么大的問題。然而,在實際的運行環境中,偶爾發現系統的一個模塊功能會喪失,失去告警接收的功能。在本地測試的時候從來沒有發現過類似問題,但是部署在實際環境中就會有發生。我們不可能實時的24小時對系統進行人工監控,那么該如何定位功能喪失的原因呢?這時,對於系統日志來說就“是時候表演真正的技術了”。后來通過系統日志分析之后發現功能的喪失是由於大量的告警同時上報導致數據庫鏈接不夠用產生大量connectionTimeout進而導致OOM異常,這個模塊徹底死了。可見,日志對於運行環境中系統的監控和問題定位是至關重要的,在系統設計、開發和實現的過程中必須時刻注意着log的輸出,這將會對於日后的系統監控和異常分析起至關重要的作用!
那么,在系統日志實現上應該注意哪些問題呢?如何做到不濫用日志、減少大量無用信息,讓日志記錄足夠精簡明了?
第一:系統的哪些運行信息需要進行日志記錄?
1、功能模塊的啟動和結束(完整的系統由多個功能模塊組成,每個模塊負責不同的功能,因此需要對模塊的啟動和結束進行監控。是否在需要的時機正常加載該模塊?又是否在退出結束的時候正常完成結束操作,正常退出?)
2、用戶的登錄和退出(哪位用戶在什么時間通過什么IP登錄或退出了系統)
3、系統的關鍵性操作(數據庫鏈接信息、網絡通信的成功與失敗等)
4、系統運行期間的異常信息(NPE、OOM以及其他的超時、轉換異常等)
5、關鍵性方法的進入和退出(一些重要業務處理的方法,在進入和結束的時候需要有日志信息進行輸出)
……
……
第二:什么樣的日志格式有助於開發者進行明確的分析?
日志信息要求必須精簡,過多的無用信息不但對系統分析起不到什么作用,反而會增加系統的運行壓力、消耗系統的運行資源。這里有個日志模板,可供參考。
時間-[線程名][日志等級]-日志輸出位置(全類名,可以精確到方法名):日志信息
2013-09-04 10:49:20.296-[Thread-initRedis21504][INFO]-com.shanghai.LoginController.initLogInfo:LingMing[User] is logining
日志信息的內容可以根據不同的情況進行設計,但是前面的時間到日志輸出位置必須要保證完整性,這樣才有利於日志的分析。
第三:如何對不同的日志信息進行等級划分?
日志等級通常分為四種:DEBUG、INFO、WARN、ERROR
DEBUG:系統調試信息,通常用於開發過程中對系統運行情況的監控,在實際運行環境中不進行輸出。
INFO:系統運行的關鍵性信息,通常用於對系統運行情況的監控。
WARN:告警信息,系統存在潛在的問題,有可能引起運行異常,但此時並未產生異常。
ERROR:系統錯誤信息,需要進行及時處理和優化。
這里列出來了各種等級的日志信息,在開發過程中哪些信息需要設置為哪種等級有賴於開發者的自己判斷,這里只是給個建議。
日志的管理是系統很重要的一部分,千萬不可忽略其重要性。完整的日志將會在系統維護中起着異常重要的作用,就好像磨刀不誤砍柴工一樣,日志就像對系統進行分析的工具,工具便捷了,對系統分析起來就能達到事半功倍的效果。開發者必須要明白日志的價值和意義,萬萬不可忽略和輕視,並且在系統設計之初就建議制定一份關於日志管理的說明規范,明確哪些方法、操作必須進行日志輸出,在進行開發過程中也要嚴格遵守。
至於日志輸出的實現,不同的開發語言有不同的日志管理框架,同一種語言也有很多不同的日志管理方案,這里就不再進行贅述。本文目的就是想讓開發者看到日志的重要性。
下面附帶一份log4j的配置文件,便於日后進行設計的時候查找。轉載自:log4j.properties配置詳解與實例-全部測試通過
################################################################################ #①配置根Logger,其語法為: # #log4j.rootLogger = [level],appenderName,appenderName2,... #level是日志記錄的優先級,分為OFF,TRACE,DEBUG,INFO,WARN,ERROR,FATAL,ALL ##Log4j建議只使用四個級別,優先級從低到高分別是DEBUG,INFO,WARN,ERROR #通過在這里定義的級別,您可以控制到應用程序中相應級別的日志信息的開關 #比如在這里定義了INFO級別,則應用程序中所有DEBUG級別的日志信息將不被打印出來 #appenderName就是指定日志信息輸出到哪個地方。可同時指定多個輸出目的 ################################################################################ ################################################################################ #②配置日志信息輸出目的地Appender,其語法為: # #log4j.appender.appenderName = fully.qualified.name.of.appender.class #log4j.appender.appenderName.optionN = valueN # #Log4j提供的appender有以下幾種: #1)org.apache.log4j.ConsoleAppender(輸出到控制台) #2)org.apache.log4j.FileAppender(輸出到文件) #3)org.apache.log4j.DailyRollingFileAppender(每天產生一個日志文件) #4)org.apache.log4j.RollingFileAppender(文件大小到達指定尺寸的時候產生一個新的文件) #5)org.apache.log4j.WriterAppender(將日志信息以流格式發送到任意指定的地方) # #1)ConsoleAppender選項屬性 # -Threshold = DEBUG:指定日志消息的輸出最低層次 # -ImmediateFlush = TRUE:默認值是true,所有的消息都會被立即輸出 # -Target = System.err:默認值System.out,輸出到控制台(err為紅色,out為黑色) # #2)FileAppender選項屬性 # -Threshold = INFO:指定日志消息的輸出最低層次 # -ImmediateFlush = TRUE:默認值是true,所有的消息都會被立即輸出 # -File = C:\log4j.log:指定消息輸出到C:\log4j.log文件 # -Append = FALSE:默認值true,將消息追加到指定文件中,false指將消息覆蓋指定的文件內容 # -Encoding = UTF-8:可以指定文件編碼格式 # #3)DailyRollingFileAppender選項屬性 # -Threshold = WARN:指定日志消息的輸出最低層次 # -ImmediateFlush = TRUE:默認值是true,所有的消息都會被立即輸出 # -File = C:\log4j.log:指定消息輸出到C:\log4j.log文件 # -Append = FALSE:默認值true,將消息追加到指定文件中,false指將消息覆蓋指定的文件內容 # -DatePattern='.'yyyy-ww:每周滾動一次文件,即每周產生一個新的文件。還可以按用以下參數: # '.'yyyy-MM:每月 # '.'yyyy-ww:每周 # '.'yyyy-MM-dd:每天 # '.'yyyy-MM-dd-a:每天兩次 # '.'yyyy-MM-dd-HH:每小時 # '.'yyyy-MM-dd-HH-mm:每分鍾 # -Encoding = UTF-8:可以指定文件編碼格式 # #4)RollingFileAppender選項屬性 # -Threshold = ERROR:指定日志消息的輸出最低層次 # -ImmediateFlush = TRUE:默認值是true,所有的消息都會被立即輸出 # -File = C:/log4j.log:指定消息輸出到C:/log4j.log文件 # -Append = FALSE:默認值true,將消息追加到指定文件中,false指將消息覆蓋指定的文件內容 # -MaxFileSize = 100KB:后綴可以是KB,MB,GB.在日志文件到達該大小時,將會自動滾動.如:log4j.log.1 # -MaxBackupIndex = 2:指定可以產生的滾動文件的最大數 # -Encoding = UTF-8:可以指定文件編碼格式 ################################################################################ ################################################################################ #③配置日志信息的格式(布局),其語法為: # #log4j.appender.appenderName.layout = fully.qualified.name.of.layout.class #log4j.appender.appenderName.layout.optionN = valueN # #Log4j提供的layout有以下幾種: #5)org.apache.log4j.HTMLLayout(以HTML表格形式布局) #6)org.apache.log4j.PatternLayout(可以靈活地指定布局模式) #7)org.apache.log4j.SimpleLayout(包含日志信息的級別和信息字符串) #8)org.apache.log4j.TTCCLayout(包含日志產生的時間、線程、類別等等信息) #9)org.apache.log4j.xml.XMLLayout(以XML形式布局) # #5)HTMLLayout選項屬性 # -LocationInfo = TRUE:默認值false,輸出java文件名稱和行號 # -Title=Struts Log Message:默認值 Log4J Log Messages # #6)PatternLayout選項屬性 # -ConversionPattern = %m%n:格式化指定的消息(參數意思下面有) # #9)XMLLayout選項屬性 # -LocationInfo = TRUE:默認值false,輸出java文件名稱和行號 # #Log4J采用類似C語言中的printf函數的打印格式格式化日志信息,打印參數如下: # %m 輸出代碼中指定的消息 # %p 輸出優先級,即DEBUG,INFO,WARN,ERROR,FATAL # %r 輸出自應用啟動到輸出該log信息耗費的毫秒數 # %c 輸出所屬的類目,通常就是所在類的全名 # %t 輸出產生該日志事件的線程名 # %n 輸出一個回車換行符,Windows平台為“\r\n”,Unix平台為“\n” # %d 輸出日志時間點的日期或時間,默認格式為ISO8601,也可以在其后指定格式 # 如:%d{yyyy年MM月dd日 HH:mm:ss,SSS},輸出類似:2012年01月05日 22:10:28,921 # %l 輸出日志事件的發生位置,包括類目名、發生的線程,以及在代碼中的行數 # 如:Testlog.main(TestLog.java:10) # %F 輸出日志消息產生時所在的文件名稱 # %L 輸出代碼中的行號 # %x 輸出和當前線程相關聯的NDC(嵌套診斷環境),像java servlets多客戶多線程的應用中 # %% 輸出一個"%"字符 # # 可以在%與模式字符之間加上修飾符來控制其最小寬度、最大寬度、和文本的對齊方式。如: # %5c: 輸出category名稱,最小寬度是5,category<5,默認的情況下右對齊 # %-5c:輸出category名稱,最小寬度是5,category<5,"-"號指定左對齊,會有空格 # %.5c:輸出category名稱,最大寬度是5,category>5,就會將左邊多出的字符截掉,<5不會有空格 # %20.30c:category名稱<20補空格,並且右對齊,>30字符,就從左邊交遠銷出的字符截掉 ################################################################################ ################################################################################ #④指定特定包的輸出特定的級別 #log4j.logger.org.springframework=DEBUG ################################################################################ #OFF,systemOut,logFile,logDailyFile,logRollingFile,logMail,logDB,ALL log4j.rootLogger =ALL,systemOut,logFile,logDailyFile,logRollingFile,logMail,logDB #輸出到控制台 log4j.appender.systemOut = org.apache.log4j.ConsoleAppender log4j.appender.systemOut.layout = org.apache.log4j.PatternLayout log4j.appender.systemOut.layout.ConversionPattern = [%-5p][%-22d{yyyy/MM/dd HH:mm:ssS}][%l]%n%m%n log4j.appender.systemOut.Threshold = DEBUG log4j.appender.systemOut.ImmediateFlush = TRUE log4j.appender.systemOut.Target = System.out #輸出到文件 log4j.appender.logFile = org.apache.log4j.FileAppender log4j.appender.logFile.layout = org.apache.log4j.PatternLayout log4j.appender.logFile.layout.ConversionPattern = [%-5p][%-22d{yyyy/MM/dd HH:mm:ssS}][%l]%n%m%n log4j.appender.logFile.Threshold = DEBUG log4j.appender.logFile.ImmediateFlush = TRUE log4j.appender.logFile.Append = TRUE log4j.appender.logFile.File = ../Struts2/WebRoot/log/File/log4j_Struts.log log4j.appender.logFile.Encoding = UTF-8 #按DatePattern輸出到文件 log4j.appender.logDailyFile = org.apache.log4j.DailyRollingFileAppender log4j.appender.logDailyFile.layout = org.apache.log4j.PatternLayout log4j.appender.logDailyFile.layout.ConversionPattern = [%-5p][%-22d{yyyy/MM/dd HH:mm:ssS}][%l]%n%m%n log4j.appender.logDailyFile.Threshold = DEBUG log4j.appender.logDailyFile.ImmediateFlush = TRUE log4j.appender.logDailyFile.Append = TRUE log4j.appender.logDailyFile.File = ../Struts2/WebRoot/log/DailyFile/log4j_Struts log4j.appender.logDailyFile.DatePattern = '.'yyyy-MM-dd-HH-mm'.log' log4j.appender.logDailyFile.Encoding = UTF-8 #設定文件大小輸出到文件 log4j.appender.logRollingFile = org.apache.log4j.RollingFileAppender log4j.appender.logRollingFile.layout = org.apache.log4j.PatternLayout log4j.appender.logRollingFile.layout.ConversionPattern = [%-5p][%-22d{yyyy/MM/dd HH:mm:ssS}][%l]%n%m%n log4j.appender.logRollingFile.Threshold = DEBUG log4j.appender.logRollingFile.ImmediateFlush = TRUE log4j.appender.logRollingFile.Append = TRUE log4j.appender.logRollingFile.File = ../Struts2/WebRoot/log/RollingFile/log4j_Struts.log log4j.appender.logRollingFile.MaxFileSize = 1MB log4j.appender.logRollingFile.MaxBackupIndex = 10 log4j.appender.logRollingFile.Encoding = UTF-8 #用Email發送日志 log4j.appender.logMail = org.apache.log4j.net.SMTPAppender log4j.appender.logMail.layout = org.apache.log4j.HTMLLayout log4j.appender.logMail.layout.LocationInfo = TRUE log4j.appender.logMail.layout.Title = Struts2 Mail LogFile log4j.appender.logMail.Threshold = DEBUG log4j.appender.logMail.SMTPDebug = FALSE log4j.appender.logMail.SMTPHost = SMTP.163.com log4j.appender.logMail.From = xly3000@163.com log4j.appender.logMail.To = xly3000@gmail.com #log4j.appender.logMail.Cc = xly3000@gmail.com #log4j.appender.logMail.Bcc = xly3000@gmail.com log4j.appender.logMail.SMTPUsername = xly3000 log4j.appender.logMail.SMTPPassword = 1234567 log4j.appender.logMail.Subject = Log4j Log Messages #log4j.appender.logMail.BufferSize = 1024 #log4j.appender.logMail.SMTPAuth = TRUE #將日志登錄到MySQL數據庫 log4j.appender.logDB = org.apache.log4j.jdbc.JDBCAppender log4j.appender.logDB.layout = org.apache.log4j.PatternLayout log4j.appender.logDB.Driver = com.mysql.jdbc.Driver log4j.appender.logDB.URL = jdbc:mysql://127.0.0.1:3306/xly log4j.appender.logDB.User = root log4j.appender.logDB.Password = 123456 log4j.appender.logDB.Sql = INSERT INTOT_log4j(project_name,create_date,level,category,file_name,thread_name,line,all_category,message)values('Struts2','%d{yyyy-MM-ddHH:mm:ss}','%p','%c','%F','%t','%L','%l','%m')
