SLF4J 扩展(二)

事件日志(Event Logging)

EventLogger 类提供了一个简单的机制,这个机制可用于记录应用中发生的事件。

EventLogger 的推荐使用方式,例如在网站应中,将数据填入到 SLF4J 的 MDC,这里数据贯
穿一个请求的始末,这些数据中包含用户 ID,用户的 IP 地址,商品名称等等。这些可以非常容易
地在 Servlet 过滤器(filter)中完成,在这里 MDC 也可以请求结束后清理掉。当一个事件需
要被记录并重现时,一个 EventData 应该被创建并发布。然后调用 EventLogger.logEvent(data)
这里 data 就是指向 EventData 对象的引用。

import org.slf4j.MDC;
import org.apache.commons.lang.time.DateUtils;

import javax.servlet.Filter;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.TimeZone;

public class RequestFilter implements Filter
{
  private FilterConfig filterConfig;
  private static String TZ_NAME = "timezoneOffset";

  public void init(FilterConfig filterConfig) throws ServletException {
    this.filterConfig = filterConfig;
  }

  /**
   * Sample filter that populates the MDC on every request.
   */
  public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
                       FilterChain filterChain) throws IOException, ServletException {
    HttpServletRequest request = (HttpServletRequest)servletRequest;
    HttpServletResponse response = (HttpServletResponse)servletResponse;
    MDC.put("ipAddress", request.getRemoteAddr());
    HttpSession session = request.getSession(false);
    TimeZone timeZone = null;
    if (session != null) {
      // Something should set this after authentication completes
      String loginId = (String)session.getAttribute("LoginId");
      if (loginId != null) {
        MDC.put("loginId", loginId);
      }
      // This assumes there is some javascript on the user's page to create the cookie.
      if (session.getAttribute(TZ_NAME) == null) {
        if (request.getCookies() != null) {
          for (Cookie cookie : request.getCookies()) {
            if (TZ_NAME.equals(cookie.getName())) {
              int tzOffsetMinutes = Integer.parseInt(cookie.getValue());
              timeZone = TimeZone.getTimeZone("GMT");
              timeZone.setRawOffset((int)(tzOffsetMinutes * DateUtils.MILLIS_PER_MINUTE));
              request.getSession().setAttribute(TZ_NAME, tzOffsetMinutes);
              cookie.setMaxAge(0);
              response.addCookie(cookie);
            }
          }
        }
      }
    }
    MDC.put("hostname", servletRequest.getServerName());
    MDC.put("productName", filterConfig.getInitParameter("ProductName"));
    MDC.put("locale", servletRequest.getLocale().getDisplayName());
    if (timeZone == null) {
      timeZone = TimeZone.getDefault();
    }
    MDC.put("timezone", timeZone.getDisplayName());
    filterChain.doFilter(servletRequest, servletResponse);
    MDC.clear();
  }

  public void destroy() {
  }
}

使用 EventLogger 的示例类。

import org.slf4j.ext.EventData;
import org.slf4j.ext.EventLogger;

import java.util.Date;
import java.util.UUID;

public class MyApp {

  public String doFundsTransfer(Account toAccount, Account fromAccount, long amount) {
    toAccount.deposit(amount);
    fromAccount.withdraw(amount);
    EventData data = new EventData();
    data.setEventDateTime(new Date());
    data.setEventType("transfer");
    String confirm = UUID.randomUUID().toString();
    data.setEventId(confirm);
    data.put("toAccount", toAccount);
    data.put("fromAccount", fromAccount);
    data.put("amount", amount);
    EventLogger.logEvent(data);
    return confirm;
  }
}

EventLogger 类使用一个被命名为 EventLogger 的日志。EventLogger 使用 INFO 级别
的日志。下面是一个使用 Logback 的配置。

<configuration>
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <layout class="ch.qos.logback.classic.PatternLayout">
      <Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Pattern>
    </layout>
  </appender>

  <appender name="events" class="ch.qos.logback.core.ConsoleAppender">
    <layout class="ch.qos.logback.classic.PatternLayout">
      <Pattern>%d{HH:mm:ss.SSS} %X - %msg%n</Pattern>
    </layout>
  </appender>

  <logger name="EventLogger" additivity="false">
    <level value="INFO"/>
    <appender appender-ref="events"/>
  </logger>

  <root level="DEBUG">
    <appender-ref ref="STDOUT" />
  </root>

</configuration>

通过Java 代理(agent)来添加日志

入门概要:

  1. Use Java 5 or later.
  2. 使用 Java 5 或者更高。
  3. Download slf4j-ext-1.7.19.jar and javassist.jar, and put them both in the same directory.
  4. 下载 slf4j-ext-1.7.19.jar 和 javassist.jar,并将它们放置在同一个目录中。
  5. Ensure your application is properly configured with slf4j-api-1.7.19.jar and a
    suitable backend.
  6. 确保你的应用已经恰当地配置 slf4j-api-1.7.19.jar 和一个适当的后端。
  7. Instead of “java …​” use “java –javaagent:PATH/slf4j-ext-1.7.19.jar=time,verbose,level=info …​” (replace PATH with the path to the jar)
  8. 使用 java --javaagent:PATH/slf4j-ext-1.7.19.jar=time,verbose,level=info …​ (使用指向 Jar 的路径来替换 PATH) 代替 java …​
  9. That’s it!
  10. 就这些!

