设计一个中间件的访问日志组件

对任何一个系统,一个强大的日志记录功能是相当重要且必要的,根据日志的记录可以及时掌握系统运行时的健康状态及故障定位。然而作为web容器存在另外一种日志——访问日志。访问日志一般会记录客户端的访问相关信息,包括客户端ip、请求时间、请求协议、请求方法、请求字节数、响应码、会话id、处理时间等等。通过访问日志可以统计访问用户的数量、访问时间分布等规律及个人爱好等等,而这些数据可以帮助公司在运营策略上做出抉择。


如果让你来设计一个访问日志组件你会如何来设计?你应该很快就会想到访问日志的核心功能就是将信息记录下来,至于要记录到哪里、以哪种形式来记录我们先不管,于是很快想到面向接口编程定义一个接口AccessLog,方法名就命名为log吧,需要传递参数包含请求对象和响应对象,如下,

public interface AccessLog {

 public void log(Request request, Response response);

}

定义一个好的接口是一个良好的开始,接下去要考虑的事是需要哪些类型的组件,针对前面的记录到哪里、以哪种形式记录我们最熟悉也最先想到的肯定就是以文件形式记录到磁盘里,于是我们来实现一个文件记录的访问日志组件吧。

public class FileAccessLog implements AccessLog{

public void log(Request request, Response response){

      String message=request与response中的值拼组成你需要的字符串。

try {

    Charset charset = Charset.defaultCharset();

    PrintWriter writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(

                    new FileOutputStream("c:/accesslog.log", true), charset), 128000),

                    false);

    writer.println(message);

    writer.flush();

 } catch (IOException e) {

}

}

}

看起来这是一个简单且不错的文件记录访问日志组件的实现,起码用于例子展示让人一看就觉得简单明了,采用PrintWriter对象用于写入操作,而且使用了BufferedWriter添加一个缓冲作用(使用Buffered如何能达到缓冲效果,如果你忘了请看我前面写的缓冲装置相关章节,看完一定会对缓冲有一个深入的了解),之所以把缓冲大小设置为128000是经验得出来的一个适合值,OutputStreamWriter则可以让字符进行编码,此处使用Charset工具提供的默认编码,FileOutputStream则指定写入的文件路径及文件名,而true表明追加日志而非覆盖。

假如你觉得用sql语言来统计日志的信息让你更加得心应手,那就写到文件就不符合需求,我们需要另外一个实现,通过jdbc将日志记录到数据库中。于是你必须另外建一个JDBCAccessLog类并重新实现log方法,使用JDBC操作数据库大家是最熟悉不过的了,受篇幅限制不详细写实现细节,但有一个前提是你必须跟数据库约好创建一张特定的表且表的结构要根据访问信息定义好。

public class JDBCAccessLog implements AccessLog{

public void log(Request request, Response response){

通过JDBCrequestresponse包含的访问信息组成一个sql语句插入数据库。

}

}

你还可以根据自己的需求定制各种各样的访问日志组件,只需实现AccessLog接口。但有时可能你会使用多个访问日志组件,例如又写入文件又持久化到数据库中,这时我们还是提供一个设配器给他吧,

public class AccessLogAdapter implements AccessLog {

    private AccessLog[] logs;

    public AccessLogAdapter(AccessLog log) {

        logs = new AccessLog[] { log };

    }

    public void add(AccessLog log) {

        AccessLog newArray[] = Arrays.copyOf(logs, logs.length + 1);

        newArray[newArray.length - 1] = log;

        logs = newArray;

    }

    public void log(Request request, Response response) {

        for (AccessLog log: logs) {

            log.log(request, response);

        }

    }

}

经过适配器的适配,log方法已经变成了遍历去调用多个访问日志组件的log方法,而适配器提供给对外的接口仍然是一个log方法,编写如下测试类log的调用将会分别向文件及数据库记录下hello tomcat

public class Test{

public static void main(String[] args){

AccessLog accessLog = new AccessLogAdapter(new FileAccessLog());

accessLog.add(new JDBCAccessLog()); 

accessLog.log(new Request("hello tomcat"),new Response());

}

}

经过以上的设计一个良好的访问日志组件就已经成型,而这也是Tomcat的访问日志组件的设计思路,而且Tomcat考虑到模块化和可配置扩展,它把访问日志组件作为一个管道中的一个阀门(管道机制忘了的朋友请看前面管道机制章节),这样就可以通过Tomcat的服务器配置文件配置实现访问日志记录功能,可以在任意容器中进行配置。


点击订购作者《Tomcat内核设计剖析》



上一篇:Nginx设置Js、Css等静态文件的缓存过期时间


下一篇:改善代码设计 —— 简化条件表达式(Simplifying Conditional Expressions)