引言:
日志大家都再熟悉不過了,日常開發(fā)中經(jīng)常要用到,甲方接口人最喜歡對我說的話就是“趕緊去查一下日志,快點!”,說話的同時瞪著個銅鈴大的眼睛。
就你接口人知道日志嗎?還要你指揮啊,我心里可真來勁,但是沒辦法誰叫我是乙方的,我想大口喘氣,也得拿手捂著,兄弟們都指著我好好說話呢,我這話一出口,我這一眾兄弟這個月的績效恐怕就打水漂了。
就在我進行心里斗爭的時候,接口人又說了“什么時候能定位到問題myeclipse控制臺沒有日志,啥時候能解決?”。(甲方規(guī)定:超1小時通報、超過2小時扣分、超4小時約談領導、24小時沒搞定打包回家)。
下面恭迎今天的主角“日志”大神出場。
什么是日志
日志:記錄程序的運行軌跡,打印一下設定的信息,比如錯誤,很方便查找關鍵信息,快速定位解決問題。
Java程序員在開發(fā)項目時都是依賴debug來調(diào)試錯誤、跟蹤代碼來解決bug,比如我們開發(fā)工具/就有很強大錯誤調(diào)試功能,比如我自己就有4個版本的:
(我懷疑你在做廣告)
當然在開發(fā)環(huán)境下,要調(diào)試這些錯誤很容易,打好斷點跟蹤代碼就行了,但項目發(fā)布到了測試、生產(chǎn)環(huán)境怎么辦?不好意思你調(diào)試不了,甲方會斷了一切你可以這么做的念想。所以日志就派上用場了,日志打印的好,就能根據(jù)日志的軌跡快速定位并解決線上問題,反之你光憑分析代碼、分析數(shù)據(jù),腦細胞胡亂打架是沒法快速解決問題的,日志記錄的信息能夠很好的幫助開發(fā)人家快速定位到問題,并且解決。
記錄日志的方式
有很多記錄日志的方式,比如 log4j、、-、slf4j 等等,這里呢我就說說我們做項目經(jīng)常用到的幾種。
log4j
配置文章可以看我的文章Log4j配置詳解
我用的是log4j-1.2.15.jar 這個包,將其放在lib下
輸出到控制臺
log4j.配置
# DEBUG 日志優(yōu)先級,stdout(自定義名字) 代表日志輸出到那個地方
log4j.rootLogger= ERROR, stdout
# 設置日志輸出類型 appender負責控制日志記錄操作的輸出 ConsoleAppender:日志信息輸出到控制臺
log4j.appender.stdout= org.apache.log4j.ConsoleAppender
# 日志自定義格式
log4j.appender.stdout.layout= org.apache.log4j.PatternLayout
# 日志輸出格式為 優(yōu)先級、 [產(chǎn)生日志事件的線程名]、日志信息、換行
log4j.appender.stdout.layout.ConversionPattern= %5p [%t] %m%n
測試代碼
package com.log;
import org.apache.log4j.Logger;
public class Log4jTest
{
Logger logger = Logger.getLogger(this.getClass());
public void test()
{
logger.debug("debug print");
logger.info("info print");
logger.warn("warn print");
logger.error("error print");
}
/**
* @param args
*/
public static void main(String[] args)
{
new Log4jTest().test();
}
}
因配置的是ERROR,所以只輸出error的信息
輸出到日志文件
log4j.配置
# DEBUG 日志優(yōu)先級,stdout(自定義名字) 代表日志輸出到那個地方
log4j.rootLogger= ERROR, stdout ,myLog
# 設置日志輸出類型 appender負責控制日志記錄操作的輸出 ConsoleAppender:日志信息輸出到控制臺
log4j.appender.stdout= org.apache.log4j.ConsoleAppender
# 日志自定義格式
log4j.appender.stdout.layout= org.apache.log4j.PatternLayout
# 日志輸出格式為 優(yōu)先級、 [產(chǎn)生日志事件的線程名]、日志信息、換行
log4j.appender.stdout.layout.ConversionPattern= %5p [%t] %m%n
### 輸出到日志文件 ###
log4j.appender.myLog = org.apache.log4j.DailyRollingFileAppender
log4j.appender.myLog.File = E:\\testlog\\log4j\\server.log
log4j.appender.myLog.Append = true
## 只輸出DEBUG級別以上的日志
log4j.appender.myLog.Threshold = DEBUG
#'.'yyyy-MM-dd: 每天產(chǎn)生一個新的文件
log4j.appender.myLog.DatePattern = '.'yyyy-MM-dd
log4j.appender.myLog.layout = org.apache.log4j.PatternLayout
log4j.appender.myLog.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [%t:%r] - [%p] [%c{1}:%L] [%M] %m%n
log4j.additivity.myLog = false
測試代碼還是之前的,運行結(jié)果如下:
自定義文本方式
每天記錄一個日志文件,這個日志文件把今天比較重要的日志記錄下來。
每個日志文件記錄的形式(時間+組裝好的內(nèi)容),如下 :
是不是有個疑問,如果有多個服務器,日志是記錄到代碼執(zhí)行的這個服務器上了,外面訪問進來肯定會訪問到不同的服務器上了,日志文件也肯定存在多個咯myeclipse控制臺沒有日志,查問題的時候不就得一個個服務器的日志都翻一遍,煩人嗎不是,這個時候你請搞工程的兄弟吃個夜宵他就會告訴你,其實服務器是可以搞成共享一個目錄的,多個服務器訪問同一個日志文件也是可以的(具體我就不講了),但說實話這種方式我們現(xiàn)在也只是偶爾用一下。
示意代碼
package com.log;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
public class FileLog {
public static void writeLog(String file, String conent) {
BufferedWriter out = null;
try {
out = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(file, true)));
out.write(conent);
out.newLine();//換行
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* @param args
*/
public static void main(String[] args) {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
SimpleDateFormat dfTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date();
//當前時間 方便日志查看
String curTime = dfTime.format(date);
//yyyy-MM-dd格式的,為了每日更換日志文件
String curDay = df.format(date);
//組裝日志內(nèi)容
String text="日志內(nèi)容"+org.apache.commons.lang.RandomStringUtils.randomNumeric(4);
String logStr="["+curTime+"]"+text;
//指定日志文件路徑
String fileName="E:/testlog/"+curDay+".txt";
System.out.println("start");
//打印日志
writeLog(fileName, logStr);
System.out.println("end");
}
}
記錄到數(shù)據(jù)庫
我們用的最多的場景是接口,把收發(fā)報文記錄下來,如果接口發(fā)生錯誤的話,將錯誤的信息記錄成一條數(shù)據(jù)。
一旦發(fā)生接口調(diào)用爭議的實話,這個就派上用場了,場景:
對端是個大廠很大的那種(甲方都不吊的那種),你送了數(shù)據(jù)過去,然后對端說沒收到或者說收到的有問題,但人家又不說具體什么問題,也不給報文,就做甩手掌柜,接口人只好找你了(你懂的,柿子都找軟的捏),你如果沒有調(diào)用報文和返回報文的記錄的話,你就等著哭吧,挨罵你只能忍著,就算你確定你的程序沒問題也沒有用,你得有證據(jù),你就得想方設法的找報文,但如果要從日志找的話不是那么好找,甚至有時候找不到。
記錄到庫就很好找,根據(jù)工單號、主鍵或者發(fā)生時間等等,可以很容易發(fā)現(xiàn)問題、定位問題,然后迅速把發(fā)送的報文、返回的報文發(fā)給接口人,手一甩就剩下你心情愉悅的摸魚了。
我一般會記錄這些信息
來源系統(tǒng)
對端系統(tǒng)/自己的系統(tǒng)代號
業(yè)務
不同的模塊寫不同的編碼,方便區(qū)分
工單號
唯一標示即可,看自己系統(tǒng)的需求
方法名
調(diào)用的方法
類型
接收或者發(fā)送
狀態(tài)
成功或者失敗
報文
具體的報文信息
操作時間
創(chuàng)建時間
當然有可能會涉及到比較長的報文,所以我們設計的是有好幾個字段來存,不夠的就往后一個字段放,比如:
String xmlInfo = vo.getXmlInfo();
int xmlLength = xmlInfo.length();
int count = xmlLength / 1000;
if (count * 1000 < xmlLength)
count++;
String xmlArr[] = new String[count];
for (int i = 0; i < count; i++)
{
int endLength = (i + 1) * 1000;
if (endLength > xmlLength)
endLength = xmlLength;
xmlArr[i] = xmlInfo.substring(i * 1000, endLength);
if (i == 0)
vo.setXmlInfo1(xmlArr[i]);
else if (i == 1)
vo.setXmlInfo2(xmlArr[i]);
else if (i == 2)
vo.setXmlInfo3(xmlArr[i]);
else if (i == 3)
vo.setXmlInfo4(xmlArr[i]);
else if (i == 4)
vo.setXmlInfo5(xmlArr[i]);
else if (i == 5)
vo.setXmlInfo6(xmlArr[i]);
else if (i == 6)
vo.setXmlInfo7(xmlArr[i]);
else if (i == 7)
vo.setXmlInfo8(xmlArr[i]);
}
最后
通常情況下在程序日志里記錄一些比較有意義的數(shù)據(jù):程序啟動、登錄、退出、重要變量的狀態(tài)變化、重要數(shù)據(jù)的執(zhí)行記錄、接口調(diào)用報文的記錄、定時執(zhí)行相關任務的狀況 等等。