在一些应用中,日志是用于追踪程序实际执行情况的,而不是用于记录偶然发生的事情。一种实现方
式是使用扩展日志在程序适当的地方添加语句;但还有一种方式,是使用工具通过修改编译后的字节
码的方式来添加语句。还有其他很多方式存在,当时包含在 slf4j-ext 中的并不是为比较而生的。
这只是提供了一个方法,在既定程序中,快速获取基本的追踪信息。

在 Java 5 中,增加了 Instrumentation 机制,这个机制提供了 Java 代理(agent)功能,它
允许你在字节码加载时,检查和修改字节码。这就可以让原来的类文件保存不变,这种字节码转换只
是在需要加载时才进行。

public class HelloWorld {
  public static void main(String args[]) {
    System.out.println("Hello World");
  }
}

一个典型的转换入下: (import 语句别忽略)

public class LoggingHelloWorld {
  final static Logger _log = LoggerFactory.getLogger(LoggingHelloWorld.class.getName());

  public static void main(String args[]) {
    if (_log.isInfoEnabled()) {
      _log.info("> main(args=" + Arrays.asList(args) + ")");
    }
    System.out.println("Hello World");
    if (_log.isInfoEnabled()) {
      _log.info("< main()");
    }
  }
}

当执行类似 java LoggingHelloWorld 1 2 3 4 时,输出也大致如下:

1 [main] INFO LoggingHelloWorld - > main(args=[1, 2, 3, 4])
Hello World
1 [main] INFO LoggingHelloWorld - < main()

可以使用下面的命令,来达到同样的效果(javassist.jar 和 slf4j-ext-1.7.19.jar 放在了相对路径 ../jars 中)

java -javaagent:../jars/slf4j-ext-1.7.19.jar HelloWorld 1 2 3 4

如何使用

javaagent 可以指定一到多个使用逗号分割的选项。所支持的选项如下:

level=X
The log level to use for the generated log statements. X is one of “info”, “debug” or “trace”. Default is “info”.对于生成日志语句所使用的日志级别。其中 X 可取的值为: infodebugtrace。默认的级别为:info
time
打印出程序启动的当前日期,并且在程序结尾再输出以毫秒计算的程序执行时间。
verbose
Print out when a class is processed as part of being loaded打印出当一个类被装载部分的处理
ignore=X:Y:…​
(高级特性)提供不不需要输出日志的类名前缀,使用冒号分割。默认的列表为:org/slf4j/:ch/qos/logback/:org/apache/log4j/
这还有一个不言自明的事实就是,为了能够输出日志,类必须能够访问 slf4j-api 的类,如果这
些类不能访问给点的类,则肯定不能重塑。

一些类使用 object.toString() 进行呈现时,也许变现行为不当。所以,应该在 logback 配
置文件明确声明日志不可用。在 Apache Jakarta commons lang 包中的 ToStringBuilder 就
是一个极好的例子。对于 logback,可以将下面这个代码片段添加到 logback.xml 中:

<logger name="org.apache.commons.lang.builder" level="OFF" />

这些还没有最终确定,也许还可能会变。

jar 文件的存放位置

javassist 库是用来实际进行字节码操纵的,为了添加任意的日志语句,它必须可用。slf4j-ext-1.7.19
可以像下面这样进行配置:

  • “javassist-3.4.GA.jar” 和 “slf4j-ext-1.7.19.jar” 可以使用 Maven 从仓库下载。而且,
    “slf4j-ext-1.7.19.jar” 在 Maven 库中,直接以 “-javaagent” 参数引用。
  • “javassist-3.4.GA.jar” 和 “slf4j-ext” 在同一个目录下。

当 javassist 没有被代理发现的话,会打印出警告信息。并且指定的字节码转换也不会工作。

其它注意事项

  • Java 代理不能用于已经被类加载器加载过的任何类。
  • Java 代理中的异常通常会被打印出来,也可能被 Java 虚拟机巧巧吞下。
  • Java 代理只会打印到错误输出(System.err)。
  • 日志名称变量是固定的(似乎也没有太大的利用价值),所以,如果这个名字被使用了,将会发生
    错误。这样确定一个没有被使用的名字,然后使用它。
  • 空方法不会被重塑。(针对接口的正确性检查)

