歡迎您光臨本站 註冊首頁

在 Web 應用中增加用戶跟蹤功能

←手機掃碼閱讀     火星人 @ 2014-03-12 , reply:0
  
隨著 Web 應用的複雜化,網站用戶的操作過程也日益複雜,網站功能的多樣化和交互性的提高為用戶提供了多種可能的瀏覽路徑。為了改進用戶的使用體驗,有時也是為了模擬用戶的操作過程以幫助用戶解決使用中的問題,需要能在日誌中識別某個用戶在整個 Session 中所經歷的操作過程,本文針對基於 Apache Log4J 的 Web 應用,討論如何利用 NDC 和 MDC 的機制,簡單快捷的為 Web 應用日誌增加用戶跟蹤的基礎數據。通過本文,讀者可以學習到關於 NDC 和 MDC 的工作機制,以及如何利用他們在一個 Web 應用中記錄用戶在一個網站上的全部行為和操作過程,並可以直接使用文中的代碼和思路,提高工作效率。

進行用戶跟蹤的重要性

隨著 Web 應用的複雜化,用戶在網站上的操作過程日益複雜。網站功能的多樣化和交互性的提高為用戶提供了多種可能的瀏覽路徑。對於一個複雜的站點,用戶在網站上操作的行為模式和操作習慣的分析,會給網站的優化提供基礎的數據支持。而從技術上要為這種分析提供支持,就需要記錄下每個用戶在網站上的操作過程。另一方面,這種數據的記錄也有助於解決用戶在使用中出現的問題。我們只要知道用戶遇到問題的時間和一些基本信息,就可以從日誌中查出此用戶遇到問題時的操作過程,從而有助於再現用戶的出錯場景,進而幫助用戶解決問題。此外,網站用戶的安全審計和分析用戶特徵的數據挖掘等工作也需要提供一個方法能對用戶的網站操作進行跟蹤和紀錄。

通常 Web 應用的開發者會在開發過程中設置很多的跟蹤點,在這些跟蹤點向日誌系統輸出一些應用程序運行的信息,如果這些信息足夠全面的話,開發者就可以利用他們猜測出程序如何處理的用戶請求,以及可能遇到了什麼問題。但不幸的是,如果一個站點在設計階段沒有把用戶跟蹤作為系統必須解決的一個問題提出的話,這些日誌很可能就只是開發者為滿足系統調試的需要而設置的一些信息。當用戶訪問量急劇增加的的時候,就會出現下面的問題。

在一個高訪問量的 Web 應用中,經常要在同一時刻處理大量的用戶請求。Web 伺服器會為每一個請求分配一個線程,每一個線程都會向日誌系統輸入一些信息,通常日誌系統都是按照時間順序而不是用戶順序排列這些信息的,這些線程的交替運行會讓所有用戶的處理信息交錯在一起,讓人很難分辨出那些記錄是同一個用戶產生的。另外,高可用性的網站經常會使用負載均衡系統平衡網路流量,這樣一個用戶的操作記錄很可能會分佈在多個 Web 伺服器上,如果我們沒有一種方法來標示一條記錄是哪個用戶產生的,從這眾多的日誌信息中篩選出對我們有用的東西將是一項艱巨的工作。

本文試圖探討的解決方案是建立在 Log4J 的基礎上的,如果你的 Web 站點已經使用了 Log4J 作為日誌系統的 API 介面,根據本文所介紹的方法,就可以很容易的在每一條日誌上保存用戶上下文信息,為用戶跟蹤保存基本的訪問數據。為了更清晰地介紹這種方法,我們先對 Log4J 以及 NDC/MDC 做個簡單的介紹。

Log4J 簡介

Log4J 主要構件

