在java專案中使用log4j的例項
環境
作業系統:win7
log4j2版本: 2.8.2
準備
下載jar
包
把jar包放入到專案中去
開始使用
假設我們要使用log4j2
,我們一般是先宣告成一個靜態成員變數:
private static final Logger logger = LogManager.getLogger(MyApp.class);
// 或者
private static final Logger logger = LogManager.getLogger(MyApp.class.getName());
宣告好logger
後,我們就可以開始使用它了。
假設我們有這麼一個程式:
package test; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class Hello { private static Logger logger = LogManager.getLogger(Hello.class.getName()); public void getHello() { logger.entry(); logger.trace("我是trace"); logger.info("我是info資訊"); logger.error("我是error"); logger.fatal("我是fatal"); logger.trace("退出程式."); logger.exit(); } public static void main(String[] args) { new Hello().getHello(); } }
如果沒有自定義配置檔案,執行上面的方法後,在控制檯顯示如下:
10:45:05.641 [main] ERROR test.Hello - 我是error
10:45:05.644 [main] FATAL test.Hello - 我是fatal
從結果上看出,只有>=error級別的日誌打印出來了。
這是因為Log4j有一個預設的配置,它的日誌級別是ERROR,輸出只有控制檯。
如果我已經定義好了日誌,我把日誌級別改成了TRACE,輸出會變成下面這樣:
10:48:21.326 [main] TRACE test.Hello - Enter 10:48:21.330 [main] TRACE test.Hello - 我是trace 10:48:21.331 [main] INFO test.Hello - 我是info資訊 10:48:21.331 [main] ERROR test.Hello - 我是error 10:48:21.331 [main] FATAL test.Hello - 我是fatal 10:48:21.331 [main] TRACE test.Hello - 退出程式. 10:48:21.331 [main] TRACE test.Hello - Exit
配置檔案
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
只需要把上面level
改為trace
就可以,看到剛才的效果!
由於這篇是簡單使用,就講這些,下篇接著講。
配置
Log4j 2的配置可以通過4種方式中的1種完成:
1、通過使用XML,JSON,YAML或屬性格式編寫的配置檔案。
2、以程式設計方式,通過建立一個ConfigurationFactory和配置實現。
3、以程式設計方式,通過呼叫配置介面中公開的API將元件新增到預設配置。
4、通過程式設計方式,通過呼叫內部Logger類的方法。
自動配置
Log4j能夠在初始化期間自動配置自身。
當Log4j啟動時,將找到所有ConfigurationFactory外掛,並按照從最高到最低的加權順序進行排列。
交付時,Log4j包含四個ConfigurationFactory實現:一個用於JSON,一個用於YAML,一個用於properties,一個用於XML。
1、Log4j將檢查log4j.configurationFile
系統屬性,如果設定,將嘗試使用與副檔名匹配的ConfigurationFactory載入配置。
2、如果沒有設定系統屬性,則properties ConfigurationFactory
將在類路徑中查詢log4j2-test.properties
。
3、如果沒有找到這樣的檔案,YAML ConfigurationFactory
將在類路徑中查詢log4j2-test.yaml
或log4j2-test.yml
。
4、如果沒有找到這樣的檔案,JSON ConfigurationFactory
將在類路徑中查詢log4j2-test.json
或log4j2-test.jsn
。
5、如果沒有找到這樣的檔案,XML ConfigurationFactory
將在類路徑中查詢log4j2-test.xml
。
6、如果找不到測試檔案,則properties ConfigurationFactory
將在類路徑中查詢log4j2.properties
。
7、如果無法找到屬性檔案,則YAML ConfigurationFactory
將在類路徑上查詢log4j2.yaml
或log4j2.yml
。
8、如果無法找到YAML
檔案,則JSON ConfigurationFactory
將在類路徑上查詢log4j2.json
或log4j2.jsn
。
9、如果無法找到JSON
檔案,則XML ConfigurationFactory
將嘗試在類路徑上找到log4j2.xml
。
10、如果沒有找到配置檔案,將使用DefaultConfiguration
。這將導致日誌輸出轉到控制檯。
日誌級別
log4j規定了預設的幾個級別:trace<debug<info<warn<error<fatal
等。這裡要說明一下:
①級別之間是包含的關係,意思是如果你設定日誌級別是trace,則大於等於這個級別的日誌都會輸出。
②基本上預設的級別沒多大區別,就是一個預設的設定。你可以通過它的API自己定義級別。你也可以隨意呼叫這些方法,不過你要在配置檔案裡面好好處理了,否則就起不到日誌的作用了,而且也不易讀,相當於一個規範,你要完全定義一套也可以,不用沒多大必要。
③這不同的級別的含義大家都很容易理解,這裡就簡單介紹一下:
level | 描述 |
---|---|
trace | 是追蹤,就是程式推進以下,你就可以寫個trace輸出,所以trace應該會特別多,不過沒關係,我們可以設定最低日誌級別不讓他輸出。 |
debug | 除錯麼,我一般就只用這個作為最低級別,trace壓根不用。是在沒辦法就用eclipse或者idea的debug功能就好了麼。 |
info | 輸出一下你感興趣的或者重要的資訊,這個用的最多了。 |
warn | 有些資訊不是錯誤資訊,但是也要給程式設計師的一些提示,類似於eclipse中程式碼的驗證不是有error 和warn(不算錯誤但是也請注意,比如以下depressed的方法)。 |
error | 錯誤資訊。用的也比較多。 |
fatal | 級別比較高了。重大錯誤,這種級別你可以直接停止程式了,是不應該出現的錯誤麼!不用那麼緊張,其實就是一個程度的問題。 |
日誌使用
緊接上篇博文
例子1:
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="OFF">
<appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</appenders>
<loggers>
<!--我們只讓這個logger輸出trace資訊,其他的都是error級別-->
<!--
additivity開啟的話,由於這個logger也是滿足root的,所以會被列印兩遍。
-->
<logger name="cn.lsw.base.log4j2.Hello" level="trace" additivity="false">
<appender-ref ref="Console"/>
</logger>
<root level="error">
<appender-ref ref="Console"/>
</root>
</loggers>
</configuration>
先簡單介紹一下下面這個配置檔案。
1)根節點configuration,然後有兩個子節點:appenders和loggers(都是複數,意思就是可以定義很多個appender和logger了)(如果想詳細的看一下這個xml的結構,可以去jar包下面去找xsd檔案和dtd檔案)
2)appenders:這個下面定義的是各個appender,就是輸出了,有好多類別,這裡也不多說(容易造成理解和解釋上的壓力,一開始也未必能聽懂,等於白講),先看這個例子,只有一個Console,這些節點可不是隨便命名的,Console就是輸出控制檯的意思。然後就針對這個輸出設定一些屬性,這裡設定了PatternLayout就是輸出格式了,基本上是前面時間,執行緒,級別,logger名稱,log資訊等,差不多,可以自己去查他們的語法規則。
3)loggers下面會定義許多個logger,這些logger通過name進行區分,來對不同的logger配置不同的輸出,方法是通過引用上面定義的logger,注意,appender-ref引用的值是上面每個appender的name,而不是節點名稱。
這個例子為了說明什麼呢?我們要說說這個logger的name(名稱)了(前面有提到)。
name機制
我們看到配置檔案中的那個name
是非常重要的。這個name
要用好的,就不能隨便亂起。
(隨便用的話,那就隨便取名字)。這個機制很簡單,就類似於java package
一樣。
上篇中建立logger
物件的時候,名稱是通過Hello.class
或者Hello.class.getName()
這樣的方法。為什麼要這樣做呢?很重要的原因就是有所謂的繼承問題。比如 如果你給com.Hello
定義了一個logger
,那麼它也適用於com.Hello.base
這個logger
。名稱的繼承是通過(.)點號分隔的。然後你會返現上面的loggers
裡面有個子節點不是logger
而是root
,而且這個root
沒有name
屬性。
這個root
相當於根節點。你所有的logger
都適用於這個logger
,所以,即使你在很多類裡面通過類名.class.getName()
或者類名.class
得到很多的logger
,而且你也沒有在配置檔案中進行任何配置,它們也能夠都輸出,因為他們都繼承了root
的log
配置。
這種繼承的說法官網的解釋叫做logger 層次結構(Hierarchy)
官網的例子:
例如:Logger配置中name為com.foo是name為com.foo.Bar的父類。類似的有,java是java.util的父類,是java.util.Vector的祖先。
上面的那個配置檔案裡面還定義了一個logger
,他的名稱是cn.lsw.base.log4j2.Hello
,這個名稱其實就是通過前面的Hello.class.getName()
或者Hello.class
得到的。上面那個配置檔案,我們為了給它做單獨配置。意思是:只有cn.lsw.base.log4j2.Hello
這個logger
輸出trace
資訊。也就是它的日誌級別為trace
,其他的logger
則繼承root
的日誌配置,日誌級別為error
,只能打印出error
及以上級別的日誌。
那麼有人會問,你單獨配置的那個logger
不也是繼承了root
的配置麼,那這樣的話,豈不是會列印兩遍日誌? 這個問題確實是存在的。當然如果你設定了additivity=false
,就不會輸出兩遍。
我們再寫一個測試類:
(我們先對上面的配置檔案做下修改,一個是logger的name改為:test.Hello,第二把additivity=false
去掉或改為true)
package test;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Logger2Test {
private static Logger logger = LogManager.getLogger(Logger2Test.class.getName());
public static void main(String[] args){
logger.trace("start programe");
Hello hello = new Hello();
hello.getHello();
logger.trace("end programe");
}
}
- 結果就是:
2017-05-17 11:51:40.783 [main] TRACE test.Hello - Enter
2017-05-17 11:51:40.783 [main] TRACE test.Hello - Enter
2017-05-17 11:51:40.787 [main] TRACE test.Hello - 我是trace
2017-05-17 11:51:40.787 [main] TRACE test.Hello - 我是trace
2017-05-17 11:51:40.787 [main] INFO test.Hello - 我是info資訊
2017-05-17 11:51:40.787 [main] INFO test.Hello - 我是info資訊
2017-05-17 11:51:40.787 [main] ERROR test.Hello - 我是error
2017-05-17 11:51:40.787 [main] ERROR test.Hello - 我是error
2017-05-17 11:51:40.787 [main] FATAL test.Hello - 我是fatal
2017-05-17 11:51:40.787 [main] FATAL test.Hello - 我是fatal
2017-05-17 11:51:40.787 [main] TRACE test.Hello - 退出程式.
2017-05-17 11:51:40.787 [main] TRACE test.Hello - 退出程式.
2017-05-17 11:51:40.787 [main] TRACE test.Hello - Exit
2017-05-17 11:51:40.787 [main] TRACE test.Hello - Exit
我們可以看出主程式Logger2Test
並沒有trace
日誌輸出,因為它繼承了root
的日誌配置(error級別及以上)。而Hello
輸出了trace及以上級別的日誌,但是呢,每個都輸出了兩遍。為什麼會這樣呢?前面也說過預設所有的logger
都繼承root
的配置的。此時的Hello
既有自己單獨的配置,也有從root
那裡繼承下來的配置,所以會列印兩次。這樣的特性,在其name
的層次結構中也是同樣適用的,比如:
我建立三個logger
名稱為test
、test.Hello
和test.Hello.Hello2
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="OFF">
<appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
</Console>
</appenders>
<loggers>
<logger name="test.Hello" level="info" additivity="true">
<appender-ref ref="Console"/>
</logger>
<logger name="test.Hello" level="info" additivity="true">
<appender-ref ref="Console"/>
</logger>
<logger name="test.foo.Hello2" level="info" additivity="true">
<appender-ref ref="Console"/>
</logger>
<root level="error">
<appender-ref ref="Console" />
</root>
</loggers>
</configuration>
列印結果:
2017-05-17 14:18:09.388 [main] INFO test.Hello - 我是info資訊
2017-05-17 14:18:09.388 [main] INFO test.Hello - 我是info資訊
2017-05-17 14:18:09.388 [main] INFO test.Hello - 我是info資訊
2017-05-17 14:18:09.392 [main] ERROR test.Hello - 我是error
2017-05-17 14:18:09.392 [main] ERROR test.Hello - 我是error
2017-05-17 14:18:09.392 [main] ERROR test.Hello - 我是error
2017-05-17 14:18:09.392 [main] FATAL test.Hello - 我是fatal
2017-05-17 14:18:09.392 [main] FATAL test.Hello - 我是fatal
2017-05-17 14:18:09.392 [main] FATAL test.Hello - 我是fatal
2017-05-17 14:18:09.393 [main] INFO test.foo.Hello2 - 我是info資訊
2017-05-17 14:18:09.393 [main] INFO test.foo.Hello2 - 我是info資訊
2017-05-17 14:18:09.393 [main] INFO test.foo.Hello2 - 我是info資訊
2017-05-17 14:18:09.393 [main] ERROR test.foo.Hello2 - 我是error
2017-05-17 14:18:09.393 [main] ERROR test.foo.Hello2 - 我是error
2017-05-17 14:18:09.393 [main] ERROR test.foo.Hello2 - 我是error
2017-05-17 14:18:09.393 [main] FATAL test.foo.Hello2 - 我是fatal
2017-05-17 14:18:09.393 [main] FATAL test.foo.Hello2 - 我是fatal
2017-05-17 14:18:09.393 [main] FATAL test.foo.Hello2 - 我是fatal
可以看出列印三篇。
在實際使用過程中,我們其實就是需要一個就行了,這時你可以設定:additivity=false
。
它會把父類全部遮蔽掉。官方說法就是把追加性關閉。
現在我們看一個稍微複雜的例子:
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="error">
<!--先定義所有的appender-->
<appenders>
<!--這個輸出控制檯的配置-->
<Console name="Console" target="SYSTEM_OUT">
<!--控制檯只輸出level及以上級別的資訊(onMatch),其他的直接拒絕(onMismatch)-->
<ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY"/>
<!--這個都知道是輸出日誌的格式-->
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
</Console>
<!--檔案會打印出所有資訊,這個log每次執行程式會自動清空,由append屬性決定,這個也挺有用的,適合臨時測試用-->
<File name="log" fileName="log/test.log" append="false">
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
</File>
<!--這個會打印出所有的資訊,每次大小超過size,則這size大小的日誌會自動存入按年份-月份建立的資料夾下面並進行壓縮,作為存檔-->
<RollingFile name="RollingFile" fileName="logs/app.log"
filePattern="log/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
<PatternLayout pattern="%d{yyyy-MM-dd 'at' HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n"/>
<SizeBasedTriggeringPolicy size="50MB"/>
</RollingFile>
</appenders>
<!--然後定義logger,只有定義了logger並引入的appender,appender才會生效-->
<loggers>
<!--建立一個預設的root的logger-->
<root level="trace">
<appender-ref ref="RollingFile"/>
<appender-ref ref="Console"/>
</root>
</loggers>
</configuration>
–未完待續
說明
rollover
表示的是當日志文件大小滿足指定大小後,就生成一個新的檔案的過程。
RollingFileAppender
RollingFileAppender
是一個OutputStreamAppender
,它(會把日誌)寫入到filename
引數命名的檔案中,並且會根據TriggeringPolicy
和RolloverPolicy
來rollover
(rolls the file over)。RollingFileAppender
會使用RollingFileManager
(繼承OutputStreamManager)來實際執行檔案的I/O和執行rollover
。儘管不能共享來做不同配置的RolloverFileAppenders
,但是如果Manager
可以訪問的話,那麼RolloverFileAppenders
可以(進行共享)。
例如:在一個servlet
容器中的兩個web
應用程式,他們有自己的配置,如果他們Log4j
是共用一個類載入器(ClassLoader)那麼就可以安全的寫入到一個檔案中。
RollingFileAppender 需要TriggeringPolicy
和RolloverStrategy
。triggering policy
決定是否應該執行rollover
的操作,而RolloverStrategy
定義了應該如何完成rollover
。如果RolloverStrategy
沒有配置的話,RollingFileAppender
將使用DefaultRolloverStrategy
。從log4j
2.5版本開始,在DefaultRolloverStrategy
中配置的自定義刪除操作在rollover
時將被執行。
從2.8版本開始,如果在DirectWriteRolloverStrategy
中沒有配置檔名,將使用DefaultRolloverStrategy
進行替換。
RollingFileAppender
不支援檔案鎖的。
這裡rollover
的操作可以理解為:當日志文件大小滿足指定大小後,就生成一個新的檔案。
RollingFileAppender引數
引數名 | 型別 | 描述 |
---|---|---|
append | boolean | 預設為true,記錄追加到檔案的最後,否則就先清除以前的記錄再寫入 |
bufferedIO | boolean | 預設true,記錄將會寫入到快取區,當快取區滿的時候,就會寫入磁碟。或者如果設定immediateFlush 將會立即寫入。檔案鎖定不能和bufferedIO一起使用。 |
bufferSize | int | 當bufferedIO設定為true是,預設是8192 bytes |
createOnDemand | boolean | 預設為false,該appender按需建立檔案,當日志事件通過所有的filters並且通過路由指向了該appender,該appender僅僅建立該檔案 |
filter | Filter | 過濾器決定事件是否應該由這個Appender來處理。通過使用CompositeFilter 來使用多個Filter |
fileName | String | 要寫入的檔案的名稱。如果檔案或其父目錄不存在,它們都將被創建出來 |
filePattern | String | 壓縮日誌檔案的檔名的模式。該模式的格式取決於所使用的RolloverPolicy 。DefaultRolloverPolicy 將接受相容SimpleDateFormat 的日期/時間模式和/或者%i(代表整數計數器)。這個模式也支援執行時插值,所以任何的查詢( eg:DateLookup)都可以包含在模式中 |
immediateFlush | boolean | 預設為true,每次寫入都會執行flush。這可以保證每次資料都被寫入磁碟,但是會影響效能。在同步的loggers中每次寫入執行flush,那就非常有用。非同步loggers和appenders將會在一系列事件結束後自動執行flush,即使設定為false。這也保證了資料寫入到磁碟而且很高效 |
layout | Layout | 這個Layout 用於格式化LogEvent .如果沒有提供預設的layout ,預設為layout 模式為%m%n 。 |
name | String | 該Appender名稱 |
policy | TriggeringPolicy | 用於決定是否發生rollover 的策略 |
strategy | RolloverStrategy | 用於決定壓縮檔案的名稱和路徑 |
ignoreExceptions | boolean | 預設為true,遇到異常時,會將事件追加到內部日誌並忽略它。設定false時,異常會傳遞給呼叫者,當這個appender被FailoverAppender包裹時,必須設定為false |
Triggering Policies
Composite Triggering Policy
Composite Triggering Policy
組合了多個triggering policies
,如果配置的策略中的任何一個返回true
,則返回true。CompositeTriggeringPolicy
簡單的通過在policies
元素包裹其他的policies
來配置。
例如,以下XML片段定義了當JVM啟動時,當日志大小達到二十兆位元組以及當前日期與日誌的開始日期不匹配時滾動日誌的策略。
<Policies>
<OnStartupTriggeringPolicy />
<SizeBasedTriggeringPolicy size="20 MB" />
<TimeBasedTriggeringPolicy />
</Policies>
Cron Triggering Policy
基於cron
表示式的CronTriggeringPolicy
觸發rollover
。
CronTriggeringPolicy 引數
引數名稱 | 型別 | 描述 |
---|---|---|
schedule | String | cron表示式。該表示式和Quartz排程所允許的表示式相同,有關表示式的完整描述,請參閱CronExpression |
evaluateOnStartup | boolean | 在啟動時,將根據檔案的最後修改時間戳評估cron表示式。如果cron表示式表示在該時間和當前時間之間應該發生rollover ,則檔案將立即rollover 。 |
OnStartup Triggering Policy
如果日誌檔案比當前jvm
啟動時間更早以及滿足或者超過最小檔案的大小就會觸發rollover
OnStartupTriggeringPolicy 引數說明:
引數名稱 | 型別 | 描述 |
---|---|---|
minSize | long | 檔案必定發生rollover 操作的最小尺寸。要是大小為0的話,那麼無論檔案大小是多少都將引起rollover 。預設值為1,這將阻止空檔案傳送rollover |
SizeBased Triggering Policy
SizeBased Triggering Policy:一旦檔案大小達到指定大小後,就會發送rollover
。
該策略接受一個interval
屬性和modulate
布林屬性。其中interval
屬性表示的是,基於時間模式應該傳送rollover
的頻率。
TimeBasedTriggeringPolicy引數說明:
引數名稱 | 型別 | 描述 |
---|---|---|
interval | integer | 根據日期格式中最具體的時間單位來決定應該多久發生一次rollover 。例如,在日期模式中小時為具體的時間單位,那麼每4小時會發生4次rollover ,預設值為1 |
modulate | boolean | 表示是否調整時間間隔以使在時間間隔邊界發生下一個rollover 。例如:假設小時為具體的時間單元,當前時間為上午3點,時間間隔為4,第一次傳送rollover 是在上午4點,接下來是上午8點,接著是中午,接著是下午4點等發生。 |
Rollover Strategies
Default Rollover Strategy
預設的rollover strategy
接受一個日期/時間模式和一個整數,其中這個整數,是RollingFileAppender
本身指定的filePattern
屬性。如果date/time
模式存在的話,它將會替換當前日期和時間的值。如果這個模式包含整數的話,它將會在每次發生rollover
時,進行遞增。如果模式同時包含date/time
和整數,那麼在模式中,整數會遞增直到結果中的data/time
模式發生改變。如果檔案模式是以".gz", ".zip", ".bz2", ".deflate", ".pack200", or ".xz"
結尾的,將會與字尾相匹配的壓縮方案進行壓縮檔案。格式為:bzip2, Deflate, Pack200 和 XZ
需要Apache Commons Compress,此外,XZ
需要XZ for Java
該模式還可以包含可以在執行時解析的查詢引用,如下面的示例所示:
預設的rollover
策略支援三種增加計數的方式。第一種叫做:fixed window
策略。為了說明它的工作原理,假設min屬性設定為1,max屬性設定為3,檔名為“foo.log”,檔名模式為:foo-%i.log。
rollover次數 | 輸出的目標 | 壓縮的日誌檔案 | 描述 |
---|---|---|---|
0 | foo.log |
- | 所有的日誌都輸出到初始檔案中 |
1 | foo.log |
foo-1.log | 在第一次rollover 時,foo.log會被重新命名為foo-1.log。同時會建立一個新的foo.log 並開始寫入。 |
2 | foo.log |
foo-1.log, foo-2.log | 在第二次發生rollover 時,foo-1.log會重新命名為foo-2.log並且foo.log會重新命名為foo-1.log。同時會建立一個新的foo.log 並開始寫入。 |
3 | foo.log | foo-1.log, foo-2.log, foo-3.log | 在第三次發生rollover 時,foo-2.log會重新命名為foo-3.log。foo-1.log重新命名為foo-2.log,foo.log會重新命名為foo-1.log。同時會建立一個新的foo.log 並開始寫入。 |
4 | foo.log | foo-1.log, foo-2.log, foo-3.log | 在第四次和隨後的rollover 時,foo-3.log會被刪除,foo-2.log重新命名為foo-3.log。foo-1.log重新命名為foo-2.log。foo.log重新命名為foo-1.log。後面同理 |
相比之下,當 fileIndex屬性設定了max
,其他設定和上面相同,將執行以下操作:
rollover次數 | 輸出的目標 | 壓縮的日誌檔案 | 描述 |
---|---|---|---|
0 | foo.log | - | 所有的日誌都輸出到初始檔案中 |
1 | foo.log | foo-1.log | 在第一次rollover時,foo.log會被重新命名為foo-1.log。同時會建立一個新的foo.log並開始寫入。 |
2 | foo.log | foo-1.log foo-2.log | 在第二次rollover時,foo.log重新命名為foo-2.log。同時會建立一個新的foo.log並開始寫入。 |
3 | foo.log | foo-1.log foo-2.log foo-3.log | 在第三次發生rollover時,fool.log重新命名為foo-3.log,同時會建立一個新的foo.log並開始寫入。 |
4 | foo.log | foo-1.log foo-2.log foo-3.log | 在第四次和隨後的rollover時,foo-1.log會被刪除,foo-2.log重新命名為foo-1.log,foo-3.log重新命名為foo-2.log,foo.log重新命名為foo-3.log。同時會建立一個新的foo.log並開始寫入。 |
最後,從2.8版本開始,如果fileIndex
屬性設定為nomax
,那麼最大和最小值,都將會被忽略掉,檔案編號將從1開發增加,並且每次rollover時遞增都從編碼最大開始(專案於max效果),而且沒有檔案數的限制。
DefaultRolloverStrategy引數
引數名稱 | 型別 | 描述 |
---|---|---|
fileIndex | String | 如果設定了max(預設就是),檔案索引(編號)高的比低的更 新些。如果設定min,檔案重新命名將遵循Fixed Window 策略 |
min | integer | 計數器的最小值。預設值為1。 |
max | integer | 計數器的最大值。一旦達到這個值,舊的檔案將在隨後的rollover 中被刪除。 |
compressionLevel | integer | 設定壓縮級別0-9,其中0=無,1=最佳速度,通過9=最佳壓縮。只適用於ZIP檔案。 |
DirectWrite Rollover Strategy
DirectWriteRolloverStrategy會將日誌事件會直接寫入檔案模式表示的檔案中去。使用這個策略不會執行檔案重新命名。如果基於大小的觸發策略導致在指定的時間段內寫入多個檔案,則它們將從一個編號開始,並持續遞增,直到發生基於時間的rollover
。
警告:如果檔案模式裡有壓縮的字尾,那麼當應用程式關閉時,當前檔案將不被壓縮。此外,如果時間變化使得檔案模式不再匹配當前檔案,則它也不會在啟動時被壓縮。
DirectWriteRolloverStrategy 引數
引數名稱 | 型別 | 描述 |
---|---|---|
maxFiles | String | 在與檔案模式(file pattern)匹配的時間段內允許的最大檔案數。如果這個數字被突破了,則最舊的檔案將被刪除。如果指定了,那麼這個值必須大於1。如果值小於零或省略,則檔案數不受限制。 |
compressionLevel | integer | 設定壓縮級別0-9,其中0=無,1=最佳速度,通過9=最佳壓縮。只適用於ZIP檔案。 |
①下面是使用RollingFileAppender
的一個示例配置,並且是基於時間和大小的觸發策略。其將會根據當前年和月份在未來7天內建立7個壓縮包,並且使用gzip
進行壓縮。
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
<Appenders>
<RollingFile name="RollingFile" fileName="logs/app.log"
filePattern="logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size="250 MB"/>
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="RollingFile"/>
</Root>
</Loggers>
</Configuration>
②第二個例子顯示一個rollover
策略,最多保留20個檔案:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
<Appenders>
<RollingFile name="RollingFile" fileName="logs/app.log"
filePattern="logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size="250 MB"/>
</Policies>
<DefaultRolloverStrategy max="20"/>
</RollingFile>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="RollingFile"/>
</Root>
</Loggers>
</Configuration>
③下面是使用RollingFileAppender
的一個示例配置,並且是基於時間和大小的觸發策略。其將會根據當前年和月份在未來7天內建立7個壓縮包,並且每6小時即被6整除時,會使用gzip
進行壓縮每個檔案。
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
<Appenders>
<RollingFile name="RollingFile" fileName="logs/app.log"
filePattern="logs/$${date:yyyy-MM}/app-%d{yyyy-MM-dd-HH}-%i.log.gz">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
<Policies>
<!--這行區別-->
<TimeBasedTriggeringPolicy interval="6" modulate="true"/>
<SizeBasedTriggeringPolicy size="250 MB"/>
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="RollingFile"/>
</Root>
</Loggers>
</Configuration>
④此示例配置使用基於cron和基於大小的觸發策略的RollingFileAppender,並且是無限制檔案數量的直接寫入到歸檔檔案中。cron觸發器是每小時發生rollover並且檔案大小不得超過250M。
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
<Appenders>
<RollingFile name="RollingFile" filePattern="logs/app-%d{yyyy-MM-dd-HH}-%i.log.gz">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
<Policies>
<CronTriggeringPolicy schedule="0 0 * * * ?"/>
<SizeBasedTriggeringPolicy size="250 MB"/>
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="RollingFile"/>
</Root>
</Loggers>
</Configuration>
⑤此示例和上面④大致一樣,但是每小時儲存的檔案數量限制了為10:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
<Appenders>
<RollingFile name="RollingFile" filePattern="logs/app-%d{yyyy-MM-dd-HH}-%i.log.gz">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
<Policies>
<CronTriggeringPolicy schedule="0 0 * * * ?"/>
<SizeBasedTriggeringPolicy size="250 MB"/>
</Policies>
<DirectWriteRolloverStrategy maxFiles="10"/>
</RollingFile>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="RollingFile"/>
</Root>
</Loggers>
</Configuration>
Log Archive Retention Policy(日誌存檔保留策略):Delete on Rollover
log4j-2.5開始引入了刪除操作,使得使用者更有效的的控制在rollover
時間內刪除檔案,而不是使用DefaultRolloverStrategy max屬性進行刪除。刪除操作允許使用者配置一個或多個條件,選擇要刪除相對於基本目錄的檔案。
注意:刪除任何檔案這是允許的操作。不僅僅是rollover
時的檔案。所以使用這個操作時,一定要小心。使用testMode
引數可以測試您的配置,而不會意外刪除錯誤的檔案。
Delete 引數:
引數名稱 | 型別 | 描述 |
---|---|---|
basePath | String | 必參。從哪裡掃描要刪除的檔案的基本路徑。 |
maxDepth | int | 要訪問的目錄的最大級別數。值為0表示僅訪問起始檔案(基本路徑本身),除非被安全管理者拒絕。Integer.MAX_VALUE的值表示應該訪問所有級別。預設為1,意思是指定基本目錄中的檔案。 |
followLinks | boolean | 是否遵循符號連結預設值為false。 |
testMode | boolean | 預設false。如果為true,檔案將不會被刪除,而是將資訊列印到info級別的status logger,可以利用這個來測試,配置是否和我們預期的一樣 |
pathSorter | PathSorter | 一個實現了PathSorter介面的外掛在選擇刪除檔案前進行排序。預設是最近修改的檔案排在前面 |
pathConditions | PathCondition[] | 如果沒有指定ScriptCondition,則為必需。一個或多個PathCondition元素。如果指定了多個條件,在刪除之前,他們需要接受全部的路徑。條件是可以巢狀的,在這種情況下,只有在外部路徑被接受的情況下,才會去評估內部路徑。如果條件沒有巢狀,則可以按照任何順序去評估。條件也可以通過IfAll 、IfAny 和IfNot 組合複合條件。他們對於的是AND、OR、NOT 。使用者可以建立自定義條件或使用內建條件:IfFileName:接受路徑(相對於基本路徑)與正則表示式或glob匹配的檔案。IfLastModified:接受與指定持續時間相同或更早的檔案。IfAccumulatedFileCount :在file tree walk 期間超過一些計數閾值後接受路徑。IfAccumulatedFileSize:在file tree walk 期間超過累積檔案大小閾值後接受路徑。ifAll:如果所有巢狀條件都接受它(邏輯與),則接受路徑。巢狀條件可以按任何順序進行評估。IfAny:如果其中一個巢狀條件接受(OR或OR),則接受路徑。巢狀條件可以按任何順序進行評估。IfNot:如果巢狀條件不接受(邏輯NOT),則接受路徑。 |
scriptCondition | ScriptCondition | 如果沒有指定PathConditions,則為必需。指定指令碼的ScriptCondition元素。ScriptCondition應包含一個Script,ScriptRef或ScriptFile元素,用於指定要執行的邏輯。有關配置ScriptFiles和ScriptRefs的更多示例,請參閱ScriptFilter文件。該指令碼傳遞了許多引數,包括在基本路徑下找到的路徑列表(最多為maxDepth),並且必須返回具有要刪除路徑的列表。 |
IfFileName 條件引數:
引數名稱 | 型別 | 描述 |
---|---|---|
glob | String | 如果regex 沒有指定的話,則必須。使用類似於正則表示式但是又具有更簡單的有限模式語言來匹配相對路徑(相對於基本路徑) |
regex | String | 如果glob 沒有指定的話,則必須。使用由Pattern類定義的正則表示式來匹配相對路徑(相對於基本路徑) |
nestedConditions | PathCondition[] | 一組可選的巢狀PathConditions如果存在任何巢狀條件,則在它們刪除檔案之前,必須都接受。僅當外部條件接受檔案(如果路徑名稱匹配)時,才會評估巢狀條件。 |
IfLastModified條件引數:
引數名稱 | 型別 | 描述 |
---|---|---|
age | String | 必須。指定持續時間duration。該條件接受比指定持續時間更早或更舊的檔案。 |
nestedConditions | PathCondition[] | 一組可選的巢狀PathConditions如果存在任何巢狀條件,則在它們刪除檔案之前,必須都接受。僅當外部條件接受檔案(如果檔案足夠老)時,才會評估巢狀條件。 |
IfAccumulatedFileCount 條件引數
引數名稱 | 型別 | 描述 |
---|---|---|
exceeds | int | 必須。將要刪除檔案的計數閾值。也就是需要保留的檔案數。 |
nestedConditions | PathCondition[] | 一組可選的巢狀PathConditions如果存在任何巢狀條件,則在它們刪除檔案之前,必須都接受。僅當外部條件接受檔案(如果超過閾值計數)時,才會評估巢狀條件。 |
IfAccumulatedFileSize 條件引數
引數名稱 | 型別 | 描述 |
---|---|---|
exceeds | String | 必須。將刪除檔案累計閥值的大小。大小可以指定位元組。字尾可以是KB, MB or GB 例如:20MB 。也就是要保留最接近該值大小的檔案。 |
nestedConditions | PathCondition[] | 一組可選的巢狀PathConditions如果存在任何巢狀條件,則在它們刪除檔案之前,必須都接受。僅當外部條件接受檔案(如果超過了閾值累積檔案大小)時,才會評估巢狀條件。 |
下面這個示例配置使用的是RollingFileAppender
,並且使用的是cron
觸發策略,是在每天的午夜觸發。歸檔儲存的目錄是基於當前年和月。在rollover
時間內匹配刪除基本目錄下所有滿足引數glob
等於*/app-*.log.gz
和超過60天或更早的檔案。
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
<Properties>
<Property name="baseDir">logs</Property>
</Properties>
<Appenders>
<RollingFile name="RollingFile" fileName="${baseDir}/app.log"
filePattern="${baseDir}/$${date:yyyy-MM}/app-%d{yyyy-MM-dd}.log.gz">
<PatternLayout pattern="%d %p %c{1.} [%t] %m%n" />
<CronTriggeringPolicy schedule="0 0 0 * * ?"/>
<DefaultRolloverStrategy>
<Delete basePath="${baseDir}" maxDepth="2">
<IfFileName glob="*/app-*.log.gz" />
<IfLastModified age="60d" />
</Delete>
</DefaultRolloverStrategy>
</RollingFile>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="RollingFile"/>
</Root>
</Loggers>
</Configuration>
下面的示例配置使用的是RollingFileAppender
並且觸發策略是基於時間和檔案大小。在未來100天內會建立100個歸檔,這些歸檔儲存的目錄是基於當前年和月的,並且會以gzip
方式進行壓縮每個歸檔,而且rollover
是每小時發生一次。這個配置也將會刪除匹配*/app-*.log.gz
和超過30天或更早的檔案。但是會保留大小進100G或者最近的10個檔案,先到則為準(whichever comes first.)。我個人理解應該是以檔案建立時間為準。
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
<Properties>
<Property name="baseDir">logs</Property>
</Properties>
<Appenders>
<RollingFile name="RollingFile" fileName="${baseDir}/app.log"
filePattern="${baseDir}/$${date:yyyy-MM}/app-%d{yyyy-MM-dd-HH}-%i.log.gz">
<PatternLayout pattern="%d %p %c{1.} [%t] %m%n" />
<Policies>
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size="250 MB"/>
</Policies>
<DefaultRolloverStrategy max="100">
<!--
Nested conditions: the inner condition is only evaluated on files
for which the outer conditions are true.
-->
<Delete basePath="${baseDir}" maxDepth="2">
<IfFileName glob="*/app-*.log.gz">
<IfLastModified age="30d">
<IfAny>
<IfAccumulatedFileSize exceeds="100 GB" />
<IfAccumulatedFileCount exceeds="10" />
</IfAny>
</IfLastModified>
</IfFileName>
</Delete>
</DefaultRolloverStrategy>
</RollingFile>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="RollingFile"/>
</Root>
</Loggers>
</Configuration>
ScriptCondition 引數:
引數名稱 | 型別 | 描述 |
---|---|---|
script | Script, ScriptFile or ScriptRef | Script元素是用於指定需要執行的邏輯。在基本路徑下找到的路徑列表,該指令碼要是通過的,並且要以java.util.List<PathWithAttributes> 返回刪除的路徑。 參考ScriptFilter文件,如何配置ScriptFilter和ScriptRefs 的示例。 |
Script 引數:
引數名稱 | 型別 | 描述 |
---|---|---|
basePath | java.nio.file.Path | 刪除操作開始掃描要刪除的檔案的目錄。可用於相對路徑列表中的路徑。 |
pathList | java.util.List | 在基本路徑下找到路徑列表直到指定的最大深度,優先排序最近修改的檔案。該指令碼可以自由修改並返回此列表。 |
statusLogger | StatusLogger | StatusLogger通常用於在指令碼執行期間記錄內部事件。 |
configuration | Configuration | 擁有此ScriptCondition的配置。 |
substitutor | StrSubstitutor | StrSubstitutor用於替換查詢變數。 |
? | String | 在配置中宣告的任何屬性 |
以下示例配置使用RollingFileAppender
並且是cron
觸發策略,觸發時間為每天午夜。歸檔儲存目錄是基於當前年和月的。該指令碼將返回在基本目錄下,日期為13號並且是星期五的rollover
檔案列表。刪除操作將刪除指令碼返回的所有檔案。
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="trace" name="MyApp" packages="">
<Properties>
<Property name="baseDir">logs</Property>
</Properties>
<Appenders>
<RollingFile name="RollingFile" fileName="${baseDir}/app.log"
filePattern="${baseDir}/$${date:yyyy-MM}/app-%d{yyyyMMdd}.log.gz">
<PatternLayout pattern="%d %p %c{1.} [%t] %m%n" />
<CronTriggeringPolicy schedule="0 0 0 * * ?"/>
<DefaultRolloverStrategy>
<Delete basePath="${baseDir}" maxDepth="2">
<ScriptCondition>
<Script name="superstitious" language="groovy"><![CDATA[
import java.nio.file.*;
def result = [];
def pattern = ~/\d*\/app-(\d*)\.log\.gz/;
pathList.each { pathWithAttributes ->
def relative = basePath.relativize pathWithAttributes.path
statusLogger.trace 'SCRIPT: relative path=' + relative + " (base=$basePath)";
// remove files dated Friday the 13th
def matcher = pattern.matcher(relative.toString());
if (matcher.find()) {
def dateString = matcher.group(1);
def calendar = Date.parse("yyyyMMdd", dateString).toCalendar();
def friday13th = calendar.get(Calendar.DAY_OF_MONTH) == 13 \
&& calendar.get(Calendar.DAY_OF_WEEK) == Calendar.FRIDAY;
if (friday13th) {
result.add pathWithAttributes;
statusLogger.trace 'SCRIPT: deleting path ' + pathWithAttributes;
}
}
}
statusLogger.trace 'SCRIPT: returning ' + result;
result;
]] >
</Script>
</ScriptCondition>
</Delete>
</DefaultRolloverStrategy>
</RollingFile>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="RollingFile"/>
</Root>
</Loggers>
</Configuration>
相關推薦
Java專案中 log4j的用法
log4j:ERROR Could not read configuration file [log4j.properties]. java.io.FileNotFoundException: log4j.properties (系統找不到指定的檔案。) at java.io.FileInputStre
在java專案中使用log4j的例項
環境 作業系統:win7 log4j2版本: 2.8.2 準備 下載jar包 把jar包放入到專案中去 開始使用 假設我們要使用log4j2,我們一般是先宣告成一個靜態成員變數: private static final Logger logger
java專案中使用log4j的例項
下面定義日誌輸出級別是 INFO,並且配置了2個輸出目的地,一個是A3,一個是console log4j.rootLogger = INFO,A3,CONSOLE //日誌最低的輸出級別 log4j.appender.A3.Threshold=INFO log4j.appender.A3.encodi
在Java專案中如何使用log4j和slf4j實現日誌列印(轉)
https://blog.csdn.net/xiao_mengxi/article/details/54910450 用maven管理,只需要匯入一個包就行 <dependency> <groupId>org.slf4j</gr
Log4j日誌在java專案中的使用(附工程原始碼)
一、關於Log4j日誌 Log4j是Apache的一個開源專案,通過使用Log4j,我們可以控制日誌資訊輸送的目的地是控制檯、檔案、GUI元件,甚至是套介面伺服器、NT的事件記錄器、UNIX Syslog守護程序等;我們也可以控制每一條日誌的輸出格式;通過定義
在java專案中使用Log4j -2-Logger
Logger是log4j操作的核心物件,Logger的Name是區分大小寫的,而且支援層級命名規則,類似於Java類的定義,通過名稱空間來區別。 Logger的等級制度 如果1個Logger的name字首是.及祖輩的Logger的name,那麼就說
Java專案中有關路徑的獲取方法
1、用Jsp獲取 1-1、獲取檔案的絕對路徑 String file=“檔案”;(例如:data.mdb) String path=application.getRealPath(file); 結果: E:\java_web\workspace.metadata.plugins\or
java專案中配置檔案的使用
讀寫xml、properties 檔案型別 新建java工程Test專案,專案src下建立demo.propertiesFileDemo.java、 demo.xmlFileDemo.java 引入相關jar包: :commons-collections-3.2.j
Java專案中讀寫檔案
1.讀取檔案 InputStream input;//輸入流 InputStreamReader isr = null; BufferedReader br = null; //用於包裝InputStreamReader,提高處理效能。因為
JAVA專案中常用的異常處理情況總結
1. java.lang.nullpointerexception 這個異常大家肯定都經常遇到,異常的解釋是"程式遇上了空指標",簡單地說就是呼叫了未經初始化的物件或者是不存在的物件,這個錯誤經常出現在建立圖片,呼叫陣列這些操作中,比如圖片未經初始化,或者圖片建立時的路徑錯誤等等。對陣列操作中出現空指標
JAVA專案中的常用的異常處理
1. java.lang.nullpointerexception 這個異常大家肯定都經常遇到,異常的解釋是"程式遇上了空指標",簡單地說就是呼叫了未經初始化的物件或者是不存在的物件,這個錯誤經常出現在建立圖片,呼叫陣列這些操作中,比如圖片未經初始化,或者圖片建立時的路徑錯誤等等。對陣列操作中出現空指標
JAVA專案中常用的異常處理情況
1.數學運算異常( java.lang.arithmeticexception) 程式中出現了除以零這樣的運算就會出這樣的異常,對這種異常,大家就要好好檢查一下自己程式中涉及到數學運算的地方,公式是不是有不妥了。 2.陣列下標越界(java.lang.arrayindexoutofboundse
JAVA專案中的常用的異常處理情況
JAVA專案中的常用的異常處理情況 &nbs
java專案中異常處理情況
一,基本概念 異常是程式在執行時出現的不正常情況。是Java按照面向物件的思想將問題進行物件封裝。這樣就方便於操作問題以及處理問題。 異常處理的目的是提高程式的健壯性。你可以在catch和finally程式碼塊中給程式一個修正機會,使得程式不因不可控制的異常而影響程式的流程。同時,通過獲取Java異常
java專案中的異常處理
java專案中的常用的異常處理情況 1)為可恢復的錯誤使用檢查型異常,為程式設計錯誤使用非檢查型錯誤。 選擇檢查型還是非檢查型異常,對於Java程式設計人員來說,總是讓人感到困惑。檢查型異常保證你對錯誤條件提供異常處理程式碼,這是一種從語言到強制你編'寫健壯的程式碼的一種方式,但同時會引入大量
JAVA專案中常用的異常知識點總結
JAVA專案中常用的異常知識點總結 1. java.lang.nullpointerexception 這個異常大家肯定都經常遇到,異常的解釋是"程式遇上了空指標",簡單地說就是呼叫了未經初始化的物件或者是不存在的物件,這個錯誤經常出現在建立圖片,呼叫陣列這些操作中,比如圖片未經初始化,或者圖片
關於JAVA專案中的常用的異常處理情況總結
1. JAVA異常處理 在面向過程式的程式語言中,我們可以通過返回值來確定方法是否正常執行。比如在一個c語言編寫的程式中,如果方法正確的執行則返回1.錯誤則返回0。在vb或delphi開發的應用程式中,出現錯誤時,我們就彈出一個訊息框給使用者。 通過方法的返回值我們並不能獲得錯誤的詳細資訊。可能因為方法由
關於JAVA專案中的常用的異常處理情況
Exception異常層次結構的根類 RuntimeException許多java.lang異類的基類 ArithmeticException算術錯誤情形 IllegalArgumentException方法接收到非法引數 ArrayIndexOutOfBoundException陣列大小小於或大於實
java專案模板-log4j
log4j.xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE log4j:configuration PUBLIC "-//APACHE//DTD LOG4J 1.2//EN" "log4j.dt
IDEA java專案中新增jar包
點選 File -> Project Structure(快捷鍵 Ctrl + Alt + Shift + s), 點選Project Structure介面左側的“Modules”顯示介面。 2.在 “Dependencies” 標籤介面下,點選右邊綠色的 “