<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    隨筆-314  評論-209  文章-0  trackbacks-0
    solo L
    發(fā)布日期:2006年07月30日,更新日期:2006年07月30日
    1996年初,歐洲安全電子市場(EU SEMPER)項(xiàng)目組決定編寫自己的日志記錄API,后來這個(gè)API演變成了Log4j。Log4j是一個(gè)開放源碼項(xiàng)目,一個(gè)非常流行的Java日志記錄包。它允許開發(fā)者向代碼中插入日志記錄語句,還允許在不修改應(yīng)用程序源碼的情況下修改記錄日志的行為。

    1996年初,歐洲安全電子市場(EU SEMPER)項(xiàng)目組決定編寫自己的日志記錄API,后來這個(gè)API演變成了Log4j。Log4j是一個(gè)開放源碼項(xiàng)目,一個(gè)非常流行的Java日志記錄包。它允許開發(fā)者向代碼中插入日志記錄語句,還允許在不修改應(yīng)用程序源碼的情況下修改日志記錄的行為。

    幾乎每一個(gè)項(xiàng)目都會(huì)使用日志記錄,但是由于日志記錄不是項(xiàng)目的核心,因此受重視的程度一般不是很高。我們認(rèn)為使用日志記錄是一件非常嚴(yán)肅的事情,而且做好使用日志記錄的規(guī)劃比單純記錄日志本身更加重要。

    本文將比較全面的闡述Log4j的設(shè)計(jì)原理和使用方法。

    回頁首

    日志記錄

    日志記錄記錄的是應(yīng)用程序運(yùn)行的軌跡。我們可以通過查看這些軌跡來調(diào)試應(yīng)用程序,這可能也是日志記錄最為流行的用法了。但是我們必須意識到規(guī)劃良好的日志記錄中還含有豐富的信息,通過手工的方式或借助一些工具(大多數(shù)時(shí)候需要自己來書寫這些工具)來分析挖掘這些信息。

    例如,如果我們在規(guī)劃中指出必須記錄用戶的每一次操作,記錄的樣式為 [日志信息]-[操作開始的時(shí)間]-[日志級別]-[日志類別]-[用戶名]-[操作名]-[消息],這只是我們假設(shè)的一種樣式,實(shí)際的日志中一般會(huì)含有比這更加豐富的信息。為了更好的理解,我們根據(jù)該樣式構(gòu)造了一些日志記錄(其中日志類別org.solol.Main、org.solol.Parser和org.solol.UserOperator使用了不同的樣式):

    [日志信息]-[2006-07-30 08:54:20]-[INFO]-[org.solol.Main]-[具體的消息]
    [日志信息]-[2006-07-30 08:55:20]-[INFO]-[org.solol.UserOperator]-[User1]-[查詢報(bào)表1]-[具體的消息]
    [日志信息]-[2006-07-30 08:55:30]-[INFO]-[org.solol.UserOperator]-[User1]-[查詢報(bào)表2]-[具體的消息]
    [日志信息]-[2006-07-30 08:56:01]-[INFO]-[org.solol.Parser]-[具體的消息]
    [日志信息]-[2006-07-30 08:57:26]-[INFO]-[org.solol.UserOperator]-[User2]-[添加用戶User3]-[具體的消息]
    [日志信息]-[2006-07-30 08:58:20]-[INFO]-[org.solol.UserOperator]-[User1]-[查詢報(bào)表3]-[具體的消息]
    [日志信息]-[2006-07-30 08:59:38]-[INFO]-[org.solol.UserOperator]-[User3]-[查詢報(bào)表1]-[具體的消息]
    [日志信息]-[2006-07-30 08:59:39]-[INFO]-[org.solol.UserOperator]-[User2]-[退出系統(tǒng)]-[具體的消息]
    

    從上面的日志記錄中我們很容易抽取出某一用戶的操作列表,如對于用戶User1我們的結(jié)果為:

    [日志信息]-[2006-07-30 08:55:20]-[INFO]-[org.solol.UserOperator]-[User1]-[查詢報(bào)表1]-[具體的消息]
    [日志信息]-[2006-07-30 08:55:30]-[INFO]-[org.solol.UserOperator]-[User1]-[查詢報(bào)表2]-[具體的消息]
    [日志信息]-[2006-07-30 08:58:20]-[INFO]-[org.solol.UserOperator]-[User1]-[查詢報(bào)表3]-[具體的消息]
    

    這樣我們就得到了某一時(shí)間段中User1的操作列表,可以利用這一列表來進(jìn)行安全分析。

    我們還可以從另外的角度來分析上面的日志記錄,如我們很容易統(tǒng)計(jì)出操作(日志類別為org.solol.UserOperator)發(fā)生的總次數(shù)(6次),其中操作[查詢報(bào)表1]為2次,[查詢報(bào)表2]為1次,[查詢報(bào)表3]為1次,[添加用戶User3]為1次,[退出系統(tǒng)]為1次。這樣我們就可以得出系統(tǒng)中的那些操作用戶使用的比較頻繁。

    以上我們從兩個(gè)角度對日記記錄中的信息進(jìn)行了簡單的挖掘,實(shí)際中待挖掘的方面要豐富的多,這取決于您的意圖和您的想象力。

    這里我們還要特別強(qiáng)調(diào)一下:所有這一切都需要有使用日志記錄的良好規(guī)劃。如果規(guī)劃不好(即日志記錄沒有規(guī)律性),那么我們挖掘時(shí)的任務(wù)就會(huì)非常繁重或者使挖掘成為一個(gè)不可能的任務(wù)。

    文章到了這里我們要來描述日志記錄的最為流行的用法了,即調(diào)試應(yīng)用程序。我們在調(diào)試應(yīng)用程序時(shí)一般會(huì)使用兩種方法,除了日志記錄之外,還有debugger調(diào)試器。

    我們不想把他們放到一起來描述,因?yàn)檫@是兩個(gè)完全不同的問題,雖然他們都用來調(diào)試應(yīng)用程序。使用debugger調(diào)試器我們可以清楚的知道引發(fā)錯(cuò)誤的上下文及其相關(guān)信息,也可以使用單步執(zhí)行、設(shè)置斷點(diǎn)、檢查變量以及暫掛和恢復(fù)線程等等比較高級的能力,但是盡管這樣它也不能替代日志記錄,同樣日志記錄也不能替代debugger調(diào)試器。我們要結(jié)合使用這兩種方法,不同的場景使用不同的方法會(huì)有更好的效果。

    我們認(rèn)為使用日志記錄來調(diào)試應(yīng)用程也應(yīng)該充分考慮軟件的開發(fā)周期。這里我們只考慮軟件開發(fā)周期中的與日志記錄有關(guān)的兩個(gè)階段:

    • 開發(fā)階段,用來記錄應(yīng)用程序的方方面面和各種細(xì)節(jié),非常詳細(xì),使得一看到它就知道那里出了問題,出了什么樣的問題。
    • 出品階段,要能夠記錄各種級別的錯(cuò)誤和警告,同時(shí)也要適度記錄應(yīng)用程序正常運(yùn)行的關(guān)鍵信息,這些信息可以給相關(guān)人員(開發(fā)人員、測試人員、用戶等)極大的信心,使他們可以毫不猶豫的告訴您--瞧我們的軟件在正常的運(yùn)行。如一個(gè)好的web服務(wù)器的啟動(dòng)日志記錄不僅要包含錯(cuò)誤和警告,還要包含服務(wù)器正在啟動(dòng),正在加載某某組件等等,最后還要提示啟動(dòng)是成功還是失敗。

    閱讀到這里我們就應(yīng)該著手實(shí)現(xiàn)我們的日志記錄了。比較幸運(yùn)的是我們有好多日志記錄軟件包可選,這就使我們不必關(guān)心日志記錄的細(xì)節(jié),只要把主要的精力放到日志記錄的規(guī)劃上就好了。我們選擇的是Log4j,文章的余下部分將主要介紹這個(gè)Java日志記錄軟件包。

    回頁首

    log4j的特性

    log4j的特性列表:

    • 在運(yùn)行速度方面進(jìn)行了優(yōu)化
    • 使用基于名稱的日志(logger)層次結(jié)構(gòu)
    • 是fail-stop的
    • 是線程安全的
    • 不受限于預(yù)定義的實(shí)用工具集
    • 可以在運(yùn)行時(shí)使用property和xml兩種格式的文件來配置日志記錄的行為
    • 在一開始就設(shè)計(jì)為能夠處理Java異常
    • 能夠定向輸出到文件(file)、控制臺(console)、java.io.OutputStream、java.io.Writer、遠(yuǎn)程服務(wù)器、遠(yuǎn)程Unix Syslog守護(hù)者、遠(yuǎn)程JMS監(jiān)聽者、NT EventLog或者發(fā)送e-mail
    • 使用DEBUG、INFO、WARN、ERROR和FATAL五5個(gè)級別
    • 可以容易的改變?nèi)罩居涗浀牟季?Layout)
    • 輸出日志記錄的目的地和寫策略可以通過實(shí)現(xiàn)Appender接口來改變
    • 支持為每個(gè)日志(logger)附加多個(gè)目的地(appender)
    • 提供國際化支持
    回頁首

    log4j的設(shè)計(jì)原理

    Log4j有三個(gè)主要的組件:Logger、Appender和Layout。這三個(gè)組件相互配合使得我們可以獲得非常強(qiáng)大的日志記錄的能力。

    Logger

    Logger的名稱是區(qū)分大小寫的,依據(jù)名稱可以確定其層次結(jié)構(gòu)(即父子關(guān)系),規(guī)則如下:

    • 如果Logger A的名稱后跟一個(gè)點(diǎn)(.)是Logger B的名稱的前綴就認(rèn)為Logger A是Logger B的祖先。
    • 如果在Logger A和Logger B之間,Logger B沒有任何其它的祖先就認(rèn)為Logger A是Logger B的父親。

    在Logger的層次結(jié)構(gòu)的最頂層是root logger,它會(huì)永遠(yuǎn)存在,而且不能通過名字取到。

    上面文字的描述可能不好的理解,為此我們給出了一張圖,Logger的層次結(jié)構(gòu)圖,從中可以非常直觀的看出三種主要組件的關(guān)系和各自所起的作用。

    圖示 1. Logger的層次結(jié)構(gòu)圖
    Logger的層次結(jié)構(gòu)圖

     

    Loger x.y是Logger x.y.z的祖先,因?yàn)閤.y.是x.y.z的前綴,這符合規(guī)則的前一條。另外在Logger x.y和Logger x.y.z之間,Logger x.y.z沒有其它的祖先,因此Logger x.y是Logger x.y.z的父親,這符合規(guī)則的后一條。這樣我們依據(jù)上面的規(guī)則就可以構(gòu)造出如圖1所示的Logger的層次結(jié)構(gòu)。

    從圖1中我們還可以看到每一個(gè)Logger都有一個(gè)Level,根據(jù)該Level的值Logger決定是否處理對應(yīng)的日志請求。如果Level沒有被設(shè)置,就象圖1中的Logger x.y一樣,又該怎么辦呢?答案是可以從祖先那里繼承。

    如果Logger C沒有被設(shè)置Level,那么它將沿著它的層次結(jié)構(gòu)向上查找,如果找到就繼承并結(jié)束,否則會(huì)一直查找到root logger結(jié)束。因?yàn)閘og4j在設(shè)計(jì)時(shí)保證root logger會(huì)被設(shè)置一個(gè)默認(rèn)的Level,所以任何logger都可以繼承到Level。

    圖1中的Logger x.y沒有被設(shè)置Level,但是根據(jù)上面的繼承規(guī)則,Logger x.y繼承了root logger的Level。

    我們在來看看Logger選擇日志記錄請求(log request)的規(guī)則:

    假設(shè)Logger M具有q級的Level,這個(gè)Level可能是設(shè)置的也可能是繼承到的。

    如果向Logger M發(fā)出一個(gè)Level為p的日志記錄請求,那么只有滿足p>=q時(shí)這個(gè)日志記錄請求才會(huì)被處理。

    org.apache.log4j.Logger中的不同方法發(fā)出不同Level的日志記錄請求,如下:

    • public void debug(Object message),發(fā)出Level為DEBUG的日志記錄請求
    • public void info(Object message),發(fā)出Level為INFO的日志記錄請求
    • public void warn(Object message),發(fā)出Level為WARN的日志記錄請求
    • public void error(Object message),發(fā)出Level為ERROR日志記錄請求
    • public void fatal(Object message),發(fā)出Level為FATAL的日志請求
    • public void log(Level l, Object message),發(fā)出指定Level的日志記錄請求

    其中的靜態(tài)常量DEBUG、INFO、WARN、ERROR、FATAL是在org.apache.log4j.Level中定義的,除了使用這些預(yù)定義的Level之外,Log4j還支持自定義Level。

    注:org.apache.log4j.Level中還預(yù)定義了一些其它的Level。

    Appender

    在Log4j中,Appender指的是日志記錄輸出的目的地。當(dāng)前支持的Appender(目的地)有文件(file)、控制臺(console)、java.io.OutputStream、java.io.Writer、遠(yuǎn)程服務(wù)器、遠(yuǎn)程Unix Syslog守護(hù)者、遠(yuǎn)程JMS監(jiān)聽者、NT EventLog或者發(fā)送e-mail。如果您在上面沒有找到適合的Appender,那就需要考慮實(shí)現(xiàn)自己的自定義Appender了。

    每個(gè)Logger可以有多個(gè)Appender,但是相同的Appender只會(huì)被添加一次。

    Appender的附加性意味著Logger C會(huì)將日志記錄發(fā)給它的和它祖先的所有Appender。在圖1中Logger a會(huì)將日志記錄發(fā)給它自己的JDBCAppender和它的祖先root logger的ConsoleAppender和FileAppender。Logger x.y.z自己沒有Appender,它將把日志記錄發(fā)給它的祖先root logger的ConsoleAppender和FileAppender,如果Logger x.y也含有Appender,那么它們也會(huì)包括在內(nèi)。

    Appender的附加性是可以被中斷的。假設(shè)Logger C的一個(gè)祖先為Logger P,如果Logger P的附加性標(biāo)志(additivity flag)設(shè)置為假,那么Logger C會(huì)將日志記錄只發(fā)給它的和在它和Logger P之間的祖先(包括Logger P)的Appender,而不會(huì)發(fā)給Logger P的祖先的Appender。Logger的附加性標(biāo)志(additivity flag)默認(rèn)值為ture。

    在圖1中如果沒有設(shè)置Logger a的附加性標(biāo)志(additivity flag),而是使用默認(rèn)值true,那么Logger a會(huì)將日志記錄發(fā)給它自己的JDBCAppender和它祖先root logger的ConsoleAppender和FileAppender,這和上面的描述相同。如果設(shè)置Logger a的附加性標(biāo)志(additivity flag)的值false,那么Logger a會(huì)將日志記錄發(fā)給它自己的JDBCAppender而不會(huì)在發(fā)給它祖先root logger的ConsoleAppender和FileAppender了。

    Layout

    Appender定制了輸出目的地,通常我們還需要定制日志記錄的輸出格式,在Log4j中是通過將Layout和Appender關(guān)聯(lián)到一起來實(shí)現(xiàn)的。Layout依據(jù)用戶的要求來格式化日志記錄。PatternLayout(標(biāo)準(zhǔn)Log4j組件)讓用戶依據(jù)類似于C語言printf函數(shù)的轉(zhuǎn)換模式來指定輸出格式。

    例如,轉(zhuǎn)換模式(conversion pattern)為"%r [%t] %-5p %c - %m%n"的PatternLayout將生成類似于以下內(nèi)容的輸出:

    176 [main] INFO  org.foo.Bar - Located nearest gas station.
    

    在上面的輸出中:

    • 第一個(gè)字段表示自程序開始到發(fā)出日志記錄請求時(shí)所消耗的毫秒數(shù)
    • 第二個(gè)字段表示發(fā)出日志記錄請求的線程
    • 第三個(gè)字段表示日志記錄請求的Level
    • 第四個(gè)字段表示發(fā)出日志記錄請求的Logger的名稱
    • 第五個(gè)字段(-后的文本)表示日志記錄請求的消息

    Log4j中還提到了一些其它的Layout,包括HTMLLayout、SimpleLayout、XMLLayout、TTCCLayout和DateLayout。如果這些不能滿足您的要求,還可以自定義自己的Layout。

    回頁首

    log4j的配置

    依據(jù)既有的經(jīng)驗(yàn)顯示用于日志記錄的代碼大約是全部代碼量的4%。如果應(yīng)用程序具有一定的規(guī)模,日志記錄語句的數(shù)量還是比較巨大的,因此必須有效的管理這些語句。

    在Log4j中我們可以通過配置Log4j環(huán)境來有效的管理日志記錄。配置的方式有三種:

    • 通過程序配置
    • 通過Property文件配置
    • 通過XML文件配置

    程序配置

    通過程序配置Log4j環(huán)境實(shí)際上就是在應(yīng)用程序的代碼中改變Logger的Level或增加減少Appender等等。

    Log4j提供了BasicConfigurator,它只是為root logger添加Appender。其中,

    • BasicConfigurator.configure()為root logger添加一個(gè)關(guān)聯(lián)著PatternLayout.TTCC_CONVERSION_PATTERN的ConsoleAppender
    • BasicConfigurator.configure(Appender appender)為root logger添加指定的Appender

    我們可以把BasicConfigurator看成是一個(gè)簡單的使用程序配置Log4j環(huán)境的示例。例如,要給root logger添加兩個(gè)Appender(A和B),下面的代碼分別完成了這個(gè)要求。

    不使用BasicConfigurator:

    //示例代碼,不能直接使用
    Logger root = Logger.getRootLogger();
    root.addAppender(A);
    root.addAppender(B);
    

    使用BasicConfigurator:

    //示例代碼,不能直接使用
    BasicConfigurator.configure(A);
    BasicConfigurator.configure(B);
    

    Property文件配置

    這里要使用PropertyConfigurator來分析配置文件并設(shè)置日志記錄,但是要注意日志記錄先前的配置不會(huì)被清除和重設(shè)。

    Property文件是由key=value這樣的鍵值對所組成的,可以使用#或!作為注釋行的開始。下面給出了兩個(gè)簡單的示例:

    非常簡單的示例1:

    log4j.rootLogger=DEBUG, A1
    log4j.appender.A1=org.apache.log4j.ConsoleAppender
    log4j.appender.A1.layout=org.apache.log4j.PatternLayout
    log4j.appender.A1.layout.ConversionPattern=%-4r %-5p [%t] %37c %3x - %m%n
    

    稍顯復(fù)雜的示例2:

    log4j.rootLogger=, A1, A2
    log4j.appender.A1=org.apache.log4j.ConsoleAppender
    log4j.appender.A1.layout=org.apache.log4j.PatternLayout
    log4j.appender.A1.layout.ConversionPattern=%d %-5p [%t] %-17c{2} (%13F:%L) %3x - %m%n
    log4j.appender.A2=org.apache.log4j.FileAppender
    log4j.appender.A2.File=filename.log
    log4j.appender.A2.Append=false
    log4j.appender.A2.layout=org.apache.log4j.PatternLayout
    log4j.appender.A2.layout.ConversionPattern=%-5r %-5p [%t] %c{2} - %m%n
    

    上面的兩個(gè)示例只是讓您對配置文件的格式有一個(gè)大體的認(rèn)識,我們將在后面詳細(xì)的描述各個(gè)配置元素的語法。

    Repository-wide threshold:

    Repository-wide threshold指定的Level的優(yōu)先級高于Logger本身的Level。語法為log4j.threshold=[level],level可以為OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL。也可以使用自定義Level,這時(shí)的語法為log4j.threshold=[level#classname]。默認(rèn)為ALL。

    依據(jù)上面的規(guī)則,我們有這樣的結(jié)論:如果log4j.threshold=ERROR,Logger C的Level=DEBUG,這時(shí)只有高于等于ERROR的日志記錄請求會(huì)被Logger C處理。

    Appender的配置:

    Appender的配置語法為

    # For appender named appenderName, set its class.
    # Note: The appender name can contain dots.
    log4j.appender.appenderName=fully.qualified.name.of.appender.class
    # Set appender specific options.
    log4j.appender.appenderName.option1=value1
    ...
    log4j.appender.appenderName.optionN=valueN
    #For each named appender you can configure its Layout.
    #The syntax for configuring an appender's layout is:
    log4j.appender.appenderName.layout=fully.qualified.name.of.layout.class
    log4j.appender.appenderName.layout.option1=value1
    ....
    log4j.appender.appenderName.layout.optionN=valueN
    

    Logger的配置:

    root logger的配置語法:

    log4j.rootLogger=[level], appenderName, appenderName, ...,其中l(wèi)evel可以為OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL。也可以使用自定義Level,這時(shí)的語法為[level#classname]。

    如果Level被指定那么root logger的Level將被配置為指定值。如果Level沒有被指定那么root logger的Level不會(huì)被修改。從上面的語法中我們可以看出通過用,分隔的列表可以為root logger指定多個(gè)Appender。

    對于root logger之外的logger語法是相似的,為log4j.logger.logger_name=[level|INHERITED|NULL], appenderName, appenderName, ...

    上面只有INHERITED和NULL需要說明一下,其它部分和root logger相同。INHERITED和NULL的意義是相同的。如果我們使用了它們,意味著這個(gè)logger將不在使用自己的Level而是從它的祖先那里繼承。

    Logger的附加性標(biāo)志(additivity flag)可以使用log4j.additivity.logger_name=[false|true]來配置。

    ObjectRenderer配置:

    我們可以通過ObjectRenderer來定義將消息對象轉(zhuǎn)換成字符串的方式。語法為log4j.renderer.fully.qualified.name.of.rendered.class=fully.qualified.name.of.rendering.class。如:

    //my.Fruit類型的消息對象將由my.FruitRenderer轉(zhuǎn)換成字符串
    log4j.renderer.my.Fruit=my.FruitRenderer
    

    對上面的各個(gè)配置元素的語法理解之后,在來看示例1和2就很容易了。

    PropertyConfigurator不支持Filter的配置。如果要支持Filter您可以使用DOMConfigurator,即使用XML文件的方式配置。

    XML文件配置

    要使用DOMConfigurator.configure()來讀取XML格式的配置文件。XML文件格式的定義是通過org/apache/log4j/xml/log4j.dtd來完成的,各個(gè)配置元素的嵌套關(guān)系如下:

    <!ELEMENT log4j:configuration (renderer*, appender*,(category|logger)*,root?,categoryFactory?)>
    

    這里沒有給出更為詳細(xì)的內(nèi)容,要了解詳細(xì)的內(nèi)容需要查閱log4j.dtd。

    下面這個(gè)簡單的示例可以使您對XML配置文件的格式有一個(gè)基本的認(rèn)識:

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE log4j SYSTEM "log4j.dtd">
    <log4j>
    <appender name="A1" class="org.apache.log4j.FileAppender">
    <layout class="org.apache.log4j.PatternLayout">
    <param name="ConversionPattern" value="%-5p %c{2} - %m\n"/>
    </layout>
    </appender>
    <appender name="A2" class="org.apache.log4j.FileAppender">
    <layout class="org.apache.log4j.TTCCLayout">
    <param name="DateFormat" value="ISO8601" />
    </layout>
    <param name="File" value="warning.log" />
    <param name="Append" value="false" />
    </appender>
    <category name="org.apache.log4j.xml" priority="debug">
    <appender-ref ref="A1" />
    </category>
    <root priority="debug">
    <appender-ref ref="A1" />
    <appender-ref ref="A2" />
    </root>
    </log4j>
    
    回頁首

    默認(rèn)初始化過程

    默認(rèn)初始化過程在LogManager類的靜態(tài)初始化器中完成。具體步驟如下:

    • 檢查系統(tǒng)屬性log4j.defaultInitOverride,如果值為false則執(zhí)行初始化過程,否則跳過初始化過程。
    • 將系統(tǒng)屬性log4j.configuration的值賦給變量resource。如果log4j.configuration沒有被定義則使用默認(rèn)值log4j.properties。
    • 試圖轉(zhuǎn)換變量resource到一個(gè)url。
    • 如果變量resource不能轉(zhuǎn)換成一個(gè)url,那么將使用org.apache.log4j.helpers.Loader.getResource(resource, Logger.class)得到一個(gè)url。
    • 如果還是得不到url,將忽略默認(rèn)初始化過程。如果得到url將使用PropertyConfigurator或DOMConfigurator來配置,也可以使用自定義的XXXConfigurator。
    參考資料
    關(guān)于作者
    solo L 一位有些理想主義的軟件工程師,創(chuàng)建了solol.org。他常常在這里發(fā)表一些對技術(shù)的見解。
    posted on 2008-05-21 10:20 xzc 閱讀(1970) 評論(0)  編輯  收藏 所屬分類: Log4j
    主站蜘蛛池模板: 日韩色视频一区二区三区亚洲| 亚洲午夜久久久久久尤物| 爱爱帝国亚洲一区二区三区| 成年人视频在线观看免费| 亚洲精品中文字幕无乱码麻豆| 久九九精品免费视频| tom影院亚洲国产一区二区| 中文字幕无码播放免费| 亚洲性色精品一区二区在线| 小小影视日本动漫观看免费| 免费播放国产性色生活片| 亚洲男人av香蕉爽爽爽爽| 久久免费线看线看| 亚洲精品午夜久久久伊人| 成年美女黄网站色大免费视频 | 无码乱人伦一区二区亚洲一 | 免费人成在线视频| 亚洲妇女无套内射精| 亚洲精品动漫人成3d在线| 中文字幕乱码系列免费| 久久99亚洲网美利坚合众国 | 免费福利在线播放| 亚洲国产精品成人AV在线| 亚洲国产天堂久久久久久| 小草在线看片免费人成视久网| 亚洲另类图片另类电影| 免费a级黄色毛片| 免费人成毛片动漫在线播放| 99久久婷婷国产综合亚洲| 亚洲国产精品无码久久久久久曰| 九九美女网站免费| 亚洲色大网站WWW永久网站| 国产亚洲精品免费视频播放| 91短视频免费在线观看| 人人爽人人爽人人片A免费| 久久青草亚洲AV无码麻豆| 午夜视频在线在免费| 久久精品视频免费播放| 亚洲av无码av在线播放| 亚洲AV乱码久久精品蜜桃| 日本一线a视频免费观看|