(Java 代理是 java.util.logging 版本的一种适配。具体描述在 http://today.java.net/pub/a/today/2008/04/24/add-logging-at-class-load-time-with-instrumentation.html。)

转载自 并发编程网 - ifeve.com

时间: 2024-09-15 16:19:49

SLF4J 扩展(二)的相关文章

SLF4J 扩展(一)

SLF4J 扩展被打包在 slf4j-ext.jar里,它随着 SLF4J 一起发布. 主要内容包含如下几部分: 性能分析器(Profiler) MDCStrLookup 扩展日志(Extended logger) 事件日志(Event Logging) 通过Java 代理(agent)来添加日志 性能分析器(Profiler) 什么是性能分析器? 根据维基百科(Wikipedia)介绍,性能分析(Profiling) 是通过搜集程序时的信息,来对程序行为进行一种研究分析活动,相对于静态的代码分

jQuery 表单验证扩展(二)

前些天写了一篇文章 jQuery 表单验证扩展(一) ,这是jQuery表单验证扩展的一个雏形,里面有了一些基本的功能!昨晚再次努力了一下,对表单中是否必填项验证这个部分做了一些修改的扩充!   一. 存在的问题 在上篇中我已经提到过,验证提示打算用文本和样式两种方式来显示提示消息,这两种提示都只能单独使用,于是新的跟新内容做了一些扩展,使得两者可以共用.前一篇写的是否必填项这个验证只是正对 Text, TextArea 这两个表单元素,在新的扩展中同时也支持了radio,checkbox 两个

Spring自定义配置Schema可扩展(二)_java

命名空间支持 要实现命名空间支持,需要继承自NamespaceHandlerSupport. package com.codestd.spring.cxf.config.schema; import org.springframework.beans.factory.xml.NamespaceHandlerSupport; import com.codestd.spring.cxf.config.EndpointBeanProcessor; /** * 处理命名空间 * @author jaun

C++程序设计-第15周 数据结构扩展与GUI开发体验

课程首页地址:http://blog.csdn.net/sxhelijian/article/details/7910565 [目的] 1. 体验用面向对象的方法操作数组和动态链表2. 体验窗口程序的实现 第一部分 引言 大学中的学习死守着课本非常的没有劲.我不是说课本和课堂没用,而是说在课内的学习之余要有所拓展和扩充.大学的课程(和课本)成为一个体系,受到各种因素的制约,势必会形成一个框框,所涉及的内容可能就会形成"铁路警察,各管一段"的局面.课程和课本是有局限的,采用的是"

jQuery 表单验证扩展(三)_jquery

在看这篇文章之前, 可以看看前两篇文章,表单验证输入范围验证是在原有的基础上改写的. (一). 输入范围验证存在问题 在第二篇上提到的问题,在原有的验证中也存在相同的问题.当然在这次改写中也解决了一些这些问题.同时也添加了对radio,checkbox,select 元素的验证.当然对于时间的验证仍没有解决,后续过程中会继续补充! (二). 验证参数的设计 onEmptyText: 当输入内容为空的时候显示文本 onEmptyClass: 当输入内容为空的时候显示样式 onSuccessText

如何将网赚扩展为事业

中介交易 http://www.aliyun.com/zixun/aggregation/6858.html">SEO诊断 淘宝客 云主机 技术大厅 这个问题可能很多赚友都没有认真想过,有很大一部分赚友就在幻想能靠网赚养活自己.他单纯停留在想这个范畴,而实际行动却无跟进. 很大一部分赚友第一次接触到的网赚就是点击网赚.调查网赚等这些简单网赚项目.注册上百个站点,纯单干,点了老半年广告,做到支付的可能只有那10几.20来个,收入甚微.于是,不停重复找寻新站点,不停的点广告,如此循环.如果,单

jQuery 表单验证扩展(四)

周末写的 jQuery 表单验证扩展(三)  这篇文章点击率过低,不知道是文章太失水准还是什么其他原因,这里写文章只是为了分享一下自己写代码的心得,同时也是巩固自己所学的东西!如果文章中存在问题,请大家多多斧正!本篇文章介绍jQuery 表单验证扩展中的控件值的比较 文章回顾: jQuery 表单验证扩展(三) jQuery 表单验证扩展(二) jQuery 表单验证扩展(一)     (一). 存在的问题 这篇文章和第一篇中提到的控件值之间的比较没有多大的区别,唯一更近的就是在样式的处理.同时

jQuery 表单验证扩展(五)

大概在一个月之前,自己尝试着写一个jQuery的表单验证插件,当初的目的并不是为了让这个插件流行起来,只是为了自己学习的需要.由于工作的问题,这个问题一直处于停留的状态.前些天报名参加了那个什么博客大赛,最近未写一篇博客,当然本文也不是了参加什么博客大赛而写,只是作为自己学习的积累.今天抽空重新整理了一下插件中的正则表达式的验证,在此分享一下.希望大家多多指教   文章回顾: jQuery 表单验证扩展(一)  jQuery 表单验证扩展(二)  jQuery 表单验证扩展(三)  jQuery

设计模式——控制反转&amp;amp;依赖注入

一.控制反转: 从简单的代码示例入手: /// <summary> /// 邮件服务类 /// </summary> public class EmailService { public string SendMessage() { return "发送通知邮件"; } } /// <summary> /// 邮件通知类 /// </summary> public class NotifycationSystem { private Em