Log4J 是 Apache 組織提供的一個日誌組件, 它設計了靈活的配置文件,利用它可以在不更改程序的情況下,通過修改配置文件來調控日誌的輸出。下面是 Log4J 最主要的三大基本構件:

  • 記錄器(Loger)

    對日誌信息進行分類篩選。通過指定優先順序,控制程序中日誌信息的輸出:高於優先順序的日誌可以被輸出,低於優先順序的日誌則被忽略。

  • 輸出源(Appenders)

    指定日誌信息的輸出設備。Log4J 目前支持的輸出設備有以下幾種:

    • org.apache.log4j.ConsoleAppender(控制台)
    • org.apache.log4j.FileAppender(文件)
    • org.apache.log4j.DailyRollingFileAppender(每天產生一個日誌文件)
    • org.apache.log4j.RollingFileAppender(文件大小到達指定尺寸的時候產生一個新的文件)
    • org.apache.log4j.WriterAppender(將日誌信息以流格式發送到任意指定的地方)
    • org.apache.log4j.SocketAppender (Socket)
    • org.apache.log4j.NtEventLogAppender (NT的Event Log)
    • org.apache.log4j.JMSAppender (電子郵件)

    程序員也可以根據自己的需要定製 Appenders,實現更複雜和更為方便實用的日誌管理,比如把日誌輸入資料庫,或者傳輸到統一的日誌伺服器,等等。

  • 布局(Layouts)

    指定日誌輸出的格式。Log4J 提供的 Layout 有以下幾種:

    • org.apache.log4j.HTMLLayout(以 HTML 表格形式布局)
    • org.apache.log4j.PatternLayout(可以靈活地指定布局模式)
    • org.apache.log4j.SimpleLayout(包含日誌信息的級別和信息字元串)
    • org.apache.log4j.TTCCLayout(包含日誌產生的時間、線程、類別等等信息)

軟體開發人員可以通過這三大構件,根據日誌的類型和優先順序進行記錄,並且可以在程序運行時去控制日誌信息輸出的格式和往什麼地方輸出(控制台、日誌文件)。

Log4J 使用示例

下面是一個在 Web 應用中使用 Log4J 的簡單例子。

第 1 步:修改 Web 應用的 web.xml 文件

在 Web 應用的 web.xml 中指明 Log4J 的配置文件名稱。


代碼 1. 在 web.xml 中配置 Log4J
				  ……  	<servlet>  		<servlet-name>log4j-init</servlet-name>  		<servlet-class>is.dsw.common.base.log4jInit</servlet-class>  		<init-param>               <!—下面的初始化參數指定 log4j 的配置文件為 log4j.properties -->  			<param-name>log4j</param-name>  			<param-value>/WEB-INF/log4j.properties</param-value>  		</init-param>  		<load-on-startup>1</load-on-startup>  	</servlet>  	<servlet>  		<servlet-name>log4jServlet</servlet-name>  		<servlet-class>is.dsw.common.base.log4jServlet</servlet-class>  		<load-on-startup>0</load-on-startup>  	</servlet>  ……        

第 2 步:對 Log4J 進行配置

我們看到,在上面的配置中指定了 Log4J 的配置文件名為 log4j.properties。在這個文件中,我們可以通過對前文所述的 Log4J 的三大控制項進行配置:


代碼 2. log4j.properties 配置文件
				  #此項指定 log4j 本身不輸出調試信息  log4j.debug=false     # 設置記錄器(logger)的輸出信息的級別,並指定信息源appender,此例中,輸出到JADEA and A1.     log4j.rootCategory=DEBUG, OPAL, A1     ……  #信息源appender為每天產生一個日誌文件  log4j.appender.OPAL= org.apache.log4j.DailyRollingFileAppender  #日誌格式為靈活布局模式  log4j.appender.OPAL.layout=org.apache.log4j.PatternLayout  #指定靈活布局模式下日誌的格式     #%c 輸出所屬類的全名     #%d 輸出日誌時間其格式為 可指定格式 如 %d{HH:mm:ss}等     #%n 換行符     #%m 輸出代碼指定信息,如info(“message”),輸出message     #%p 輸出日誌的優先順序,即 FATAL ,ERROR,INFO 等  log4j.appender.OPAL.layout.ConversionPattern=%d %p %c - %m%n        

第 3 步:在 Web 應用的 Java 代碼中使用 Log4J

進行完上面兩步的配置,我們就可以在 Java 程序中使用 Log4J 進行日誌的輸出。

首先讀取 Log4J 配置信息,初始化 Logger。


代碼 3. 初始化 Log4J
				  public class log4jInit extends HttpServlet {       public void init() {          /*找到在web.xml中指定的log4j.properties文件並讀取配置信息*/          String prefix =  getServletContext().getRealPath("/");          String file = getInitParameter("log4j");          System.out.println("................log4j start");          if(file != null) {              PropertyConfigurator.configure(prefix+file);          }      }  }        

然後在 Java 代碼中就可以任意地使用 Log4J 進行日誌輸出。


代碼 4. 在 Web 應用的 Java 代碼中使用 Log4J 進行日誌輸出
				  public class log4jServlet extends HttpServlet {       public void doGet(HttpServletRequest req, HttpServletResponse res)      throws IOException {          Logger logger = Logger.getLogger(log4jServlet.class);	          /*輸出日誌*/          logger.info("Entering doGet@Log4jServlet.");          /*更多的代碼和日誌輸出*/          ........          ........          logger.info("Exiting doGet@Log4jServlet.");      }  }        

運行該 Web 應用后,當有客戶機訪問該 Web 應用時,就會得到應用程序輸出的日誌信息。下面是以三個併發用戶訪問該 Web 應用的情況下,從列印出的日誌中挑選出的上述相關日誌信息:


圖 1. 運行 Web 應用后輸出的日誌

在上面的例子中,我們看到,利用 Log4J 提供的功能,我們很方便地輸出了程序的日誌,但是同時也看到,這樣的日誌在多用戶併發訪問的情況下,特別是應用程序複雜且擁有大量併發訪問用戶的情況下,根本無法區分哪些日誌是屬於哪個用戶的,這樣就使得進行日誌分析和用戶跟蹤變成一件非常困難的事情。


NDC 介紹

NDC(Nested Diagnostic Context)是 Neil Harrison 在名為《Patterns for Logging Diagnostic Messages》的書中提出的嵌套診斷環境的機制。這種機制的提出,主要為了減少多線程的系統為每個客戶單獨記錄日誌的系統開銷。在過去,區分兩個客戶的日誌輸出的常用方法是單獨為每個客戶機實例化新類別,但該方法會增加類別數量,並增加日誌記錄的管理開銷。Neil Harrison 提出的方法就是把用戶的上下文信息放到嵌套式診斷環境 (NDC) 中。

NDC 為每一個線程管理一個堆棧。開發人員可以在代碼中合適的位置嵌入簡單的 push 和 pop 方法,用來維護堆棧。通常 push 進堆棧的是可以唯一標示客戶的上下文信息,如 SessionID 或者客戶名稱,IP 地址等。因為每個客戶請求都會有單獨的 NDC 堆棧,因此日誌系統在輸出的時候會根據每個線程找到對應的堆棧,並在輸出日誌的時候附加上堆棧內的信息。開發人員就可以很容易的在日誌中區分出各個不同客戶所產生的日誌條目。

Log4J 從 1.2 起開始支持 NDC,org.apache.log4j.NDC 聲明如下:


代碼 5. NDC 聲明代碼
				  public class NDC {      // 返回診斷堆棧的內容      public static String get();      // 從堆棧的頂端刪除一個元素      public static String pop();      //在堆棧頂端加入一個元素      public static void push(String message);      //察看這個堆棧最頂層的元素,但不刪除它      public static String peek()      // 刪除這個線程的堆棧內容      public static void remove();  }       

要注意的是,org.apache.log4j.NDC 類中所有的方法都是靜態的。假設 NDC 日誌輸出功能被打開,每一次的日誌請求,Log4J 組件都會把當前線程的整個 NDC 堆棧內容輸出在日誌條目中。這樣的過程不需要開發人員寫過多的代碼,程序員只需要在代碼中合適的地方通過 push 和 pop 方法將正確的信息放到 NDC 的堆棧中,然後通過修改 Log4J 的配置文件,指定用戶標誌信息輸出的位置和格式,而原來 Java 代碼中輸出日誌的代碼不需要任何修改,就能夠輸出帶有用戶標誌信息的日誌。

在前面的 Log4J 使用示例 部分,我們曾經講過 Log4J 配置文件中相應的配置信息,其中 PatternLayout 的 ConversionPattern 用於程序員指定日誌輸出的格式。要使用 NDC 的方式輸出用戶標誌信息,只需要在 PatternLayout 的格式定義 ConversionPattern 中使用 %x,就能在相應的位置上輸出 NDC 存儲的上下文信息。具體的使用方法我們將在後面的 在 Web 應用中添加用戶跟蹤 部分進行介紹。


MDC介紹

MDC 和 NDC 相似,也可以減少多線程的系統為每個客戶單獨記錄日誌的系統開銷。它同樣是為每個線程建立一個獨立的存儲空間,開發人員可以根據需要把信息存入其中。不同的是 MDC 使用 Map 的機制來存儲信息,信息以 key/value 對的形式存儲在 Map 中。

Log4J 從 1.3 alpha 版本開始提供對 MDC 的支持,org.apache.log4j.MDC 聲明如下:


代碼 6. MDC 聲明代碼
				  public class MDC {      // 清空map所有的條目。      public static void clear();      // 根據key值返回相應的對象      public static object get(String key);      //返回所有的key值.      public static Enumeration getKeys();      //把key值和關聯的對象,插入map中      public static void put(String key, Object val),      //刪除key對應的對象      public static  remove(String key)  }        

MDC 和 NDC 的使用方法也類似,區別只是在 Log4J配置文件中,在通過 PatternLayout 的 ConversionPattern 來配置日誌的格式的時候,需要使用 %x{key} 來輸出相應的用戶標誌信息對象。

下面,我們通過具體的例子來說明如何在使用 Log4J 的 Web 應用中增加用戶標誌信息,達到進行用戶跟蹤的目的。在開發中,對於使用 NDC 還是 MDC 的機制,要看具體的應用在處理上下文信息的時候,是採用堆棧式的還是 Map 式的方便。下面我們以 NDC 為例進行說明。


在 Web 應用中添加用戶跟蹤

通常,開發人員會在系統的很多地方設置輸出點,輸出日誌。如果要全面的修改這些輸出點使日誌條目附加上所需的信息,是一件繁重的工作。我們可以利用 Servlet 的 filter 來簡化這項工作。Servlets Filter 是 Servlet 2.3 規範中出現的,它能截取用戶從客戶端提交的請求,並在請求沒有到達真正需要訪問的資源前運行一個指定的類。如果我們在這個類中實現 NDC 或 MDC 的功能,就可以大量簡化日誌修改的工作。下面的清單顯示了實現這種修改所需的三個步驟。在這裡,我們使用的是 NDC,您也可以使用 MDC 實現相同的功能。

在下面的例子中,介紹如何在前面已有的 Log4J 使用示例 的 Web 應用代碼的基礎上,通過為 Web 應用的 Servlet 增加一個 filter 的方法,將用戶標誌信息在 filter 中壓入/彈出 NDC 堆棧,而不需要修改任何原來的 Java 程序中的輸出日誌的代碼,使用起來非常簡便。

第 1 步:增加一個處理 NDC 堆棧信息的 filter 類

本例通過在 filter 中取得訪問該 Web 應用的客戶機的IP地址,用以唯一地標識客戶。您也可以和實際的應用程序代碼相配合,使用更加人性化的方式來唯一標識客戶,如取得 Session 中存儲的客戶名稱等。


代碼 7. 在 filter 中增加將用戶標誌信息放入 NDC 堆棧
				  package is.dsw.base.filter;  import javax.servlet.Filter;  import javax.servlet.http.HttpServletRequest;  import javax.servlet.http.HttpSession;  import org.apache.log4j.NDC;  public class Log4jNdcFilter implements Filter {    public void doFilter(ServletRequest request, ServletResponse response,      FilterChain chain) throws IOException, ServletException {      // 獲得客戶的網路地址      String address = request.getRemoteAddr();  	// 把網路地址放入NDC中. 那麼在在layout pattern 中通過使用 %x,就可在每條日之中增加網路地址的信息.  	NDC.push(address);  	//繼續處理其他的filter鏈.  	chain.doFilter(req, res);       // 從NDC的堆棧中刪除網路地址.      NDC.pop();      }  }        

第 2 步:修改 Web 應用的 web.xml 文件

我們需要修改 Web 應用的 web.xml 文件,對 filter 進行設置。在 <web-app< 元素下面增加下列的代碼行:


代碼 8. 修改 web.xml 文件,增加 filter 配置
				  ……  <filter>    <filter-name>NdcFilter</filter-name>    <filter-class> is.dsw.base.filter.Log4jNdcFilter</filter-class>  </filter>  <filter-mapping>    <filter-name>NdcFilter</filter-name>    <url-pattern>/*</url-pattern>  </filter-mapping>  ……        

上述 XML 中的 url-pattern 元素與這個 Web 應用中的所有 URL 匹配。如果只想應用於部分 URL,可以相應地調整模式。

第 3 步:修改 Log4J 的配置文件

需要對 Log4J.properties 文件的配置做一些改變,以便看到 NDC 上下文。在 NDC 簡介部分,我們曾經說過,%x 表示會在每個日誌行上列印當前 NDC 上下文。我們對 Log4J使用示例 中的 Log4J.properties配置文件 進行如下修改,在 PatternLayout 的格式定義 ConversionPattern 中增加 %x, 將 NDC 堆棧中的信息在 %x 指定的位置上進行輸出。如下:


代碼 9. Log4J 配置文件中修改 PatternLayout 的輸出格式
				  log4j.appender.A1.layout.ConversionPattern=%d %p %c %x - %m%n        

完成以上修改之後,每一條記錄都會把我們在 Filter 中 push 進 NDC 堆棧的內容列印出來。仍然以 Log4J 使用示例 中的三個併發用戶訪問為例,我們可以得到如下的日誌信息,和前面不使用 NDC 的方式下 列印的日誌信息相比較,可以看到在原來日誌的基礎上增加了客戶機 IP 地址,這樣可以很容易地區分不同的用戶的信息,為我們進行日誌分析和用戶跟蹤打下了很好的基礎。


圖 2. 應用 NDC 之後,運行 Web 應用后輸出的日誌

或許以上的描述已經讓你了解到如何利用 NDC 和 MDC 的機制來記錄某個用戶的唯一標示,或者跟蹤其他特定於應用的數據。一旦這些用戶標示的數據記入日誌,則能很容易地利用其他工具將他們抽取出來,如 grep。如果我們自定義一個資料庫的 Appenders,把日誌信息插入資料庫的話,還能很容易的對這些數據進行統計和分析。


結論

Log4J 提供了對 NDC 和 MDC 機制的支持,開發人員可以利用此機製為每條日誌記錄增加我們需要的內容。本文通過在 servlet 的 filter 中合適的位置應用很少的代碼,就可以修改整個應用的日誌策略。合理的善用本文所提到的機制,可以節省我們的工作量,也簡化程序中日誌的維護。最終,客戶可以獲得更好的網路應用程序,遇到問題也能從技術支持團隊得到迅速有效的響應。

值得注意的是,我們並不能保證本文所提出的用戶跟蹤方案可以完美地解決所有的問題。如果讀者希望採用本方案,請參考 Apache Log4J 的文檔以了解更多的信息。(責任編輯:A6)



[火星人 ] 在 Web 應用中增加用戶跟蹤功能已經有720次圍觀

http://coctec.com/docs/linux/show-post-69073.html